mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-23 10:51:30 +00:00
Compare commits
67 Commits
postgres_q
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccfc219ba3 | ||
|
|
b954b14f56 | ||
|
|
ed9c7dbb6b | ||
|
|
aadbb50e7d | ||
|
|
abff435e88 | ||
|
|
d1c4059320 | ||
|
|
9b214a3171 | ||
|
|
ba66aa939b | ||
|
|
f9af595eee | ||
|
|
7ce8c7a5a1 | ||
|
|
9ad3f589cc | ||
|
|
9267b2cc18 | ||
|
|
c7d1b2dcdb | ||
|
|
a526410e44 | ||
|
|
58cb85156c | ||
|
|
dd5ccade2b | ||
|
|
38ebef6c02 | ||
|
|
7ad5862e8f | ||
|
|
755b732532 | ||
|
|
1b5a9b83ff | ||
|
|
fb8871cfbf | ||
|
|
874cec26fc | ||
|
|
9d2d0e4754 | ||
|
|
c7c894d6d9 | ||
|
|
547954c3dc | ||
|
|
7f220bf8be | ||
|
|
1ee6abf890 | ||
|
|
72c3605dbe | ||
|
|
a7a38807df | ||
|
|
1285ffc440 | ||
|
|
b4bca3d41a | ||
|
|
2a47e4311c | ||
|
|
49d9f489d8 | ||
|
|
31cc129aa7 | ||
|
|
99af4acca4 | ||
|
|
80dd161a6f | ||
|
|
850f1dfb97 | ||
|
|
a827e79317 | ||
|
|
7dbccff41d | ||
|
|
9184684e75 | ||
|
|
701f486d8f | ||
|
|
5059917edf | ||
|
|
ab1409efb1 | ||
|
|
5de9e682ba | ||
|
|
b2ab979360 | ||
|
|
2f32a676d0 | ||
|
|
950c8ee470 | ||
|
|
b0eaac5b84 | ||
|
|
952491a3ad | ||
|
|
5dde66b032 | ||
|
|
5b1db2778c | ||
|
|
1213788f9c | ||
|
|
1ce3adff0f | ||
|
|
00eb07da11 | ||
|
|
afe59c1589 | ||
|
|
53b5ebd39c | ||
|
|
5b365e642f | ||
|
|
333b3de587 | ||
|
|
e909ac43f4 | ||
|
|
8433a027ad | ||
|
|
81dfd76198 | ||
|
|
7c77ffda6c | ||
|
|
a34d3bb000 | ||
|
|
42731d1aae | ||
|
|
3abbb63adc | ||
|
|
2e618cb3c4 | ||
|
|
beca0d6608 |
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}"
|
||||
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
|
||||
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}"
|
||||
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
|
||||
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 "GitHub" -Password "$AZURE_DEVOPS_PAT"
|
||||
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "jawelton@microsoft.com" -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 "GitHub" -Password "$AZURE_DEVOPS_PAT"
|
||||
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "jawelton@microsoft.com" -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
|
||||
|
||||
41
SECURITY.md
Normal file
41
SECURITY.md
Normal file
@@ -0,0 +1,41 @@
|
||||
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.7 BLOCK -->
|
||||
|
||||
## Security
|
||||
|
||||
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
|
||||
|
||||
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||
|
||||
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
|
||||
|
||||
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
|
||||
|
||||
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
|
||||
|
||||
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
|
||||
|
||||
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
|
||||
* Full paths of source file(s) related to the manifestation of the issue
|
||||
* The location of the affected source code (tag/branch/commit or direct URL)
|
||||
* Any special configuration required to reproduce the issue
|
||||
* Step-by-step instructions to reproduce the issue
|
||||
* Proof-of-concept or exploit code (if possible)
|
||||
* Impact of the issue, including how an attacker might exploit the issue
|
||||
|
||||
This information will help us triage your report more quickly.
|
||||
|
||||
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
|
||||
|
||||
## Preferred Languages
|
||||
|
||||
We prefer all communications to be in English.
|
||||
|
||||
## Policy
|
||||
|
||||
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
|
||||
|
||||
<!-- END MICROSOFT SECURITY.MD BLOCK -->
|
||||
@@ -1,5 +1,6 @@
|
||||
<!doctype html>
|
||||
<html class="default no-js">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
@@ -9,256 +10,292 @@
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
</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>
|
||||
</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>
|
||||
<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 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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</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>Keys blade of a Azure Cosmos DB for Apache Cassandra account.</p>
|
||||
</div>
|
||||
</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>
|
||||
</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>
|
||||
</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>Keys blade of a Cassandra API account.</p>
|
||||
</div>
|
||||
</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 Gremlin API 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 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>
|
||||
</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>
|
||||
</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>
|
||||
<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>
|
||||
<div class="container tsd-generator">
|
||||
<p>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p>
|
||||
</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>
|
||||
<div class="overlay"></div>
|
||||
<script src="../assets/js/main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
35
images/Copilot.svg
Normal file
35
images/Copilot.svg
Normal file
@@ -0,0 +1,35 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="40" height="40" fill="white"/>
|
||||
<path d="M17 3.73926L20 4.04436V12.6862L15.2 13.8013L13.6 15.9967V19.4479C13.6 21.7669 14.8545 23.9045 16.879 25.0353L21.4036 27.5626L13.6 31.69L10.3559 32.1523L7.27902 30.4337C5.25445 29.3028 4 27.1652 4 24.8462V14.7591C4 12.4395 5.25512 10.3015 7.28055 9.17085L16.3174 4.12639L16.3121 4.13012L17 3.73926Z" fill="url(#paint0_radial_420_101763)"/>
|
||||
<path d="M17 3.73926L20 4.04436V12.6862L15.2 13.8013L13.6 15.9967V19.4479C13.6 21.7669 14.8545 23.9045 16.879 25.0353L21.4036 27.5626L13.6 31.69L10.3559 32.1523L7.27902 30.4337C5.25445 29.3028 4 27.1652 4 24.8462V14.7591C4 12.4395 5.25512 10.3015 7.28055 9.17085L16.3174 4.12639L16.3121 4.13012L17 3.73926Z" fill="url(#paint1_linear_420_101763)"/>
|
||||
<path d="M26.399 15.001L34.399 19.801L35.999 21.401V24.8452C35.999 27.1642 34.7446 29.3018 32.72 30.4327L23.12 35.7949C21.1804 36.8784 18.8177 36.8784 16.878 35.7949L7.27804 30.4327C7.09146 30.3284 6.91141 30.2157 6.73828 30.095L7.278 30.3965C9.21762 31.4799 11.5803 31.4799 13.52 30.3965L23.12 25.0342C25.1445 23.9033 26.399 21.7657 26.399 19.4467V15.001Z" fill="url(#paint2_radial_420_101763)"/>
|
||||
<path d="M26.399 15.001L34.399 19.801L35.999 21.401V24.8452C35.999 27.1642 34.7446 29.3018 32.72 30.4327L23.12 35.7949C21.1804 36.8784 18.8177 36.8784 16.878 35.7949L7.27804 30.4327C7.09146 30.3284 6.91141 30.2157 6.73828 30.095L7.278 30.3965C9.21762 31.4799 11.5803 31.4799 13.52 30.3965L23.12 25.0342C25.1445 23.9033 26.399 21.7657 26.399 19.4467V15.001Z" fill="url(#paint3_linear_420_101763)"/>
|
||||
<path d="M32.7191 9.17053L23.119 3.8117C21.1802 2.72943 18.819 2.72943 16.8802 3.8117L16.3151 4.1271C14.6239 5.31737 13.5996 7.26472 13.5996 9.36025V16.0461L16.8802 14.2149C18.819 13.1326 21.1802 13.1326 23.119 14.2149L32.7191 19.5737C34.6984 20.6786 35.9421 22.7456 35.9977 25.0039C35.999 24.9514 35.9996 24.8987 35.9996 24.8459V14.7588C35.9996 12.4392 34.7445 10.3011 32.7191 9.17053Z" fill="url(#paint4_radial_420_101763)"/>
|
||||
<path d="M32.7191 9.17053L23.119 3.8117C21.1802 2.72943 18.819 2.72943 16.8802 3.8117L16.3151 4.1271C14.6239 5.31737 13.5996 7.26472 13.5996 9.36025V16.0461L16.8802 14.2149C18.819 13.1326 21.1802 13.1326 23.119 14.2149L32.7191 19.5737C34.6984 20.6786 35.9421 22.7456 35.9977 25.0039C35.999 24.9514 35.9996 24.8987 35.9996 24.8459V14.7588C35.9996 12.4392 34.7445 10.3011 32.7191 9.17053Z" fill="url(#paint5_linear_420_101763)"/>
|
||||
<defs>
|
||||
<radialGradient id="paint0_radial_420_101763" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(17.5054 12.8837) rotate(112.544) scale(23.4519 19.3067)">
|
||||
<stop offset="0.206732" stop-color="#45D586"/>
|
||||
<stop offset="0.875628" stop-color="#128245"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint1_linear_420_101763" x1="15.0625" y1="29.6174" x2="13.787" y2="27.2436" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.9999" stop-color="#0F773F"/>
|
||||
<stop offset="1" stop-color="#0078D4" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint2_radial_420_101763" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(10.899 27.5214) rotate(-3.9995) scale(24.6197 15.4594)">
|
||||
<stop offset="0.140029" stop-color="#FBFF47"/>
|
||||
<stop offset="0.952721" stop-color="#54B228"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint3_linear_420_101763" x1="30.8932" y1="18.5508" x2="29.5491" y2="20.8921" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.9999" stop-color="#27770B"/>
|
||||
<stop offset="1" stop-color="#8C66BA" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint4_radial_420_101763" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(30.632 19.3878) rotate(-138.33) scale(22.8015 13.0047)">
|
||||
<stop stop-color="#95FEA0"/>
|
||||
<stop offset="0.839255" stop-color="#10B7B7"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint5_linear_420_101763" x1="13.5996" y1="11.7134" x2="16.2131" y2="11.7134" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.9999" stop-color="#0A7B7B"/>
|
||||
<stop offset="1" stop-color="#436DCD" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
3
images/YoutubePlaceholder.svg
Normal file
3
images/YoutubePlaceholder.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="334" height="154" viewBox="0 0 334 154" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0.5" y="0.5" width="333" height="153" fill="#F3F2F1" stroke="#E1DFDD"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 188 B |
BIN
images/firewallRule.png
Normal file
BIN
images/firewallRule.png
Normal file
Binary file not shown.
|
After 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="#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"/>
|
||||
<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"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 652 B After Width: | Height: | Size: 649 B |
@@ -18,7 +18,8 @@ module.exports = {
|
||||
// clearMocks: false,
|
||||
|
||||
// Indicates whether the coverage information should be collected while executing the test
|
||||
collectCoverage: true,
|
||||
|
||||
collectCoverage: process.env.skipCodeCoverage === "true" ? false : true,
|
||||
|
||||
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
||||
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,15 +388,14 @@ 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;
|
||||
}
|
||||
@@ -410,112 +409,111 @@ 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 {
|
||||
@@ -524,4 +522,4 @@ input::-webkit-inner-spin-button {
|
||||
width: 100%;
|
||||
padding-top: 10px;
|
||||
}
|
||||
}*/
|
||||
}*/
|
||||
|
||||
@@ -2576,6 +2576,10 @@ 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;
|
||||
@@ -3087,3 +3091,4 @@ a:link {
|
||||
background: white;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,17 @@
|
||||
overflow: auto;
|
||||
|
||||
.databaseHeader {
|
||||
padding: 1px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.collectionHeader {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.loadMoreHeader {
|
||||
color: RGB(5, 99, 193);
|
||||
}
|
||||
}
|
||||
|
||||
.notebookResourceTree {
|
||||
@@ -23,6 +28,4 @@
|
||||
.clickDisabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
2101
package-lock.json
generated
2101
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -162,7 +162,7 @@
|
||||
"jest-playwright-preset": "1.5.1",
|
||||
"jest-trx-results-processor": "0.0.7",
|
||||
"less": "3.8.1",
|
||||
"less-loader": "4.1.0",
|
||||
"less-loader": "11.1.3",
|
||||
"less-vars-loader": "1.1.0",
|
||||
"mini-css-extract-plugin": "2.1.0",
|
||||
"monaco-editor-webpack-plugin": "1.7.0",
|
||||
|
||||
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 { userContext } from "../UserContext";
|
||||
import { getApiShortDisplayName } from "../Utils/APITypeUtils";
|
||||
import { NormalizedEventKey } from "./Constants";
|
||||
|
||||
export interface CollapsedResourceTreeProps {
|
||||
@@ -36,7 +36,7 @@ export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps
|
||||
id="collapseToggleLeftPaneButton"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label="Expand Tree"
|
||||
aria-label={getApiShortDisplayName() + `Expand tree`}
|
||||
onClick={toggleLeftPaneExpanded}
|
||||
onKeyPress={onKeyPressToggleLeftPaneExpanded}
|
||||
ref={focusButton}
|
||||
@@ -45,7 +45,7 @@ export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps
|
||||
<img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" />
|
||||
</span>
|
||||
<span className="collectionCollapsed">
|
||||
<span>{userContext.apiType} API</span>
|
||||
<span>{getApiShortDisplayName()}</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -45,6 +45,8 @@ 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;
|
||||
@@ -139,7 +141,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;
|
||||
@@ -426,3 +428,6 @@ export class JunoEndpoints {
|
||||
public static readonly Prod = "https://tools.cosmos.azure.com";
|
||||
public static readonly Stage = "https://tools-staging.cosmos.azure.com";
|
||||
}
|
||||
|
||||
export const QueryCopilotSampleDatabaseId = "CopilotSampleDb";
|
||||
export const QueryCopilotSampleContainerId = "SampleContainer";
|
||||
|
||||
@@ -55,7 +55,6 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
|
||||
label={entityValueLabel && entityValueLabel}
|
||||
className="addEntityTextField"
|
||||
id="entityValueId"
|
||||
autoFocus
|
||||
disabled={isEntityValueDisable}
|
||||
type={entityValueType}
|
||||
placeholder={entityValuePlaceholder}
|
||||
|
||||
14
src/Common/EnvironmentUtility.test.ts
Normal file
14
src/Common/EnvironmentUtility.test.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
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 * as OfferUtility from "./OfferUtility";
|
||||
import { SDKOfferDefinition, Offer } from "../Contracts/DataModels";
|
||||
import { OfferResponse } from "@azure/cosmos";
|
||||
import { Offer, SDKOfferDefinition } from "../Contracts/DataModels";
|
||||
import * as OfferUtility from "./OfferUtility";
|
||||
|
||||
describe("parseSDKOfferResponse", () => {
|
||||
it("manual throughput", () => {
|
||||
@@ -31,6 +31,26 @@ 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,6 +6,7 @@ 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 {
|
||||
@@ -42,7 +43,7 @@ export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps
|
||||
{/* Collections Window Title/Command Bar - Start */}
|
||||
<div className="collectiontitle">
|
||||
<div className="coltitle">
|
||||
<span className="titlepadcol">{userContext.apiType} API</span>
|
||||
<span className="titlepadcol">{getApiShortDisplayName()}</span>
|
||||
<div className="float-right">
|
||||
<span
|
||||
className="padimgcolrefresh"
|
||||
@@ -50,7 +51,7 @@ export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps
|
||||
role="button"
|
||||
data-bind="click: onRefreshResourcesClick, clickBubble: false, event: { keypress: onRefreshDatabasesKeyPress }"
|
||||
tabIndex={0}
|
||||
aria-label="Refresh tree"
|
||||
aria-label={getApiShortDisplayName() + `Refresh tree`}
|
||||
title="Refresh tree"
|
||||
>
|
||||
<img className="refreshcol" src={refreshImg} alt="Refresh Tree" />
|
||||
@@ -62,7 +63,7 @@ export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps
|
||||
onClick={toggleLeftPaneExpanded}
|
||||
onKeyPress={onKeyPressToggleLeftPaneExpanded}
|
||||
tabIndex={0}
|
||||
aria-label="Collapse Tree"
|
||||
aria-label={getApiShortDisplayName() + `Collapse Tree`}
|
||||
title="Collapse Tree"
|
||||
ref={focusButton}
|
||||
>
|
||||
|
||||
@@ -73,6 +73,17 @@ 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;
|
||||
@@ -126,12 +137,29 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
|
||||
/>
|
||||
{!isEntityValueDisable && (
|
||||
<TooltipHost content="Edit property" id="editTooltip">
|
||||
<Image {...imageProps} src={EditIcon} alt="editEntity" id="editEntity" onClick={onEditEntity} />
|
||||
<div tabIndex={0}>
|
||||
<Image
|
||||
{...imageProps}
|
||||
src={EditIcon}
|
||||
alt="editEntity"
|
||||
onClick={onEditEntity}
|
||||
tabIndex={0}
|
||||
onKeyPress={handleKeyPress}
|
||||
/>
|
||||
</div>
|
||||
</TooltipHost>
|
||||
)}
|
||||
{isDeleteOptionVisible && userContext.apiType !== "Cassandra" && (
|
||||
<TooltipHost content="Delete property" id="deleteTooltip">
|
||||
<Image {...imageProps} src={DeleteIcon} alt="delete entity" id="deleteEntity" onClick={onDeleteEntity} />
|
||||
<Image
|
||||
{...imageProps}
|
||||
src={DeleteIcon}
|
||||
alt="delete entity"
|
||||
id="deleteEntity"
|
||||
onClick={onDeleteEntity}
|
||||
tabIndex={0}
|
||||
onKeyPress={handleKeyPressdelete}
|
||||
/>
|
||||
</TooltipHost>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -9,7 +9,7 @@ export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ children }:
|
||||
return (
|
||||
<span>
|
||||
<TooltipHost content={children}>
|
||||
<Icon iconName="Info" ariaLabel="Info" className="panelInfoIcon" tabIndex={0} />
|
||||
<Icon iconName="Info" ariaLabel={children} className="panelInfoIcon" tabIndex={0} />
|
||||
</TooltipHost>
|
||||
</span>
|
||||
);
|
||||
|
||||
49
src/Common/UrlUtility.test.ts
Normal file
49
src/Common/UrlUtility.test.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
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,6 +4,7 @@ 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();
|
||||
@@ -13,7 +14,7 @@ export const deleteDocument = async (collection: CollectionBase, documentId: Doc
|
||||
await client()
|
||||
.database(collection.databaseId)
|
||||
.container(collection.id())
|
||||
.item(documentId.id(), documentId.partitionKeyValue?.length === 0 ? undefined : documentId.partitionKeyValue)
|
||||
.item(documentId.id(), getPartitionKeyValue(documentId))
|
||||
.delete();
|
||||
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
|
||||
} catch (error) {
|
||||
|
||||
12
src/Common/dataAccess/getPartitionKeyValue.ts
Normal file
12
src/Common/dataAccess/getPartitionKeyValue.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
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,3 +1,4 @@
|
||||
import { Queries } from "Common/Constants";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { userContext } from "../../UserContext";
|
||||
@@ -31,6 +32,35 @@ 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,6 +6,7 @@ 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();
|
||||
@@ -21,8 +22,7 @@ export const readDocument = async (collection: CollectionBase, documentId: Docum
|
||||
const response = await client()
|
||||
.database(collection.databaseId)
|
||||
.container(collection.id())
|
||||
// use undefined if the partitionKeyValue is empty
|
||||
.item(documentId.id(), documentId.partitionKeyValue?.length === 0 ? undefined : documentId.partitionKeyValue)
|
||||
.item(documentId.id(), getPartitionKeyValue(documentId))
|
||||
.read(options);
|
||||
|
||||
return response?.resource;
|
||||
|
||||
@@ -6,6 +6,7 @@ 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,
|
||||
@@ -25,7 +26,7 @@ export const updateDocument = async (
|
||||
const response = await client()
|
||||
.database(collection.databaseId)
|
||||
.container(collection.id())
|
||||
.item(documentId.id(), documentId.partitionKeyValue?.length === 0 ? undefined : documentId.partitionKeyValue)
|
||||
.item(documentId.id(), getPartitionKeyValue(documentId))
|
||||
.replace(newDocument, options);
|
||||
|
||||
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
|
||||
|
||||
@@ -33,6 +33,8 @@ export interface DatabaseAccountExtendedProperties {
|
||||
privateEndpointConnections?: unknown[];
|
||||
capacity?: { totalThroughputLimit: number };
|
||||
locations?: DatabaseAccountResponseLocation[];
|
||||
postgresqlEndpoint?: string;
|
||||
publicNetworkAccess?: string;
|
||||
}
|
||||
|
||||
export interface DatabaseAccountResponseLocation {
|
||||
@@ -154,6 +156,11 @@ export interface Collection extends Resource {
|
||||
requestSchema?: () => void;
|
||||
}
|
||||
|
||||
export interface CollectionsWithPagination {
|
||||
continuationToken?: string;
|
||||
collections?: Collection[];
|
||||
}
|
||||
|
||||
export interface Database extends Resource {
|
||||
collections?: Collection[];
|
||||
}
|
||||
@@ -566,6 +573,16 @@ 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,6 +35,9 @@ 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(): Promise<void>;
|
||||
loadCollections(restart?: boolean): Promise<void>;
|
||||
findCollectionWithId(collectionId: string): Collection;
|
||||
openAddCollection(database: Database, event: MouseEvent): void;
|
||||
onSettingsClick: () => void;
|
||||
@@ -186,7 +186,6 @@ export interface Collection extends CollectionBase {
|
||||
onDrop(source: Collection, event: { originalEvent: DragEvent }): void;
|
||||
uploadFiles(fileList: FileList): Promise<{ data: UploadDetailsRecord[] }>;
|
||||
|
||||
getLabel(): string;
|
||||
getPendingThroughputSplitNotification(): Promise<DataModels.Notification>;
|
||||
}
|
||||
|
||||
@@ -372,6 +371,7 @@ export enum TerminalKind {
|
||||
Default = 0,
|
||||
Mongo = 1,
|
||||
Cassandra = 2,
|
||||
Postgres = 3,
|
||||
}
|
||||
|
||||
export interface DataExplorerInputsFrame {
|
||||
@@ -395,6 +395,11 @@ 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;
|
||||
|
||||
@@ -11,9 +11,9 @@ import DeleteTriggerIcon from "../../images/DeleteTrigger.svg";
|
||||
import DeleteUDFIcon from "../../images/DeleteUDF.svg";
|
||||
import HostedTerminalIcon from "../../images/Hosted-Terminal.svg";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import { useSidePanel } from "../hooks/useSidePanel";
|
||||
import { userContext } from "../UserContext";
|
||||
import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils";
|
||||
import { useSidePanel } from "../hooks/useSidePanel";
|
||||
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
|
||||
import Explorer from "./Explorer";
|
||||
import { useNotebook } from "./Notebook/useNotebook";
|
||||
@@ -152,7 +152,7 @@ export const createStoreProcedureContextMenuItems = (
|
||||
{
|
||||
iconSrc: DeleteSprocIcon,
|
||||
onClick: () => storedProcedure.delete(),
|
||||
label: "Delete Store Procedure",
|
||||
label: "Delete Stored Procedure",
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
@@ -73,7 +73,7 @@ export class AccordionItemComponent extends React.Component<AccordionItemCompone
|
||||
<img
|
||||
className="expandCollapseIcon"
|
||||
src={this.state.isExpanded ? TriangleDownIcon : TriangleRightIcon}
|
||||
alt="Hide"
|
||||
alt={this.state.isExpanded ? `${this.props.title} hide` : `${this.props.title} expand`}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* 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";
|
||||
@@ -40,6 +41,7 @@ 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();
|
||||
}
|
||||
|
||||
@@ -74,6 +76,8 @@ 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">
|
||||
<div className="infoPanelMain" tabIndex={0}>
|
||||
<Icon className="infoIconMain" iconName="Help" styles={{ root: { verticalAlign: "middle" } }} />
|
||||
<Label className="infoLabelMain">Help</Label>
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,7 @@ exports[`InfoComponent renders 1`] = `
|
||||
>
|
||||
<div
|
||||
className="infoPanelMain"
|
||||
tabIndex={0}
|
||||
>
|
||||
<Icon
|
||||
className="infoIconMain"
|
||||
|
||||
@@ -310,7 +310,16 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
||||
/>
|
||||
)}
|
||||
|
||||
{this.isLargePartitionKeyEnabled() && <Text>Large {this.partitionKeyName.toLowerCase()} has been enabled</Text>}
|
||||
{userContext.apiType === "SQL" && this.isLargePartitionKeyEnabled() && (
|
||||
<Text>Large {this.partitionKeyName.toLowerCase()} has been enabled.</Text>
|
||||
)}
|
||||
|
||||
{userContext.apiType === "SQL" &&
|
||||
(this.isHierarchicalPartitionedContainer() ? (
|
||||
<Text>Hierarchically partitioned container.</Text>
|
||||
) : (
|
||||
<Text>Non-hierarchically partitioned container.</Text>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -328,6 +337,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
||||
};
|
||||
|
||||
public isLargePartitionKeyEnabled = (): boolean => this.props.collection.partitionKey?.version >= 2;
|
||||
public isHierarchicalPartitionedContainer = (): boolean => this.props.collection.partitionKey?.kind === "MultiHash";
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
|
||||
@@ -82,7 +82,6 @@ interface ThroughputInputAutoPilotV3State {
|
||||
spendAckChecked: boolean;
|
||||
exceedFreeTierThroughput: boolean;
|
||||
}
|
||||
|
||||
export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
ThroughputInputAutoPilotV3Props,
|
||||
ThroughputInputAutoPilotV3State
|
||||
@@ -624,7 +623,10 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
return (
|
||||
<>
|
||||
{warningMessage && (
|
||||
<MessageBar messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}>
|
||||
<MessageBar
|
||||
messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}
|
||||
role="alert"
|
||||
>
|
||||
{warningMessage}
|
||||
</MessageBar>
|
||||
)}
|
||||
|
||||
@@ -15,6 +15,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
||||
"iconName": "WarningSolid",
|
||||
}
|
||||
}
|
||||
role="alert"
|
||||
>
|
||||
<Text
|
||||
styles={
|
||||
|
||||
@@ -218,7 +218,10 @@ exports[`SubSettingsComponent analyticalTimeToLive hidden 1`] = `
|
||||
<Text>
|
||||
Large
|
||||
partition key
|
||||
has been enabled
|
||||
has been enabled.
|
||||
</Text>
|
||||
<Text>
|
||||
Non-hierarchically partitioned container.
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
@@ -495,7 +498,10 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
|
||||
<Text>
|
||||
Large
|
||||
partition key
|
||||
has been enabled
|
||||
has been enabled.
|
||||
</Text>
|
||||
<Text>
|
||||
Non-hierarchically partitioned container.
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
@@ -734,7 +740,10 @@ exports[`SubSettingsComponent changeFeedPolicy hidden 1`] = `
|
||||
<Text>
|
||||
Large
|
||||
partition key
|
||||
has been enabled
|
||||
has been enabled.
|
||||
</Text>
|
||||
<Text>
|
||||
Non-hierarchically partitioned container.
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
@@ -1036,7 +1045,10 @@ exports[`SubSettingsComponent renders 1`] = `
|
||||
<Text>
|
||||
Large
|
||||
partition key
|
||||
has been enabled
|
||||
has been enabled.
|
||||
</Text>
|
||||
<Text>
|
||||
Non-hierarchically partitioned container.
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
@@ -1313,7 +1325,10 @@ exports[`SubSettingsComponent timeToLiveSeconds hidden 1`] = `
|
||||
<Text>
|
||||
Large
|
||||
partition key
|
||||
has been enabled
|
||||
has been enabled.
|
||||
</Text>
|
||||
<Text>
|
||||
Non-hierarchically partitioned container.
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -35,6 +35,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {
|
||||
"armResourceId": undefined,
|
||||
"retryOptions": Object {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
@@ -111,6 +112,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onRefreshDatabasesKeyPress": [Function],
|
||||
"onRefreshResourcesClick": [Function],
|
||||
"phoenixClient": PhoenixClient {
|
||||
"armResourceId": undefined,
|
||||
"retryOptions": Object {
|
||||
"maxTimeout": 5000,
|
||||
"minTimeout": 5000,
|
||||
|
||||
@@ -46,10 +46,13 @@ export class TabComponent extends React.Component<TabComponentProps> {
|
||||
}
|
||||
|
||||
let className = "toggleSwitch";
|
||||
let ariaselected;
|
||||
if (index === this.props.currentTabIndex) {
|
||||
className += " selectedToggle";
|
||||
ariaselected = true;
|
||||
} else {
|
||||
className += " unselectedToggle";
|
||||
ariaselected = false;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -57,9 +60,10 @@ export class TabComponent extends React.Component<TabComponentProps> {
|
||||
<AccessibleElement
|
||||
as="span"
|
||||
className={className}
|
||||
role="presentation"
|
||||
role="tab"
|
||||
onActivated={() => this.setActiveTab(index)}
|
||||
aria-label={`Select tab: ${tab.title}`}
|
||||
aria-selected={ariaselected}
|
||||
>
|
||||
{tab.title}
|
||||
</AccessibleElement>
|
||||
@@ -77,7 +81,11 @@ export class TabComponent extends React.Component<TabComponentProps> {
|
||||
|
||||
return (
|
||||
<div className="tabComponentContainer">
|
||||
{!this.props.hideHeader && <div className="tabs tabSwitch">{this.renderTabTitles()}</div>}
|
||||
{!this.props.hideHeader && (
|
||||
<div className="tabs tabSwitch" role="tablist">
|
||||
{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 mode"]').simulate("change");
|
||||
wrapper.find('[aria-label="Manual database throughput"]').simulate("change");
|
||||
expect(wrapper.find('[aria-label="Throughput header"]').at(0).text()).toBe(
|
||||
"Container throughput (400 - unlimited RU/s)"
|
||||
);
|
||||
|
||||
wrapper.find('[aria-label="Autoscale mode"]').simulate("change");
|
||||
wrapper.find('[aria-label="Autoscale database throughput"]').simulate("change");
|
||||
expect(wrapper.find('[aria-label="Throughput header"]').at(0).text()).toBe("Container throughput (autoscale)");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -185,36 +185,48 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
</Stack>
|
||||
|
||||
<Stack horizontal verticalAlign="center">
|
||||
<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>
|
||||
<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="Manual mode"
|
||||
checked={!isAutoscaleSelected}
|
||||
type="radio"
|
||||
aria-required={true}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
onChange={(e) => handleOnChangeMode(e, "Manual")}
|
||||
/>
|
||||
<span className="throughputInputRadioBtnLabel">Manual</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>
|
||||
</Stack>
|
||||
|
||||
{isAutoscaleSelected && (
|
||||
<Stack className="throughputInputSpacing">
|
||||
<Text variant="small" aria-label="ruDescription">
|
||||
<Text variant="small" aria-label="capacity calculator of azure cosmos db">
|
||||
Estimate your required RU/s with{" "}
|
||||
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/" aria-label="ruDescription">
|
||||
<Link
|
||||
target="_blank"
|
||||
href="https://cosmos.azure.com/capacitycalculator/"
|
||||
aria-label="capacity calculator of azure cosmos db"
|
||||
>
|
||||
capacity calculator
|
||||
</Link>
|
||||
.
|
||||
@@ -238,7 +250,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
step={AutoPilotUtils.autoPilotIncrementStep}
|
||||
min={AutoPilotUtils.autoPilotThroughput1K}
|
||||
value={throughput.toString()}
|
||||
aria-label="Max request units per second"
|
||||
ariaLabel={`${isDatabase ? "Database" : getCollectionName()} max RU/s`}
|
||||
required={true}
|
||||
errorMessage={throughputError}
|
||||
/>
|
||||
|
||||
@@ -344,13 +344,13 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<StyledIconBase
|
||||
ariaLabel="Info"
|
||||
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."
|
||||
className="panelInfoIcon"
|
||||
iconName="Info"
|
||||
tabIndex={0}
|
||||
>
|
||||
<IconBase
|
||||
ariaLabel="Info"
|
||||
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."
|
||||
className="panelInfoIcon"
|
||||
iconName="Info"
|
||||
styles={[Function]}
|
||||
@@ -630,7 +630,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
}
|
||||
>
|
||||
<i
|
||||
aria-label="Info"
|
||||
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."
|
||||
className="panelInfoIcon root-57"
|
||||
data-icon-name="Info"
|
||||
role="img"
|
||||
@@ -654,40 +654,45 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
<div
|
||||
className="ms-Stack css-58"
|
||||
>
|
||||
<input
|
||||
aria-label="Autoscale mode"
|
||||
aria-required={true}
|
||||
checked={true}
|
||||
className="throughputInputRadioBtn"
|
||||
<div
|
||||
key=".0:$.0"
|
||||
onChange={[Function]}
|
||||
role="radio"
|
||||
tabIndex={0}
|
||||
type="radio"
|
||||
/>
|
||||
<span
|
||||
className="throughputInputRadioBtnLabel"
|
||||
key=".0:$.1"
|
||||
role="radiogroup"
|
||||
>
|
||||
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>
|
||||
<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>
|
||||
</div>
|
||||
</Stack>
|
||||
<Stack
|
||||
@@ -697,23 +702,23 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
className="ms-Stack throughputInputSpacing css-59"
|
||||
>
|
||||
<Text
|
||||
aria-label="ruDescription"
|
||||
aria-label="capacity calculator of azure cosmos db"
|
||||
key=".0:$.0"
|
||||
variant="small"
|
||||
>
|
||||
<span
|
||||
aria-label="ruDescription"
|
||||
aria-label="capacity calculator of azure cosmos db"
|
||||
className="css-54"
|
||||
>
|
||||
Estimate your required RU/s with
|
||||
|
||||
<StyledLinkBase
|
||||
aria-label="ruDescription"
|
||||
aria-label="capacity calculator of azure cosmos db"
|
||||
href="https://cosmos.azure.com/capacitycalculator/"
|
||||
target="_blank"
|
||||
>
|
||||
<LinkBase
|
||||
aria-label="ruDescription"
|
||||
aria-label="capacity calculator of azure cosmos db"
|
||||
href="https://cosmos.azure.com/capacitycalculator/"
|
||||
styles={[Function]}
|
||||
target="_blank"
|
||||
@@ -992,7 +997,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
}
|
||||
>
|
||||
<a
|
||||
aria-label="ruDescription"
|
||||
aria-label="capacity calculator of azure cosmos db"
|
||||
className="ms-Link root-60"
|
||||
href="https://cosmos.azure.com/capacitycalculator/"
|
||||
onClick={[Function]}
|
||||
@@ -1331,13 +1336,13 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
onMouseLeave={[Function]}
|
||||
>
|
||||
<StyledIconBase
|
||||
ariaLabel="Info"
|
||||
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."
|
||||
className="panelInfoIcon"
|
||||
iconName="Info"
|
||||
tabIndex={0}
|
||||
>
|
||||
<IconBase
|
||||
ariaLabel="Info"
|
||||
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."
|
||||
className="panelInfoIcon"
|
||||
iconName="Info"
|
||||
styles={[Function]}
|
||||
@@ -1617,7 +1622,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
}
|
||||
>
|
||||
<i
|
||||
aria-label="Info"
|
||||
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."
|
||||
className="panelInfoIcon root-57"
|
||||
data-icon-name="Info"
|
||||
role="img"
|
||||
@@ -1635,7 +1640,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
</div>
|
||||
</Stack>
|
||||
<StyledTextFieldBase
|
||||
aria-label="Max request units per second"
|
||||
ariaLabel="Container max RU/s"
|
||||
errorMessage=""
|
||||
id="autoscaleRUValueField"
|
||||
key=".0:$.2"
|
||||
@@ -1658,7 +1663,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
value="4000"
|
||||
>
|
||||
<TextFieldBase
|
||||
aria-label="Max request units per second"
|
||||
ariaLabel="Container max RU/s"
|
||||
deferredValidationTime={200}
|
||||
errorMessage=""
|
||||
id="autoscaleRUValueField"
|
||||
@@ -1956,6 +1961,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
>
|
||||
<input
|
||||
aria-invalid={false}
|
||||
aria-label="Container max RU/s"
|
||||
className="ms-TextField-field field-64"
|
||||
id="autoscaleRUValueField"
|
||||
min={1000}
|
||||
|
||||
@@ -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}>
|
||||
<div className="nodeChildren" data-test={node.label} role="group">
|
||||
{TreeNodeComponent.getSortedChildren(node).map((childNode: TreeNode) => (
|
||||
<TreeNodeComponent
|
||||
key={`${childNode.label}-${generation + 1}-${childNode.timestamp}`}
|
||||
|
||||
@@ -100,6 +100,7 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
|
||||
<div
|
||||
className="nodeChildren"
|
||||
data-test="label"
|
||||
role="group"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
generation={3}
|
||||
@@ -251,6 +252,7 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
|
||||
<div
|
||||
className="nodeChildren"
|
||||
data-test="label"
|
||||
role="group"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
generation={13}
|
||||
@@ -352,6 +354,7 @@ exports[`TreeNodeComponent renders loading icon 1`] = `
|
||||
<div
|
||||
className="nodeChildren"
|
||||
data-test="label"
|
||||
role="group"
|
||||
/>
|
||||
</AnimateHeight>
|
||||
</div>
|
||||
@@ -465,6 +468,7 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
|
||||
<div
|
||||
className="nodeChildren"
|
||||
data-test="label"
|
||||
role="group"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
generation={13}
|
||||
@@ -594,6 +598,7 @@ 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: 1px dashed @AccentMedium;
|
||||
outline: 2px @AccentMedium;
|
||||
}
|
||||
|
||||
.treeNodeHeader {
|
||||
|
||||
@@ -133,7 +133,6 @@ describe("ContainerSampleGenerator", () => {
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
|
||||
// Rejects with error that contains experience
|
||||
expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
|
||||
});
|
||||
|
||||
|
||||
@@ -93,7 +93,8 @@ export default class Explorer {
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
this._isInitializingNotebooks = false;
|
||||
this.phoenixClient = new PhoenixClient();
|
||||
|
||||
this.phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id);
|
||||
useNotebook.subscribe(
|
||||
() => this.refreshCommandBarButtons(),
|
||||
(state) => state.isNotebooksEnabledForAccount
|
||||
@@ -185,9 +186,7 @@ export default class Explorer {
|
||||
useNotebook.getState().setNotebookBasePath(userContext.features.notebookBasePath);
|
||||
}
|
||||
|
||||
if (userContext.apiType !== "Postgres") {
|
||||
this.refreshExplorer();
|
||||
}
|
||||
this.refreshExplorer();
|
||||
}
|
||||
|
||||
public async initiateAndRefreshNotebookList(): Promise<void> {
|
||||
@@ -353,7 +352,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 = {
|
||||
@@ -578,7 +577,7 @@ export default class Explorer {
|
||||
try {
|
||||
await Promise.all(
|
||||
databasesToLoad.map(async (database: ViewModels.Database) => {
|
||||
await database.loadCollections();
|
||||
await database.loadCollections(true);
|
||||
const isNewDatabase: boolean = _.some(newDatabases, (db: ViewModels.Database) => db.id() === database.id());
|
||||
if (isNewDatabase) {
|
||||
database.expandDatabase();
|
||||
@@ -1058,6 +1057,10 @@ 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");
|
||||
}
|
||||
@@ -1244,9 +1247,11 @@ export default class Explorer {
|
||||
}
|
||||
|
||||
public async refreshExplorer(): Promise<void> {
|
||||
userContext.authType === AuthType.ResourceToken
|
||||
? this.refreshDatabaseForResourceToken()
|
||||
: this.refreshAllDatabases();
|
||||
if (userContext.apiType !== "Postgres") {
|
||||
userContext.authType === AuthType.ResourceToken
|
||||
? this.refreshDatabaseForResourceToken()
|
||||
: this.refreshAllDatabases();
|
||||
}
|
||||
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
|
||||
|
||||
// TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount
|
||||
|
||||
@@ -729,6 +729,7 @@ export class D3ForceGraph implements GraphRenderer {
|
||||
var iconGroup = newNodes
|
||||
.append("g")
|
||||
.attr("class", "iconContainer")
|
||||
.attr("role", "group")
|
||||
.attr("tabindex", 0)
|
||||
.attr("aria-label", (d: D3Node) => {
|
||||
return this.retrieveNodeCaption(d);
|
||||
|
||||
@@ -6,9 +6,9 @@ import * as React from "react";
|
||||
import LoadGraphIcon from "../../../../images/LoadGraph.png";
|
||||
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
|
||||
import { queryDocuments } from "../../../Common/dataAccess/queryDocuments";
|
||||
import { queryDocumentsPage } from "../../../Common/dataAccess/queryDocumentsPage";
|
||||
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { InputProperty } from "../../../Contracts/ViewModels";
|
||||
@@ -182,7 +182,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
||||
public static readonly WITHOUT_STEP_ARGS_MAX_CHARS = 10000; // maximums char size of the without() step parameter
|
||||
public static readonly ROOT_LIST_PAGE_SIZE = 100;
|
||||
private static readonly MAX_LATEST_QUERIES = 10;
|
||||
private static readonly MAX_RESULT_SIZE = 10000;
|
||||
// TODO This prevents loading too much data in GraphExplorer and d3. A better is to cap the size of the result, rather than the number of items in the array.
|
||||
private static readonly MAX_RESULT_SIZE = 100_000;
|
||||
|
||||
private static readonly TAB_INDEX_JSON = 0;
|
||||
private static readonly TAB_INDEX_GRAPH = 1;
|
||||
|
||||
@@ -40,6 +40,7 @@ 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"
|
||||
@@ -135,6 +136,7 @@ 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"
|
||||
|
||||
@@ -31,7 +31,7 @@ export class GremlinClient {
|
||||
public client: GremlinSimpleClient;
|
||||
public pendingResults: Map<string, PendingResultData>; // public for testing purposes
|
||||
private maxResultSize: number;
|
||||
private static readonly PENDING_REQUEST_TIMEOUT_MS = 6 /* minutes */ * 60 /* seconds */ * 1000 /* ms */;
|
||||
private static readonly PENDING_REQUEST_TIMEOUT_MS = 1 /* hour */ * 3_600 /* seconds */ * 1_000 /* ms */;
|
||||
private static readonly TIMEOUT_ERROR_MSG = `Pending request timed out (${GremlinClient.PENDING_REQUEST_TIMEOUT_MS} ms)`;
|
||||
private static readonly LOG_AREA = "GremlinClient";
|
||||
|
||||
|
||||
@@ -17,19 +17,21 @@ export class MiddlePaneComponent extends React.Component<MiddlePaneComponentProp
|
||||
<div className="middlePane">
|
||||
<div className="graphTitle">
|
||||
<span className="paneTitle">Graph</span>
|
||||
<span
|
||||
<button
|
||||
style={{ border: "none", background: "none" }}
|
||||
className="graphExpandCollapseBtn pull-right"
|
||||
onClick={this.props.toggleExpandGraph}
|
||||
role="button"
|
||||
aria-expanded={this.props.isTabsContentExpanded}
|
||||
aria-name="View graph in full screen"
|
||||
tabIndex={0}
|
||||
aria-label={
|
||||
this.props.isTabsContentExpanded ? "Collapse graph to minimized view" : "View graph in full screen"
|
||||
}
|
||||
>
|
||||
<img
|
||||
src={this.props.isTabsContentExpanded ? CollapseArrowIcon : ExpandIcon}
|
||||
alt={this.props.isTabsContentExpanded ? "collapse graph content" : "expand graph content"}
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="maingraphContainer">
|
||||
<GraphVizComponent forceGraphParams={this.props.graphVizProps.forceGraphParams} />
|
||||
|
||||
@@ -107,11 +107,11 @@ describe("New Vertex Component", () => {
|
||||
it("should call onTypeChange method on type dropdown change", () => {
|
||||
const DOWN_ARROW = { keyCode: 40 };
|
||||
const onTypeChange = jest.fn();
|
||||
const dropdown = screen.getByRole("listbox");
|
||||
const dropdown = screen.getByRole("combobox");
|
||||
dropdown.onclick = onTypeChange();
|
||||
dropdown.onkeydown = onTypeChange();
|
||||
|
||||
fireEvent.keyDown(screen.getByRole("listbox"), DOWN_ARROW);
|
||||
fireEvent.keyDown(screen.getByRole("combobox"), DOWN_ARROW);
|
||||
fireEvent.click(screen.getByText(/number/));
|
||||
expect(onTypeChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -145,6 +145,7 @@ export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = (
|
||||
type="text"
|
||||
id="propertyKeyNewVertexPane"
|
||||
componentRef={input}
|
||||
aria-required="true"
|
||||
placeholder="Key"
|
||||
autoComplete="off"
|
||||
aria-label={`Enter value for propery ${index + 1}`}
|
||||
@@ -152,6 +153,8 @@ export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = (
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onKeyChange(event, index)}
|
||||
/>
|
||||
</div>
|
||||
<span className="mandatoryStar">* </span>
|
||||
|
||||
<div className="valueCol">
|
||||
<TextField
|
||||
className="edgeInput"
|
||||
@@ -165,7 +168,7 @@ export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = (
|
||||
</div>
|
||||
<div>
|
||||
<Dropdown
|
||||
role="listbox"
|
||||
role="combobox"
|
||||
placeholder="Select an option"
|
||||
defaultSelectedKey={data.values[0].type}
|
||||
style={{ width: 100 }}
|
||||
|
||||
@@ -25,7 +25,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
updateUserContext({
|
||||
databaseAccount: {
|
||||
properties: {
|
||||
capabilities: [{ name: "EnableTable" }],
|
||||
capabilities: [{ name: "EnableMongo" }],
|
||||
},
|
||||
} as DatabaseAccount,
|
||||
});
|
||||
@@ -38,6 +38,38 @@ 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,11 +51,13 @@ export function createStaticCommandBarButtons(
|
||||
const buttons: CommandButtonComponentProps[] = [];
|
||||
|
||||
buttons.push(newCollectionBtn);
|
||||
if (userContext.apiType !== "Tables" && userContext.apiType !== "Cassandra") {
|
||||
const addSynapseLink = createOpenSynapseLinkDialogButton(container);
|
||||
|
||||
const addSynapseLink = createOpenSynapseLinkDialogButton(container);
|
||||
if (addSynapseLink) {
|
||||
buttons.push(createDivider());
|
||||
buttons.push(addSynapseLink);
|
||||
if (addSynapseLink) {
|
||||
buttons.push(createDivider());
|
||||
buttons.push(addSynapseLink);
|
||||
}
|
||||
}
|
||||
|
||||
if (userContext.apiType !== "Tables") {
|
||||
@@ -202,6 +204,7 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
|
||||
if (showOpenFullScreen) {
|
||||
const label = "Open Full Screen";
|
||||
const fullScreenButton: CommandButtonComponentProps = {
|
||||
id: "openFullScreenBtn",
|
||||
iconSrc: OpenInTabIcon,
|
||||
iconAlt: label,
|
||||
onCommandClick: () => {
|
||||
@@ -523,6 +526,28 @@ 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 {
|
||||
@@ -587,16 +612,7 @@ function createStaticCommandBarButtonsForResourceToken(
|
||||
}
|
||||
|
||||
export function createPostgreButtons(container: Explorer): CommandButtonComponentProps[] {
|
||||
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,
|
||||
};
|
||||
const openPostgreShellBtn = createOpenPsqlTerminalButton(container);
|
||||
|
||||
return [openPostgreShellBtn];
|
||||
}
|
||||
|
||||
@@ -147,12 +147,9 @@ 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,8 +112,6 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
||||
className="notificationConsoleControls"
|
||||
>
|
||||
<Dropdown
|
||||
aria-label="All"
|
||||
aria-labelledby="consoleFilterLabel"
|
||||
label="Filter:"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
@@ -136,7 +134,6 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
||||
},
|
||||
]
|
||||
}
|
||||
role="combobox"
|
||||
selectedKey="All"
|
||||
/>
|
||||
<span
|
||||
@@ -278,8 +275,6 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
||||
className="notificationConsoleControls"
|
||||
>
|
||||
<Dropdown
|
||||
aria-label="All"
|
||||
aria-labelledby="consoleFilterLabel"
|
||||
label="Filter:"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
@@ -302,7 +297,6 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
||||
},
|
||||
]
|
||||
}
|
||||
role="combobox"
|
||||
selectedKey="All"
|
||||
/>
|
||||
<span
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
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();
|
||||
this.phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id);
|
||||
this.retryOptions = {
|
||||
retries: Notebook.retryAttempts,
|
||||
maxTimeout: Notebook.retryAttemptDelayMs,
|
||||
|
||||
@@ -124,8 +124,9 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
}
|
||||
|
||||
const firstWriteLocation =
|
||||
databaseAccount?.properties?.writeLocations &&
|
||||
databaseAccount?.properties?.writeLocations[0]?.locationName.toLowerCase();
|
||||
userContext.apiType === "Postgres"
|
||||
? databaseAccount?.location
|
||||
: databaseAccount?.properties?.writeLocations?.[0]?.locationName.toLowerCase();
|
||||
const disallowedLocationsUri = `${configContext.BACKEND_ENDPOINT}/api/disallowedLocations`;
|
||||
const authorizationHeader = getAuthorizationHeader();
|
||||
try {
|
||||
@@ -307,13 +308,16 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
||||
let isPhoenixFeatures = false;
|
||||
|
||||
const isPublicInternetAllowed = isPublicInternetAccessAllowed();
|
||||
const phoenixClient = new PhoenixClient();
|
||||
const phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id);
|
||||
const dbAccountAllowedInfo = await phoenixClient.getDbAccountAllowedStatus();
|
||||
|
||||
if (dbAccountAllowedInfo.status === HttpStatusCodes.OK) {
|
||||
if (dbAccountAllowedInfo?.type === PhoenixErrorType.PhoenixFlightFallback) {
|
||||
isPhoenixNotebooks = isPublicInternetAllowed && userContext.features.phoenixNotebooks === true;
|
||||
isPhoenixFeatures = isPublicInternetAllowed && userContext.features.phoenixFeatures === 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");
|
||||
} else {
|
||||
isPhoenixNotebooks = isPhoenixFeatures = isPublicInternetAllowed;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
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.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");
|
||||
expect(screen.getByText("Open")).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -1,66 +1,26 @@
|
||||
import { DefaultButton, PrimaryButton, Spinner, Stack, Text, TextField } from "@fluentui/react";
|
||||
import copyToClipboard from "clipboard-copy";
|
||||
import { PrimaryButton, Stack, Text } from "@fluentui/react";
|
||||
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 (
|
||||
<>
|
||||
<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" }}
|
||||
/>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -57,7 +57,7 @@ const SharedDatabaseDefault: DataModels.IndexingPolicy = {
|
||||
],
|
||||
};
|
||||
|
||||
const AllPropertiesIndexed: DataModels.IndexingPolicy = {
|
||||
export const AllPropertiesIndexed: DataModels.IndexingPolicy = {
|
||||
indexingMode: "consistent",
|
||||
automatic: true,
|
||||
includedPaths: [
|
||||
@@ -89,9 +89,10 @@ export interface AddCollectionPanelState {
|
||||
enableIndexing: boolean;
|
||||
isSharded: boolean;
|
||||
partitionKey: string;
|
||||
subPartitionKeys: string[];
|
||||
enableDedicatedThroughput: boolean;
|
||||
createMongoWildCardIndex: boolean;
|
||||
useHashV2: boolean;
|
||||
useHashV1: boolean;
|
||||
enableAnalyticalStore: boolean;
|
||||
uniqueKeys: string[];
|
||||
errorMessage: string;
|
||||
@@ -121,9 +122,11 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
enableIndexing: true,
|
||||
isSharded: userContext.apiType !== "Tables",
|
||||
partitionKey: this.getPartitionKey(),
|
||||
subPartitionKeys: [],
|
||||
enableDedicatedThroughput: false,
|
||||
createMongoWildCardIndex: isCapabilityEnabled("EnableMongo"),
|
||||
useHashV2: false,
|
||||
createMongoWildCardIndex:
|
||||
isCapabilityEnabled("EnableMongo") && !isCapabilityEnabled("EnableMongo16MBDocumentSupport"),
|
||||
useHashV1: false,
|
||||
enableAnalyticalStore: false,
|
||||
uniqueKeys: [],
|
||||
errorMessage: "",
|
||||
@@ -259,37 +262,46 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
true
|
||||
).toLocaleLowerCase()}.`}
|
||||
>
|
||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||
<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()}.`}
|
||||
/>
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
|
||||
<Stack horizontal verticalAlign="center">
|
||||
<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>
|
||||
<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="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>
|
||||
<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>
|
||||
</Stack>
|
||||
|
||||
{this.state.createNewDatabase && (
|
||||
@@ -335,7 +347,14 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
true
|
||||
).toLocaleLowerCase()} within the database.`}
|
||||
>
|
||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||
<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.`}
|
||||
/>
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
)}
|
||||
@@ -383,7 +402,13 @@ 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 iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||
<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.`}
|
||||
/>
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
|
||||
@@ -466,7 +491,14 @@ 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} />
|
||||
<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."
|
||||
}
|
||||
/>
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
|
||||
@@ -513,7 +545,12 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||
content={this.getPartitionKeyTooltipText()}
|
||||
>
|
||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||
<Icon
|
||||
iconName="Info"
|
||||
className="panelInfoIcon"
|
||||
tabIndex={0}
|
||||
ariaLabel={this.getPartitionKeyTooltipText()}
|
||||
/>
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
|
||||
@@ -545,6 +582,77 @@ 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: 200, 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
|
||||
</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 .NET
|
||||
V3, Java V4 SDK, or preview JavaScript V3 SDK.{" "}
|
||||
<Link href="https://aka.ms/cosmos-hierarchical-partitioning" target="_blank">
|
||||
Learn more
|
||||
</Link>
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
@@ -571,7 +679,17 @@ 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} />
|
||||
<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.`}
|
||||
/>
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
)}
|
||||
@@ -602,11 +720,18 @@ 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} />
|
||||
<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."
|
||||
}
|
||||
/>
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
|
||||
@@ -669,40 +794,48 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||
content={this.getAnalyticalStorageTooltipContent()}
|
||||
>
|
||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||
<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."
|
||||
/>
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
|
||||
<Stack horizontal verticalAlign="center">
|
||||
<input
|
||||
className="panelRadioBtn"
|
||||
checked={this.state.enableAnalyticalStore}
|
||||
disabled={!this.isSynapseLinkEnabled()}
|
||||
aria-label="Enable analytical store"
|
||||
aria-checked={this.state.enableAnalyticalStore}
|
||||
name="analyticalStore"
|
||||
type="radio"
|
||||
role="radio"
|
||||
id="enableAnalyticalStoreBtn"
|
||||
tabIndex={0}
|
||||
onChange={this.onEnableAnalyticalStoreRadioBtnChange.bind(this)}
|
||||
/>
|
||||
<span className="panelRadioBtnLabel">On</span>
|
||||
<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="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>
|
||||
<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>
|
||||
</Stack>
|
||||
|
||||
{!this.isSynapseLinkEnabled() && (
|
||||
@@ -735,7 +868,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
}}
|
||||
>
|
||||
<Stack className="panelGroupSpacing" id="collapsibleSectionContent">
|
||||
{isCapabilityEnabled("EnableMongo") && (
|
||||
{isCapabilityEnabled("EnableMongo") && !isCapabilityEnabled("EnableMongo16MBDocumentSupport") && (
|
||||
<Stack className="panelGroupSpacing">
|
||||
<Stack horizontal>
|
||||
<span className="mandatoryStar">* </span>
|
||||
@@ -746,7 +879,12 @@ 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} />
|
||||
<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."
|
||||
/>
|
||||
</TooltipHost>
|
||||
</Stack>
|
||||
|
||||
@@ -766,18 +904,29 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
)}
|
||||
|
||||
{userContext.apiType === "SQL" && (
|
||||
<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 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>
|
||||
)}
|
||||
</Stack>
|
||||
</CollapsibleSectionComponent>
|
||||
@@ -832,12 +981,20 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
return isLowerCase ? partitionKeyName.toLocaleLowerCase() : partitionKeyName;
|
||||
}
|
||||
|
||||
private getPartitionKeyPlaceHolder(): string {
|
||||
private getPartitionKeyPlaceHolder(index?: number): string {
|
||||
switch (userContext.apiType) {
|
||||
case "Mongo":
|
||||
return "e.g., address.zipCode";
|
||||
return "e.g., categoryId";
|
||||
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";
|
||||
}
|
||||
@@ -959,7 +1116,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
return userContext.apiType === "SQL" ? "/pk" : "pk";
|
||||
}
|
||||
if (this.props.isQuickstart) {
|
||||
return userContext.apiType === "SQL" ? "/address" : "address";
|
||||
return userContext.apiType === "SQL" ? "/categoryId" : "categoryId";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -1163,11 +1320,16 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
}
|
||||
|
||||
const uniqueKeyPolicy: DataModels.UniqueKeyPolicy = this.parseUniqueKeys();
|
||||
const partitionKeyVersion = this.state.useHashV2 ? 2 : undefined;
|
||||
const partitionKeyVersion = this.state.useHashV1 ? undefined : 2;
|
||||
const partitionKey: DataModels.PartitionKey = partitionKeyString
|
||||
? {
|
||||
paths: [partitionKeyString],
|
||||
kind: "Hash",
|
||||
paths: [
|
||||
partitionKeyString,
|
||||
...(userContext.apiType === "SQL" && this.state.subPartitionKeys.length > 0
|
||||
? this.state.subPartitionKeys
|
||||
: []),
|
||||
],
|
||||
kind: userContext.apiType === "SQL" && this.state.subPartitionKeys.length > 0 ? "MultiHash" : "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,6 +24,7 @@ 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}>
|
||||
<a className="paneErrorLink" role="button" onClick={expandConsole} tabIndex={0} onKeyPress={expandConsole}>
|
||||
More details
|
||||
</a>
|
||||
)}
|
||||
|
||||
@@ -4,11 +4,11 @@ import React, { FunctionComponent, useState } from "react";
|
||||
import { Areas, SavedQueries } from "../../../Common/Constants";
|
||||
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||
import { Query } from "../../../Contracts/DataModels";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { useTabs } from "../../../hooks/useTabs";
|
||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { useTabs } from "../../../hooks/useTabs";
|
||||
import Explorer from "../../Explorer";
|
||||
import { NewQueryTab } from "../../Tabs/QueryTab/QueryTab";
|
||||
import { useDatabases } from "../../useDatabases";
|
||||
@@ -16,9 +16,13 @@ import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneFor
|
||||
|
||||
interface SaveQueryPaneProps {
|
||||
explorer: Explorer;
|
||||
queryToSave?: string;
|
||||
}
|
||||
|
||||
export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({ explorer }: SaveQueryPaneProps): JSX.Element => {
|
||||
export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({
|
||||
explorer,
|
||||
queryToSave,
|
||||
}: SaveQueryPaneProps): JSX.Element => {
|
||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
||||
const [formError, setFormError] = useState<string>("");
|
||||
@@ -36,7 +40,7 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({ explorer
|
||||
}
|
||||
|
||||
const queryTab = useTabs.getState().activeTab as NewQueryTab;
|
||||
const query: string = queryTab && queryTab.iTabAccessor.onSaveClickEvent();
|
||||
const query: string = queryToSave || queryTab?.iTabAccessor.onSaveClickEvent();
|
||||
|
||||
if (!queryName || queryName.length === 0) {
|
||||
setFormError("No query name specified");
|
||||
@@ -62,8 +66,8 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({ explorer
|
||||
try {
|
||||
await explorer.queriesClient.saveQuery(queryParam);
|
||||
setLoadingFalse();
|
||||
queryTab.tabTitle(queryParam.queryName);
|
||||
queryTab.tabPath(`${queryTab.collection.databaseId}>${queryTab.collection.id()}>${queryParam.queryName}`);
|
||||
queryTab?.tabTitle(queryParam.queryName);
|
||||
queryTab?.tabPath(`${queryTab.collection.databaseId}>${queryTab.collection.id()}>${queryParam.queryName}`);
|
||||
traceSuccess(
|
||||
Action.SaveQuery,
|
||||
{
|
||||
|
||||
@@ -21,6 +21,11 @@ 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"
|
||||
@@ -50,6 +55,7 @@ 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);
|
||||
|
||||
@@ -185,6 +191,25 @@ 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,6 +97,35 @@ 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"
|
||||
>
|
||||
@@ -182,6 +211,35 @@ 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,6 +14,7 @@ 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,6 +242,11 @@ 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}>
|
||||
@@ -284,7 +289,13 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
||||
);
|
||||
})}
|
||||
{userContext.apiType !== "Cassandra" && (
|
||||
<Stack horizontal onClick={addNewEntity} className="addButtonEntiy">
|
||||
<Stack
|
||||
horizontal
|
||||
onClick={addNewEntity}
|
||||
className="addButtonEntiy"
|
||||
tabIndex={0}
|
||||
onKeyPress={handlekeypressaddentity}
|
||||
>
|
||||
<Image {...imageProps} src={AddPropertyIcon} alt="Add Entity" />
|
||||
<Text className="addNewParamStyle">{getAddButtonLabel(userContext.apiType)}</Text>
|
||||
</Stack>
|
||||
|
||||
@@ -29,10 +29,14 @@ 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,6 +31,7 @@ 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}
|
||||
@@ -41,39 +42,43 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
horizontal={true}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<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"
|
||||
<div
|
||||
role="radiogroup"
|
||||
>
|
||||
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>
|
||||
<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>
|
||||
</Stack>
|
||||
<Stack
|
||||
className="panelGroupSpacing"
|
||||
@@ -124,6 +129,7 @@ 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}
|
||||
@@ -163,8 +169,10 @@ 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>
|
||||
@@ -206,6 +214,7 @@ 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}
|
||||
@@ -223,13 +232,36 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
id="addCollection-partitionKeyValue"
|
||||
onChange={[Function]}
|
||||
pattern=".*"
|
||||
placeholder="e.g., /address/zipCode"
|
||||
placeholder="Required - first partition key e.g., /TenantId"
|
||||
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": 200,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
Add hierarchical partition key
|
||||
</CustomizedDefaultButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Stack
|
||||
@@ -246,6 +278,7 @@ 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}
|
||||
@@ -303,6 +336,7 @@ 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}
|
||||
@@ -313,42 +347,46 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
horizontal={true}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<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"
|
||||
<div
|
||||
role="radiogroup"
|
||||
>
|
||||
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>
|
||||
<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>
|
||||
</Stack>
|
||||
<Stack
|
||||
className="panelGroupSpacing"
|
||||
@@ -396,26 +434,49 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
||||
className="panelGroupSpacing"
|
||||
id="collapsibleSectionContent"
|
||||
>
|
||||
<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,
|
||||
},
|
||||
<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,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
/>
|
||||
<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>
|
||||
|
||||
11
src/Explorer/QueryCopilot/CopilotCarousel.test.tsx
Normal file
11
src/Explorer/QueryCopilot/CopilotCarousel.test.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import Explorer from "../Explorer";
|
||||
import { QueryCopilotCarousel } from "./CopilotCarousel";
|
||||
|
||||
describe("Query Copilot Carousel snapshot test", () => {
|
||||
it("should render when isOpen is true", () => {
|
||||
const wrapper = shallow(<QueryCopilotCarousel isOpen={true} explorer={new Explorer()} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
275
src/Explorer/QueryCopilot/CopilotCarousel.tsx
Normal file
275
src/Explorer/QueryCopilot/CopilotCarousel.tsx
Normal file
@@ -0,0 +1,275 @@
|
||||
import {
|
||||
DefaultButton,
|
||||
ISeparatorStyles,
|
||||
IconButton,
|
||||
Image,
|
||||
Link,
|
||||
Modal,
|
||||
PrimaryButton,
|
||||
Separator,
|
||||
Spinner,
|
||||
Stack,
|
||||
Text,
|
||||
} from "@fluentui/react";
|
||||
import { QueryCopilotSampleDatabaseId, StyleConstants } from "Common/Constants";
|
||||
import { handleError } from "Common/ErrorHandlingUtils";
|
||||
import { createCollection } from "Common/dataAccess/createCollection";
|
||||
import * as DataModels from "Contracts/DataModels";
|
||||
import { ContainerSampleGenerator } from "Explorer/DataSamples/ContainerSampleGenerator";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { AllPropertiesIndexed } from "Explorer/Panes/AddCollectionPanel";
|
||||
import { PromptCard } from "Explorer/QueryCopilot/PromptCard";
|
||||
import { useDatabases } from "Explorer/useDatabases";
|
||||
import { useCarousel } from "hooks/useCarousel";
|
||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||
import React, { useState } from "react";
|
||||
import YoutubePlaceholder from "../../../images/YoutubePlaceholder.svg";
|
||||
|
||||
interface QueryCopilotCarouselProps {
|
||||
isOpen: boolean;
|
||||
explorer: Explorer;
|
||||
}
|
||||
|
||||
const separatorStyles: Partial<ISeparatorStyles> = {
|
||||
root: {
|
||||
selectors: {
|
||||
"::before": {
|
||||
background: StyleConstants.BaseMedium,
|
||||
},
|
||||
},
|
||||
padding: "16px 0",
|
||||
height: 1,
|
||||
},
|
||||
};
|
||||
|
||||
export const QueryCopilotCarousel: React.FC<QueryCopilotCarouselProps> = ({
|
||||
isOpen,
|
||||
explorer,
|
||||
}: QueryCopilotCarouselProps): JSX.Element => {
|
||||
const [page, setPage] = useState<number>(1);
|
||||
const [isCreatingDatabase, setIsCreatingDatabase] = useState<boolean>(false);
|
||||
const [selectedPrompt, setSelectedPrompt] = useState<number>(1);
|
||||
|
||||
const getHeaderText = (): string => {
|
||||
switch (page) {
|
||||
case 1:
|
||||
return "What exactly is copilot?";
|
||||
case 2:
|
||||
return "Setting up your Sample database";
|
||||
case 3:
|
||||
return "Sample prompts to help you";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
const getQueryCopilotInitialInput = (): string => {
|
||||
switch (selectedPrompt) {
|
||||
case 1:
|
||||
return "Write a query to return all records in this table";
|
||||
case 2:
|
||||
return "Write a query to return all records in this table created in the last thirty days";
|
||||
case 3:
|
||||
return 'Write a query to return all records in this table created in the last thirty days which also have the record owner as "Contoso"';
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
const createSampleDatabase = async (): Promise<void> => {
|
||||
const database = useDatabases.getState().findDatabaseWithId(QueryCopilotSampleDatabaseId);
|
||||
if (database) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsCreatingDatabase(true);
|
||||
const params: DataModels.CreateCollectionParams = {
|
||||
createNewDatabase: true,
|
||||
collectionId: "SampleContainer",
|
||||
databaseId: QueryCopilotSampleDatabaseId,
|
||||
databaseLevelThroughput: true,
|
||||
autoPilotMaxThroughput: 1000,
|
||||
offerThroughput: undefined,
|
||||
indexingPolicy: AllPropertiesIndexed,
|
||||
partitionKey: {
|
||||
paths: ["/CategoryId"],
|
||||
kind: "Hash",
|
||||
version: 2,
|
||||
},
|
||||
};
|
||||
await createCollection(params);
|
||||
await explorer.refreshAllDatabases();
|
||||
const database = useDatabases.getState().findDatabaseWithId(QueryCopilotSampleDatabaseId);
|
||||
// populate sample container with sample data
|
||||
await database.loadCollections();
|
||||
const collection = database.findCollectionWithId("SampleContainer");
|
||||
collection.isSampleCollection = true;
|
||||
const sampleGenerator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorer);
|
||||
await sampleGenerator.populateContainerAsync(collection, "/CategoryId");
|
||||
// auto-expand sample database + container and show teaching bubble
|
||||
await database.expandDatabase();
|
||||
collection.expandCollection();
|
||||
useDatabases.getState().updateDatabase(database);
|
||||
} catch (error) {
|
||||
//TODO: show error in UI
|
||||
handleError(error, "Query copilot quickstart");
|
||||
throw error;
|
||||
} finally {
|
||||
setIsCreatingDatabase(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getContent = (): JSX.Element => {
|
||||
switch (page) {
|
||||
case 1:
|
||||
return (
|
||||
<Stack style={{ marginTop: 8 }}>
|
||||
<Text style={{ fontSize: 13 }}>
|
||||
A couple of lines about copilot and the background about it. The idea is to have some text to give context
|
||||
to the user.
|
||||
</Text>
|
||||
<Text style={{ fontSize: 14, fontWeight: 600, marginTop: 16 }}>How do you use copilot</Text>
|
||||
<Text style={{ fontSize: 13, marginTop: 8 }}>
|
||||
To generate queries , just describe the query you want and copilot will generate the query for you.Watch
|
||||
this video to learn more about how to use copilot.
|
||||
</Text>
|
||||
<Image src={YoutubePlaceholder} style={{ margin: "16px auto" }} />
|
||||
<Text style={{ fontSize: 14, fontWeight: 600 }}>What is copilot good at</Text>
|
||||
<Text style={{ fontSize: 13, marginTop: 8 }}>
|
||||
A couple of lines about what copilot can do and its capablites with a link to{" "}
|
||||
<Link href="" target="_blank">
|
||||
documentation
|
||||
</Link>{" "}
|
||||
if possible.
|
||||
</Text>
|
||||
<Text style={{ fontSize: 14, fontWeight: 600, marginTop: 16 }}>What are its limitations</Text>
|
||||
<Text style={{ fontSize: 13, marginTop: 8 }}>
|
||||
A couple of lines about what copilot cant do and its limitations.{" "}
|
||||
<Link href="" target="_blank">
|
||||
Link to documentation
|
||||
</Link>
|
||||
</Text>
|
||||
<Text style={{ fontSize: 14, fontWeight: 600, marginTop: 16 }}>Disclaimer</Text>
|
||||
<Text style={{ fontSize: 13, marginTop: 8 }}>
|
||||
AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.{" "}
|
||||
<Link href="" target="_blank">
|
||||
Read preview terms
|
||||
</Link>
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<Stack style={{ marginTop: 8 }}>
|
||||
<Text style={{ fontSize: 13 }}>
|
||||
Before you get started, we need to configure your sample database for you. Here is a summary of the
|
||||
database being created for your reference. Configuration values can be updated using the settings icon in
|
||||
the query builder.
|
||||
</Text>
|
||||
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 24 }}>Database Id</Text>
|
||||
<Text style={{ fontSize: 13 }}>CopilotSampleDb</Text>
|
||||
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 16 }}>Database throughput (autoscale)</Text>
|
||||
<Text style={{ fontSize: 13 }}>Autoscale</Text>
|
||||
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 16 }}>Database Max RU/s</Text>
|
||||
<Text>1000</Text>
|
||||
<Text style={{ fontSize: 10, marginTop: 8 }}>
|
||||
Your database throughput will automatically scale from{" "}
|
||||
<strong>100 RU/s (10% of max RU/s) - 1000 RU/s</strong> based on usage.
|
||||
</Text>
|
||||
<Text style={{ fontSize: 10, marginTop: 8 }}>
|
||||
Estimated monthly cost (USD): <strong>$8.76 - $87.60</strong> (1 region, 100 - 1000 RU/s, $0.00012/RU)
|
||||
</Text>
|
||||
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 16 }}>Container Id</Text>
|
||||
<Text style={{ fontSize: 13 }}>SampleContainer</Text>
|
||||
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 16 }}>Partition key</Text>
|
||||
<Text style={{ fontSize: 13 }}>CategoryId</Text>
|
||||
</Stack>
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<Stack>
|
||||
<Text>To help you get started, here are some sample prompts to get you started</Text>
|
||||
<Stack tokens={{ childrenGap: 12 }} style={{ marginTop: 16 }}>
|
||||
<PromptCard
|
||||
header="Write a query to return all records in this table"
|
||||
description="This is a basic query which returns all records in the table "
|
||||
onSelect={() => setSelectedPrompt(1)}
|
||||
isSelected={selectedPrompt === 1}
|
||||
/>
|
||||
<PromptCard
|
||||
header="Write a query to return all records in this table created in the last thirty days"
|
||||
description="This builds on the previous query which returns all records in the table which were inserted in the last thirty days. You can also modify this query to return records based upon creation date"
|
||||
onSelect={() => setSelectedPrompt(2)}
|
||||
isSelected={selectedPrompt === 2}
|
||||
/>
|
||||
<PromptCard
|
||||
header='Write a query to return all records in this table created in the last thirty days which also have the record owner as "Contoso"'
|
||||
description='This builds on the previous query which returns all records in the table which were inserted in the last thirty days but which has the record owner as "contoso"'
|
||||
onSelect={() => setSelectedPrompt(3)}
|
||||
isSelected={selectedPrompt === 3}
|
||||
/>
|
||||
</Stack>
|
||||
<Text style={{ fontSize: 13, marginTop: 32 }}>
|
||||
Interested in learning more about how to write effective prompts. Please read this article for more
|
||||
information.
|
||||
</Text>
|
||||
<Text style={{ fontSize: 13, marginTop: 16 }}>
|
||||
You can also access these prompts by selecting the Samples prompts button in the query builder page.
|
||||
</Text>
|
||||
<Text style={{ fontSize: 13, marginTop: 16 }}>
|
||||
Don't like any of the prompts? Just click Get Started and write your own prompt.
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal styles={{ main: { width: 880 } }} isOpen={isOpen && page < 4}>
|
||||
<Stack style={{ padding: 16 }}>
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text variant="xLarge">{getHeaderText()}</Text>
|
||||
<IconButton
|
||||
iconProps={{ iconName: "Cancel" }}
|
||||
onClick={() => useCarousel.getState().setShowCopilotCarousel(false)}
|
||||
/>
|
||||
</Stack>
|
||||
{getContent()}
|
||||
<Separator styles={separatorStyles} />
|
||||
<Stack horizontal horizontalAlign="start" verticalAlign="center">
|
||||
{page !== 1 && (
|
||||
<DefaultButton
|
||||
text="Previous"
|
||||
style={{ marginRight: 8 }}
|
||||
onClick={() => setPage(page - 1)}
|
||||
disabled={isCreatingDatabase}
|
||||
/>
|
||||
)}
|
||||
<PrimaryButton
|
||||
text={page === 3 ? "Get started" : "Next"}
|
||||
onClick={async () => {
|
||||
if (page === 3) {
|
||||
useCarousel.getState().setShowCopilotCarousel(false);
|
||||
useTabs.getState().setQueryCopilotTabInitialInput(getQueryCopilotInitialInput());
|
||||
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
|
||||
return;
|
||||
}
|
||||
|
||||
if (page === 2) {
|
||||
await createSampleDatabase();
|
||||
}
|
||||
|
||||
setPage(page + 1);
|
||||
}}
|
||||
disabled={isCreatingDatabase}
|
||||
/>
|
||||
{isCreatingDatabase && <Spinner style={{ marginLeft: 8 }} />}
|
||||
{isCreatingDatabase && <Text style={{ marginLeft: 8, color: "#0078D4" }}>Setting up your database...</Text>}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
19
src/Explorer/QueryCopilot/PromptCard.test.tsx
Normal file
19
src/Explorer/QueryCopilot/PromptCard.test.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { PromptCard } from "./PromptCard";
|
||||
|
||||
describe("Prompt card snapshot test", () => {
|
||||
it("should render properly if isSelected is true", () => {
|
||||
const wrapper = shallow(
|
||||
<PromptCard header="TestHeader" description="TestDescription" isSelected={true} onSelect={() => undefined} />
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render properly if isSelected is false", () => {
|
||||
const wrapper = shallow(
|
||||
<PromptCard header="TestHeader" description="TestDescription" isSelected={false} onSelect={() => undefined} />
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
48
src/Explorer/QueryCopilot/PromptCard.tsx
Normal file
48
src/Explorer/QueryCopilot/PromptCard.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { ChoiceGroup, Stack, Text } from "@fluentui/react";
|
||||
import React from "react";
|
||||
|
||||
interface PromptCardProps {
|
||||
header: string;
|
||||
description: string;
|
||||
isSelected: boolean;
|
||||
onSelect: () => void;
|
||||
}
|
||||
|
||||
export const PromptCard: React.FC<PromptCardProps> = ({
|
||||
header,
|
||||
description,
|
||||
isSelected,
|
||||
onSelect,
|
||||
}: PromptCardProps): JSX.Element => {
|
||||
return (
|
||||
<Stack
|
||||
horizontal
|
||||
style={{
|
||||
padding: "16px 0 16px 16px ",
|
||||
boxSizing: "border-box",
|
||||
width: 650,
|
||||
height: 100,
|
||||
border: "1px solid #F3F2F1",
|
||||
boxShadow: "0px 1.6px 3.6px rgba(0, 0, 0, 0.132), 0px 0.3px 0.9px rgba(0, 0, 0, 0.108)",
|
||||
}}
|
||||
>
|
||||
<Stack.Item grow={1}>
|
||||
<Stack horizontal>
|
||||
<div>
|
||||
<Text style={{ fontSize: 13, color: "#00A2AD", background: "#F8FFF0" }}>Prompt</Text>
|
||||
</div>
|
||||
<Text style={{ fontSize: 13, marginLeft: 16 }}>{header}</Text>
|
||||
</Stack>
|
||||
<Text style={{ fontSize: 10, marginTop: 16 }}>{description}</Text>
|
||||
</Stack.Item>
|
||||
<Stack.Item style={{ marginLeft: 16 }}>
|
||||
<ChoiceGroup
|
||||
styles={{ flexContainer: { width: 36 } }}
|
||||
options={[{ key: "selected", text: "" }]}
|
||||
selectedKey={isSelected ? "selected" : ""}
|
||||
onChange={onSelect}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
13
src/Explorer/QueryCopilot/QueryCopilotTab.test.tsx
Normal file
13
src/Explorer/QueryCopilot/QueryCopilotTab.test.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import Explorer from "../Explorer";
|
||||
import { QueryCopilotTab } from "./QueryCopilotTab";
|
||||
|
||||
describe("Query copilot tab snapshot test", () => {
|
||||
it("should render with initial input", () => {
|
||||
const wrapper = shallow(
|
||||
<QueryCopilotTab initialInput="Write a query to return all records in this table" explorer={new Explorer()} />
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
164
src/Explorer/QueryCopilot/QueryCopilotTab.tsx
Normal file
164
src/Explorer/QueryCopilot/QueryCopilotTab.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
/* eslint-disable no-console */
|
||||
import { FeedOptions } from "@azure/cosmos";
|
||||
import { IconButton, Image, Link, Stack, Text, TextField } from "@fluentui/react";
|
||||
import { QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants";
|
||||
import { getErrorMessage, handleError } from "Common/ErrorHandlingUtils";
|
||||
import { shouldEnableCrossPartitionKey } from "Common/HeadersUtility";
|
||||
import { MinimalQueryIterator } from "Common/IteratorUtilities";
|
||||
import { queryDocuments } from "Common/dataAccess/queryDocuments";
|
||||
import { queryDocumentsPage } from "Common/dataAccess/queryDocumentsPage";
|
||||
import { QueryResults } from "Contracts/ViewModels";
|
||||
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
|
||||
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||
import { SaveQueryPane } from "Explorer/Panes/SaveQueryPane/SaveQueryPane";
|
||||
import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection";
|
||||
import { queryPagesUntilContentPresent } from "Utils/QueryUtils";
|
||||
import { useSidePanel } from "hooks/useSidePanel";
|
||||
import React, { useState } from "react";
|
||||
import SplitterLayout from "react-splitter-layout";
|
||||
import CopilotIcon from "../../../images/Copilot.svg";
|
||||
import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
|
||||
import SaveQueryIcon from "../../../images/save-cosmos.svg";
|
||||
|
||||
interface QueryCopilotTabProps {
|
||||
initialInput: string;
|
||||
explorer: Explorer;
|
||||
}
|
||||
|
||||
export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
|
||||
initialInput,
|
||||
explorer,
|
||||
}: QueryCopilotTabProps): JSX.Element => {
|
||||
const [userInput, setUserInput] = useState<string>(initialInput || "");
|
||||
const [query, setQuery] = useState<string>("");
|
||||
const [selectedQuery, setSelectedQuery] = useState<string>("");
|
||||
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
||||
const [queryIterator, setQueryIterator] = useState<MinimalQueryIterator>();
|
||||
const [queryResults, setQueryResults] = useState<QueryResults>();
|
||||
const [errorMessage, setErrorMessage] = useState<string>("");
|
||||
|
||||
const generateQuery = (): string => {
|
||||
switch (userInput) {
|
||||
case "Write a query to return all records in this table":
|
||||
return "SELECT * FROM c";
|
||||
case "Write a query to return all records in this table created in the last thirty days":
|
||||
return "SELECT * FROM c WHERE c._ts > (DATEDIFF(s, '1970-01-01T00:00:00Z', GETUTCDATE()) - 2592000) * 1000";
|
||||
case `Write a query to return all records in this table created in the last thirty days which also have the record owner as "Contoso"`:
|
||||
return `SELECT * FROM c WHERE c.owner = "Contoso" AND c._ts > (DATEDIFF(s, '1970-01-01T00:00:00Z', GETUTCDATE()) - 2592000) * 1000`;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
const onExecuteQueryClick = async (): Promise<void> => {
|
||||
const queryToExecute = selectedQuery || query;
|
||||
const queryIterator = queryDocuments(QueryCopilotSampleDatabaseId, QueryCopilotSampleContainerId, queryToExecute, {
|
||||
enableCrossPartitionQuery: shouldEnableCrossPartitionKey(),
|
||||
} as FeedOptions);
|
||||
setQueryIterator(queryIterator);
|
||||
|
||||
setTimeout(async () => {
|
||||
await queryDocumentsPerPage(0, queryIterator);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const queryDocumentsPerPage = async (firstItemIndex: number, queryIterator: MinimalQueryIterator): Promise<void> => {
|
||||
try {
|
||||
setIsExecuting(true);
|
||||
const queryResults: QueryResults = await queryPagesUntilContentPresent(
|
||||
firstItemIndex,
|
||||
async (firstItemIndex: number) =>
|
||||
queryDocumentsPage(QueryCopilotSampleContainerId, queryIterator, firstItemIndex)
|
||||
);
|
||||
|
||||
setQueryResults(queryResults);
|
||||
setErrorMessage("");
|
||||
} catch (error) {
|
||||
const errorMessage = getErrorMessage(error);
|
||||
setErrorMessage(errorMessage);
|
||||
handleError(errorMessage, "executeQueryCopilotTab");
|
||||
} finally {
|
||||
setIsExecuting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getCommandbarButtons = (): CommandButtonComponentProps[] => {
|
||||
const executeQueryBtnLabel = selectedQuery ? "Execute Selection" : "Execute Query";
|
||||
const executeQueryBtn = {
|
||||
iconSrc: ExecuteQueryIcon,
|
||||
iconAlt: executeQueryBtnLabel,
|
||||
onCommandClick: () => onExecuteQueryClick(),
|
||||
commandButtonLabel: executeQueryBtnLabel,
|
||||
ariaLabel: executeQueryBtnLabel,
|
||||
hasPopup: false,
|
||||
};
|
||||
|
||||
const saveQueryBtn = {
|
||||
iconSrc: SaveQueryIcon,
|
||||
iconAlt: "Save Query",
|
||||
onCommandClick: () =>
|
||||
useSidePanel.getState().openSidePanel("Save Query", <SaveQueryPane explorer={explorer} queryToSave={query} />),
|
||||
commandButtonLabel: "Save Query",
|
||||
ariaLabel: "Save Query",
|
||||
hasPopup: false,
|
||||
};
|
||||
|
||||
return [executeQueryBtn, saveQueryBtn];
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
useCommandBar.getState().setContextButtons(getCommandbarButtons());
|
||||
}, [query]);
|
||||
|
||||
return (
|
||||
<Stack className="tab-pane" style={{ padding: 24, width: "100%", height: "100%" }}>
|
||||
<Stack horizontal verticalAlign="center">
|
||||
<Image src={CopilotIcon} />
|
||||
<Text style={{ marginLeft: 8, fontWeight: 600, fontSize: 16 }}>Copilot</Text>
|
||||
</Stack>
|
||||
<Stack horizontal verticalAlign="center" style={{ marginTop: 16, width: "100%" }}>
|
||||
<TextField
|
||||
value={userInput}
|
||||
onChange={(_, newValue) => setUserInput(newValue)}
|
||||
style={{ lineHeight: 30 }}
|
||||
styles={{ root: { width: "90%" } }}
|
||||
/>
|
||||
<IconButton
|
||||
iconProps={{ iconName: "Send" }}
|
||||
style={{ marginLeft: 8 }}
|
||||
onClick={() => setQuery(generateQuery())}
|
||||
/>
|
||||
</Stack>
|
||||
<Text style={{ marginTop: 8, marginBottom: 24, fontSize: 12 }}>
|
||||
AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.{" "}
|
||||
<Link href="" target="_blank">
|
||||
Read preview terms
|
||||
</Link>
|
||||
</Text>
|
||||
|
||||
<Stack className="tabPaneContentContainer">
|
||||
<SplitterLayout vertical={true} primaryIndex={0} primaryMinSize={100} secondaryMinSize={200}>
|
||||
<EditorReact
|
||||
language={"sql"}
|
||||
content={query}
|
||||
isReadOnly={false}
|
||||
ariaLabel={"Editing Query"}
|
||||
lineNumbers={"on"}
|
||||
onContentChanged={(newQuery: string) => setQuery(newQuery)}
|
||||
onContentSelected={(selectedQuery: string) => setSelectedQuery(selectedQuery)}
|
||||
/>
|
||||
<QueryResultSection
|
||||
isMongoDB={false}
|
||||
queryEditorContent={selectedQuery || query}
|
||||
error={errorMessage}
|
||||
queryResults={queryResults}
|
||||
isExecuting={isExecuting}
|
||||
executeQueryDocumentsPage={(firstItemIndex: number) => queryDocumentsPerPage(firstItemIndex, queryIterator)}
|
||||
/>
|
||||
</SplitterLayout>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,198 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Query Copilot Carousel snapshot test should render when isOpen is true 1`] = `
|
||||
<Modal
|
||||
isOpen={true}
|
||||
styles={
|
||||
Object {
|
||||
"main": Object {
|
||||
"width": 880,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
style={
|
||||
Object {
|
||||
"padding": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="space-between"
|
||||
>
|
||||
<Text
|
||||
variant="xLarge"
|
||||
>
|
||||
What exactly is copilot?
|
||||
</Text>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
Object {
|
||||
"iconName": "Cancel",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack
|
||||
style={
|
||||
Object {
|
||||
"marginTop": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 13,
|
||||
}
|
||||
}
|
||||
>
|
||||
A couple of lines about copilot and the background about it. The idea is to have some text to give context to the user.
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 14,
|
||||
"fontWeight": 600,
|
||||
"marginTop": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
How do you use copilot
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 13,
|
||||
"marginTop": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
To generate queries , just describe the query you want and copilot will generate the query for you.Watch this video to learn more about how to use copilot.
|
||||
</Text>
|
||||
<Image
|
||||
src=""
|
||||
style={
|
||||
Object {
|
||||
"margin": "16px auto",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 14,
|
||||
"fontWeight": 600,
|
||||
}
|
||||
}
|
||||
>
|
||||
What is copilot good at
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 13,
|
||||
"marginTop": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
A couple of lines about what copilot can do and its capablites with a link to
|
||||
|
||||
<StyledLinkBase
|
||||
href=""
|
||||
target="_blank"
|
||||
>
|
||||
documentation
|
||||
</StyledLinkBase>
|
||||
|
||||
if possible.
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 14,
|
||||
"fontWeight": 600,
|
||||
"marginTop": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
What are its limitations
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 13,
|
||||
"marginTop": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
A couple of lines about what copilot cant do and its limitations.
|
||||
|
||||
<StyledLinkBase
|
||||
href=""
|
||||
target="_blank"
|
||||
>
|
||||
Link to documentation
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 14,
|
||||
"fontWeight": 600,
|
||||
"marginTop": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
Disclaimer
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 13,
|
||||
"marginTop": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.
|
||||
|
||||
<StyledLinkBase
|
||||
href=""
|
||||
target="_blank"
|
||||
>
|
||||
Read preview terms
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
</Stack>
|
||||
<Separator
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"height": 1,
|
||||
"padding": "16px 0",
|
||||
"selectors": Object {
|
||||
"::before": Object {
|
||||
"background": undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="start"
|
||||
verticalAlign="center"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
text="Next"
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Modal>
|
||||
`;
|
||||
171
src/Explorer/QueryCopilot/__snapshots__/PromptCard.test.tsx.snap
Normal file
171
src/Explorer/QueryCopilot/__snapshots__/PromptCard.test.tsx.snap
Normal file
@@ -0,0 +1,171 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Prompt card snapshot test should render properly if isSelected is false 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
Object {
|
||||
"border": "1px solid #F3F2F1",
|
||||
"boxShadow": "0px 1.6px 3.6px rgba(0, 0, 0, 0.132), 0px 0.3px 0.9px rgba(0, 0, 0, 0.108)",
|
||||
"boxSizing": "border-box",
|
||||
"height": 100,
|
||||
"padding": "16px 0 16px 16px ",
|
||||
"width": 650,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StackItem
|
||||
grow={1}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<div>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"background": "#F8FFF0",
|
||||
"color": "#00A2AD",
|
||||
"fontSize": 13,
|
||||
}
|
||||
}
|
||||
>
|
||||
Prompt
|
||||
</Text>
|
||||
</div>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 13,
|
||||
"marginLeft": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
TestHeader
|
||||
</Text>
|
||||
</Stack>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 10,
|
||||
"marginTop": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
TestDescription
|
||||
</Text>
|
||||
</StackItem>
|
||||
<StackItem
|
||||
style={
|
||||
Object {
|
||||
"marginLeft": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StyledChoiceGroup
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"key": "selected",
|
||||
"text": "",
|
||||
},
|
||||
]
|
||||
}
|
||||
selectedKey=""
|
||||
styles={
|
||||
Object {
|
||||
"flexContainer": Object {
|
||||
"width": 36,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</StackItem>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Prompt card snapshot test should render properly if isSelected is true 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
Object {
|
||||
"border": "1px solid #F3F2F1",
|
||||
"boxShadow": "0px 1.6px 3.6px rgba(0, 0, 0, 0.132), 0px 0.3px 0.9px rgba(0, 0, 0, 0.108)",
|
||||
"boxSizing": "border-box",
|
||||
"height": 100,
|
||||
"padding": "16px 0 16px 16px ",
|
||||
"width": 650,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StackItem
|
||||
grow={1}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<div>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"background": "#F8FFF0",
|
||||
"color": "#00A2AD",
|
||||
"fontSize": 13,
|
||||
}
|
||||
}
|
||||
>
|
||||
Prompt
|
||||
</Text>
|
||||
</div>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 13,
|
||||
"marginLeft": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
TestHeader
|
||||
</Text>
|
||||
</Stack>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 10,
|
||||
"marginTop": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
TestDescription
|
||||
</Text>
|
||||
</StackItem>
|
||||
<StackItem
|
||||
style={
|
||||
Object {
|
||||
"marginLeft": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StyledChoiceGroup
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"key": "selected",
|
||||
"text": "",
|
||||
},
|
||||
]
|
||||
}
|
||||
selectedKey="selected"
|
||||
styles={
|
||||
Object {
|
||||
"flexContainer": Object {
|
||||
"width": 36,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</StackItem>
|
||||
</Stack>
|
||||
`;
|
||||
@@ -0,0 +1,124 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Query copilot tab snapshot test should render with initial input 1`] = `
|
||||
<Stack
|
||||
className="tab-pane"
|
||||
style={
|
||||
Object {
|
||||
"height": "100%",
|
||||
"padding": 24,
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<Image
|
||||
src=""
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 16,
|
||||
"fontWeight": 600,
|
||||
"marginLeft": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
Copilot
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
Object {
|
||||
"marginTop": 16,
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<StyledTextFieldBase
|
||||
onChange={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"lineHeight": 30,
|
||||
}
|
||||
}
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": "90%",
|
||||
},
|
||||
}
|
||||
}
|
||||
value="Write a query to return all records in this table"
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
Object {
|
||||
"iconName": "Send",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"marginLeft": 8,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 12,
|
||||
"marginBottom": 24,
|
||||
"marginTop": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.
|
||||
|
||||
<StyledLinkBase
|
||||
href=""
|
||||
target="_blank"
|
||||
>
|
||||
Read preview terms
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
<Stack
|
||||
className="tabPaneContentContainer"
|
||||
>
|
||||
<t
|
||||
customClassName=""
|
||||
onDragEnd={null}
|
||||
onDragStart={null}
|
||||
onSecondaryPaneSizeChange={null}
|
||||
percentage={false}
|
||||
primaryIndex={0}
|
||||
primaryMinSize={100}
|
||||
secondaryMinSize={200}
|
||||
vertical={true}
|
||||
>
|
||||
<EditorReact
|
||||
ariaLabel="Editing Query"
|
||||
content=""
|
||||
isReadOnly={false}
|
||||
language="sql"
|
||||
lineNumbers="on"
|
||||
onContentChanged={[Function]}
|
||||
onContentSelected={[Function]}
|
||||
/>
|
||||
<QueryResultSection
|
||||
error=""
|
||||
executeQueryDocumentsPage={[Function]}
|
||||
isExecuting={false}
|
||||
isMongoDB={false}
|
||||
queryEditorContent=""
|
||||
/>
|
||||
</t>
|
||||
</Stack>
|
||||
</Stack>
|
||||
`;
|
||||
105
src/Explorer/Quickstart/PostgreQuickstartCommands.ts
Normal file
105
src/Explorer/Quickstart/PostgreQuickstartCommands.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
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 tutotrial to learn how to create a database, add sample data, connect to a sample app and more.";
|
||||
return "Launch the quickstart for a tutorial 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:
|
||||
|
||||
22
src/Explorer/Quickstart/QuickstartFirewallNotification.tsx
Normal file
22
src/Explorer/Quickstart/QuickstartFirewallNotification.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
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,6 +11,17 @@ 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";
|
||||
@@ -35,24 +46,6 @@ 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);
|
||||
@@ -102,24 +95,32 @@ WHERE user_id = 3861633;`;
|
||||
<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]}>
|
||||
<Pivot
|
||||
style={{ marginTop: 10, width: "100%" }}
|
||||
selectedKey={GuideSteps[currentStep]}
|
||||
onLinkClick={(item?: PivotItem) => setCurrentStep(Object.values(GuideSteps).indexOf(item.props.itemKey))}
|
||||
>
|
||||
<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 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
|
||||
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.
|
||||
</Text>
|
||||
<Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "80%" }} />
|
||||
<Youtube videoId="nT64dFSfiUo" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
||||
</Stack>
|
||||
</PivotItem>
|
||||
<PivotItem
|
||||
@@ -129,19 +130,28 @@ WHERE user_id = 3861633;`;
|
||||
onClick={() => setCurrentStep(1)}
|
||||
>
|
||||
<Stack style={{ marginTop: 20 }}>
|
||||
<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>
|
||||
<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>
|
||||
<Stack horizontal style={{ marginTop: 16 }}>
|
||||
<TextField
|
||||
id="newTableCommand"
|
||||
multiline
|
||||
rows={6}
|
||||
rows={5}
|
||||
readOnly
|
||||
defaultValue={newTableCommand}
|
||||
styles={{ root: { width: "90%" } }}
|
||||
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",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
iconProps={{
|
||||
@@ -150,7 +160,7 @@ WHERE user_id = 3861633;`;
|
||||
onClick={() => onCopyBtnClicked("#newTableCommand")}
|
||||
/>
|
||||
</Stack>
|
||||
<Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "60%" }} />
|
||||
<Youtube videoId="il_sA6U1WcY" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
||||
</Stack>
|
||||
</PivotItem>
|
||||
<PivotItem
|
||||
@@ -161,21 +171,32 @@ WHERE user_id = 3861633;`;
|
||||
>
|
||||
<Stack style={{ marginTop: 20 }}>
|
||||
<Text>
|
||||
Congratulations, you have now created your first 2 tables.
|
||||
Let’s distribute the two tables using the create_distributed_table() function.
|
||||
<br />
|
||||
<br />
|
||||
Your table needs to be sharded on the worker nodes. You need to distribute table before you load any
|
||||
data or run any queries
|
||||
We are choosing “user_id” as the distribution column for our sample dataset.
|
||||
</Text>
|
||||
<DefaultButton style={{ marginTop: 16, width: 150 }}>Distribute table</DefaultButton>
|
||||
<DefaultButton
|
||||
style={{ marginTop: 16, width: 200 }}
|
||||
onClick={() => useTerminal.getState().sendMessage(distributeTableCommand)}
|
||||
>
|
||||
Create distributed table
|
||||
</DefaultButton>
|
||||
<Stack horizontal style={{ marginTop: 16 }}>
|
||||
<TextField
|
||||
id="distributeTableCommand"
|
||||
multiline
|
||||
rows={2}
|
||||
rows={5}
|
||||
readOnly
|
||||
defaultValue={distributeTableCommand}
|
||||
styles={{ root: { width: "90%" } }}
|
||||
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",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
iconProps={{
|
||||
@@ -184,7 +205,7 @@ WHERE user_id = 3861633;`;
|
||||
onClick={() => onCopyBtnClicked("#distributeTableCommand")}
|
||||
/>
|
||||
</Stack>
|
||||
<Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "60%" }} />
|
||||
<Youtube videoId="kCCDRRrN1r0" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
||||
</Stack>
|
||||
</PivotItem>
|
||||
<PivotItem
|
||||
@@ -194,22 +215,28 @@ WHERE user_id = 3861633;`;
|
||||
onClick={() => setCurrentStep(3)}
|
||||
>
|
||||
<Stack style={{ marginTop: 20 }}>
|
||||
<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>
|
||||
<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>
|
||||
<Stack horizontal style={{ marginTop: 16 }}>
|
||||
<TextField
|
||||
id="loadDataCommand"
|
||||
multiline
|
||||
rows={4}
|
||||
rows={5}
|
||||
readOnly
|
||||
defaultValue={loadDataCommand}
|
||||
styles={{ root: { width: "90%" } }}
|
||||
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",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
iconProps={{
|
||||
@@ -218,7 +245,7 @@ WHERE user_id = 3861633;`;
|
||||
onClick={() => onCopyBtnClicked("#loadDataCommand")}
|
||||
/>
|
||||
</Stack>
|
||||
<Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "60%" }} />
|
||||
<Youtube videoId="XSMEE2tujEk" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
||||
</Stack>
|
||||
</PivotItem>
|
||||
<PivotItem
|
||||
@@ -229,19 +256,29 @@ WHERE user_id = 3861633;`;
|
||||
>
|
||||
<Stack style={{ marginTop: 20 }}>
|
||||
<Text>
|
||||
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.
|
||||
Congratulations on creating and distributing your tables. Now, it's time to run your first query!
|
||||
</Text>
|
||||
<DefaultButton style={{ marginTop: 16, width: 110 }}>Try query</DefaultButton>
|
||||
<DefaultButton
|
||||
style={{ marginTop: 16, width: 115 }}
|
||||
onClick={() => useTerminal.getState().sendMessage(queryCommand)}
|
||||
>
|
||||
Try queries
|
||||
</DefaultButton>
|
||||
<Stack horizontal style={{ marginTop: 16 }}>
|
||||
<TextField
|
||||
id="queryCommand"
|
||||
multiline
|
||||
rows={6}
|
||||
rows={5}
|
||||
readOnly
|
||||
defaultValue={queryCommand}
|
||||
styles={{ root: { width: "90%" } }}
|
||||
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",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
iconProps={{
|
||||
@@ -250,7 +287,7 @@ WHERE user_id = 3861633;`;
|
||||
onClick={() => onCopyBtnClicked("#queryCommand")}
|
||||
/>
|
||||
</Stack>
|
||||
<Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "60%" }} />
|
||||
<Youtube videoId="k_EanjMtaPg" style={{ margin: "20px 0" }} opts={{ width: "90%" }} />
|
||||
</Stack>
|
||||
</PivotItem>
|
||||
</Pivot>
|
||||
@@ -258,7 +295,7 @@ WHERE user_id = 3861633;`;
|
||||
{currentStep === 5 && (
|
||||
<Stack style={{ margin: "auto" }} horizontalAlign="center">
|
||||
<Image src={CompleteIcon} />
|
||||
<Text variant="mediumPlus" style={{ fontWeight: 600, marginTop: 7 }}>
|
||||
<Text variant="mediumPlus" style={{ fontWeight: 900, marginTop: 7 }}>
|
||||
You are all set!
|
||||
</Text>
|
||||
<Text variant="mediumPlus" style={{ marginTop: 8 }}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Link, Stack, TeachingBubble, Text } from "@fluentui/react";
|
||||
import { DirectionalHint, Link, Stack, TeachingBubble, Text } from "@fluentui/react";
|
||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
||||
import React from "react";
|
||||
@@ -18,6 +18,11 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
let totalSteps = 9;
|
||||
if (userContext.isTryCosmosDBSubscription) {
|
||||
totalSteps = 10;
|
||||
}
|
||||
|
||||
switch (step) {
|
||||
case 1:
|
||||
return isSampleDBExpanded ? (
|
||||
@@ -33,7 +38,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
},
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent="Step 1 of 8"
|
||||
footerContent={"Step 1 of " + totalSteps}
|
||||
>
|
||||
Start viewing and working with your data by opening Documents under Data
|
||||
</TeachingBubble>
|
||||
@@ -55,7 +60,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(1),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent="Step 2 of 8"
|
||||
footerContent={"Step 2 of " + totalSteps}
|
||||
>
|
||||
View documents here using the documents window. You can also use your favorite MongoDB tools and drivers to do
|
||||
so.
|
||||
@@ -78,7 +83,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(2),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent="Step 3 of 8"
|
||||
footerContent={"Step 3 of " + totalSteps}
|
||||
>
|
||||
Add new document by copy / pasting JSON or uploading a JSON. You can also use your favorite MongoDB tools and
|
||||
drivers to do so.
|
||||
@@ -99,9 +104,9 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(3),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent="Step 4 of 8"
|
||||
footerContent={"Step 4 of " + totalSteps}
|
||||
>
|
||||
Query your data using the filter function. Azure Cosmos DB API for MongoDB provides comprehensive support for
|
||||
Query your data using the filter function. Azure Cosmos DB for MongoDB provides comprehensive support for
|
||||
MongoDB query language constructs. You can also use your favorite MongoDB tools and drivers to do so.
|
||||
</TeachingBubble>
|
||||
);
|
||||
@@ -120,7 +125,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(4),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent="Step 5 of 8"
|
||||
footerContent={"Step 5 of " + totalSteps}
|
||||
>
|
||||
Change throughput provisioned to your collection according to your needs
|
||||
</TeachingBubble>
|
||||
@@ -140,7 +145,7 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(5),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent="Step 6 of 8"
|
||||
footerContent={"Step 6 of " + totalSteps}
|
||||
>
|
||||
Use the indexing policy editor to create and edit your indexes.
|
||||
</TeachingBubble>
|
||||
@@ -160,12 +165,54 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(6),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent="Step 7 of 8"
|
||||
footerContent={"Step 7 of " + totalSteps}
|
||||
>
|
||||
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!"
|
||||
@@ -180,10 +227,10 @@ export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
}}
|
||||
secondaryButtonProps={{
|
||||
text: "Previous",
|
||||
onClick: () => setStep(7),
|
||||
onClick: () => (userContext.isTryCosmosDBSubscription ? setStep(9) : setStep(8)),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent="Step 8 of 8"
|
||||
footerContent={"Step " + totalSteps + " of " + totalSteps}
|
||||
>
|
||||
<Stack>
|
||||
<Text style={{ color: "white" }}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Link, Stack, TeachingBubble, Text } from "@fluentui/react";
|
||||
import { DirectionalHint, Link, Stack, TeachingBubble, Text } from "@fluentui/react";
|
||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
||||
import React from "react";
|
||||
@@ -17,6 +17,10 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
if (userContext.apiType !== "SQL") {
|
||||
return <></>;
|
||||
}
|
||||
let totalSteps = 8;
|
||||
if (userContext.isTryCosmosDBSubscription) {
|
||||
totalSteps = 9;
|
||||
}
|
||||
|
||||
switch (step) {
|
||||
case 1:
|
||||
@@ -33,7 +37,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
},
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent="Step 1 of 7"
|
||||
footerContent={"Step 1 of " + totalSteps}
|
||||
>
|
||||
Start viewing and working with your data by opening Items under Data
|
||||
</TeachingBubble>
|
||||
@@ -55,7 +59,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(1),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent="Step 2 of 7"
|
||||
footerContent={"Step 2 of " + totalSteps}
|
||||
>
|
||||
View item here using the items window. Additionally you can also filter items to be reviewed with the filter
|
||||
function
|
||||
@@ -78,7 +82,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(2),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent="Step 3 of 7"
|
||||
footerContent={"Step 3 of " + totalSteps}
|
||||
>
|
||||
Add new item by copy / pasting JSON; or uploading a JSON
|
||||
</TeachingBubble>
|
||||
@@ -98,7 +102,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(3),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent="Step 4 of 7"
|
||||
footerContent={"Step 4 of " + totalSteps}
|
||||
>
|
||||
Query your data using either the filter function or new query.
|
||||
</TeachingBubble>
|
||||
@@ -118,7 +122,7 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(4),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent="Step 5 of 7"
|
||||
footerContent={"Step 5 of " + totalSteps}
|
||||
>
|
||||
Change throughput provisioned to your container according to your needs
|
||||
</TeachingBubble>
|
||||
@@ -138,12 +142,54 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
onClick: () => setStep(5),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent="Step 6 of 7"
|
||||
footerContent={"Step 6 of " + totalSteps}
|
||||
>
|
||||
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!"
|
||||
@@ -158,10 +204,10 @@ export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
|
||||
}}
|
||||
secondaryButtonProps={{
|
||||
text: "Previous",
|
||||
onClick: () => setStep(6),
|
||||
onClick: () => (userContext.isTryCosmosDBSubscription ? setStep(8) : setStep(7)),
|
||||
}}
|
||||
onDismiss={() => onDimissTeachingBubble()}
|
||||
footerContent="Step 7 of 7"
|
||||
footerContent={"Step " + totalSteps + " of " + totalSteps}
|
||||
>
|
||||
<Stack>
|
||||
<Text style={{ color: "white" }}>
|
||||
|
||||
@@ -14,10 +14,6 @@
|
||||
padding-right: 16px;
|
||||
max-width: 1168px;
|
||||
|
||||
> * {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
> .title {
|
||||
position: relative; // To attach FeaturePanelLauncher as absolute
|
||||
color: @BaseHigh;
|
||||
@@ -40,6 +36,7 @@
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
margin: 40px auto;
|
||||
width: 84%;
|
||||
|
||||
> .mainButton {
|
||||
min-width: 124px;
|
||||
|
||||
@@ -11,20 +11,24 @@ import {
|
||||
TeachingBubbleContent,
|
||||
Text,
|
||||
} from "@fluentui/react";
|
||||
import { sendMessage } from "Common/MessageHandler";
|
||||
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||
import { TerminalKind } from "Contracts/ViewModels";
|
||||
import { SplashScreenButton } from "Explorer/SplashScreen/SplashScreenButton";
|
||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
|
||||
import { useCarousel } from "hooks/useCarousel";
|
||||
import { usePostgres } from "hooks/usePostgres";
|
||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||
import * as React from "react";
|
||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
|
||||
import ConnectIcon from "../../../images/Connect_color.svg";
|
||||
import ContainersIcon from "../../../images/Containers.svg";
|
||||
import CopilotIcon from "../../../images/Copilot.svg";
|
||||
import LinkIcon from "../../../images/Link_blue.svg";
|
||||
import NotebookIcon from "../../../images/notebook/Notebook-resource.svg";
|
||||
import NotebookColorIcon from "../../../images/Notebooks.svg";
|
||||
import PowerShellIcon from "../../../images/PowerShell.svg";
|
||||
import QuickStartIcon from "../../../images/Quickstart_Lightning.svg";
|
||||
import NotebookIcon from "../../../images/notebook/Notebook-resource.svg";
|
||||
import CollectionIcon from "../../../images/tree-collection.svg";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { userContext } from "../../UserContext";
|
||||
@@ -52,10 +56,6 @@ export interface SplashScreenProps {
|
||||
}
|
||||
|
||||
export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
private static readonly dataModelingUrl = "https://docs.microsoft.com/azure/cosmos-db/modeling-data";
|
||||
private static readonly throughputEstimatorUrl = "https://cosmos.azure.com/capacitycalculator";
|
||||
private static readonly failoverUrl = "https://docs.microsoft.com/azure/cosmos-db/high-availability";
|
||||
|
||||
private readonly container: Explorer;
|
||||
private subscriptions: Array<{ dispose: () => void }>;
|
||||
|
||||
@@ -91,6 +91,12 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
() => this.setState({}),
|
||||
(state) => state.showPostgreTeachingBubble
|
||||
),
|
||||
},
|
||||
{
|
||||
dispose: usePostgres.subscribe(
|
||||
() => this.setState({}),
|
||||
(state) => state.showResetPasswordBubble
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -100,16 +106,157 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
this.setState({});
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
const mainItems = this.createMainItems();
|
||||
private getSplashScreenButtons = (): JSX.Element => {
|
||||
if (userContext.features.enableCopilot && userContext.apiType === "SQL") {
|
||||
return (
|
||||
<Stack style={{ width: "66%", cursor: "pointer", margin: "40px auto" }} tokens={{ childrenGap: 16 }}>
|
||||
<Stack horizontal tokens={{ childrenGap: 16 }}>
|
||||
<SplashScreenButton
|
||||
imgSrc={QuickStartIcon}
|
||||
title={"Launch quick start"}
|
||||
description={"Launch a quick start tutorial to get started with sample data"}
|
||||
onClick={() => {
|
||||
this.container.onNewCollectionClicked({ isQuickstart: true });
|
||||
traceOpen(Action.LaunchQuickstart, { apiType: userContext.apiType });
|
||||
}}
|
||||
/>
|
||||
<SplashScreenButton
|
||||
imgSrc={ContainersIcon}
|
||||
title={`New ${getCollectionName()}`}
|
||||
description={"Create a new container for storage and throughput"}
|
||||
onClick={() => {
|
||||
this.container.onNewCollectionClicked();
|
||||
traceOpen(Action.NewContainerHomepage, { apiType: userContext.apiType });
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack horizontal tokens={{ childrenGap: 16 }}>
|
||||
<SplashScreenButton
|
||||
imgSrc={CopilotIcon}
|
||||
title={"Query faster with Copilot"}
|
||||
description={
|
||||
"Copilot is your AI buddy that helps you write Azure Cosmos DB queries like a pro. Try it using our sample data set now!"
|
||||
}
|
||||
onClick={() => useCarousel.getState().setShowCopilotCarousel(true)}
|
||||
/>
|
||||
<SplashScreenButton
|
||||
imgSrc={ConnectIcon}
|
||||
title={"Connect"}
|
||||
description={"Prefer using your own choice of tooling? Find the connection string you need to connect"}
|
||||
onClick={() => useTabs.getState().openAndActivateReactTab(ReactTabKind.Connect)}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const mainItems = this.createMainItems();
|
||||
return (
|
||||
<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>
|
||||
)}
|
||||
{mainItems.map((item) => (
|
||||
<Stack
|
||||
id={`mainButton-${item.id}`}
|
||||
horizontal
|
||||
className="mainButton focusable"
|
||||
key={`${item.title}`}
|
||||
onClick={item.onClick}
|
||||
onKeyPress={(event: React.KeyboardEvent) => this.onSplashScreenItemKeyPress(event, item.onClick)}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
<div>
|
||||
<img src={item.iconSrc} alt="" />
|
||||
</div>
|
||||
<div className="legendContainer">
|
||||
<Stack horizontal verticalAlign="center" style={{ marginBottom: 8 }}>
|
||||
<div className="legend">{item.title}</div>
|
||||
{item.showLinkIcon && <Image style={{ marginLeft: 8, width: 16 }} src={LinkIcon} />}
|
||||
</Stack>
|
||||
|
||||
<div id={item.id} className="newDescription">
|
||||
{item.description}
|
||||
</div>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<div className="connectExplorerContainer">
|
||||
<form className="connectExplorerFormContainer">
|
||||
<div className="splashScreenContainer">
|
||||
<div className="splashScreen">
|
||||
<div className="title">
|
||||
{userContext.apiType === "Postgres" ? "Welcome to Cosmos DB - PostgreSQL" : "Welcome to Cosmos DB"}
|
||||
<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"}
|
||||
<FeaturePanelLauncher />
|
||||
</div>
|
||||
<div className="subtitle">
|
||||
@@ -117,51 +264,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
? "Get started with our sample datasets, documentation, and additional tools."
|
||||
: "Globally distributed, multi-model database service for any scale"}
|
||||
</div>
|
||||
<div className="mainButtonsContainer">
|
||||
{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
|
||||
horizontal
|
||||
className="mainButton focusable"
|
||||
key={`${item.title}`}
|
||||
onClick={item.onClick}
|
||||
onKeyPress={(event: React.KeyboardEvent) => this.onSplashScreenItemKeyPress(event, item.onClick)}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
<div>
|
||||
<img src={item.iconSrc} alt="" />
|
||||
</div>
|
||||
<div className="legendContainer">
|
||||
<Stack horizontal verticalAlign="center" style={{ marginBottom: 8 }}>
|
||||
<div className="legend">{item.title}</div>
|
||||
{item.showLinkIcon && <Image style={{ marginLeft: 8, width: 16 }} src={LinkIcon} />}
|
||||
</Stack>
|
||||
|
||||
<div id={item.id} className="newDescription">
|
||||
{item.description}
|
||||
</div>
|
||||
</div>
|
||||
</Stack>
|
||||
))}
|
||||
</div>
|
||||
{this.getSplashScreenButtons()}
|
||||
{useCarousel.getState().showCoachMark && (
|
||||
<Coachmark
|
||||
target="#quickstartDescription"
|
||||
@@ -191,8 +294,8 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
</Coachmark>
|
||||
)}
|
||||
{userContext.apiType === "Postgres" ? (
|
||||
<Stack horizontal style={{ margin: "0 auto" }} tokens={{ childrenGap: "15%" }}>
|
||||
<Stack>
|
||||
<Stack horizontal style={{ margin: "0 auto", width: "84%" }} tokens={{ childrenGap: 16 }}>
|
||||
<Stack style={{ width: "33%" }}>
|
||||
<Text
|
||||
variant="large"
|
||||
style={{
|
||||
@@ -204,7 +307,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
</Text>
|
||||
{this.getNextStepItems()}
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Stack style={{ width: "33%" }}>
|
||||
<Text
|
||||
variant="large"
|
||||
style={{
|
||||
@@ -216,6 +319,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
</Text>
|
||||
{this.getTipsAndLearnMoreItems()}
|
||||
</Stack>
|
||||
<Stack style={{ width: "33%" }}></Stack>
|
||||
</Stack>
|
||||
) : (
|
||||
<div className="moreStuffContainer">
|
||||
@@ -253,7 +357,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
public createMainItems(): SplashScreenItem[] {
|
||||
const heroes: SplashScreenItem[] = [];
|
||||
|
||||
if (userContext.apiType === "SQL" || userContext.apiType === "Mongo" || userContext.apiType === "Postgres") {
|
||||
if (
|
||||
userContext.apiType === "SQL" ||
|
||||
userContext.apiType === "Mongo" ||
|
||||
(userContext.apiType === "Postgres" && !userContext.isReplica)
|
||||
) {
|
||||
const launchQuickstartBtn = {
|
||||
id: "quickstartDescription",
|
||||
iconSrc: QuickStartIcon,
|
||||
@@ -282,7 +390,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.Mongo),
|
||||
onClick: () => this.container.openNotebookTerminal(TerminalKind.Postgres),
|
||||
};
|
||||
heroes.push(postgreShellBtn);
|
||||
} else {
|
||||
@@ -300,11 +408,11 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
|
||||
const connectBtn = {
|
||||
iconSrc: ConnectIcon,
|
||||
title: userContext.apiType === "Postgres" ? "Connect with PG Admin" : "Connect",
|
||||
title: userContext.apiType === "Postgres" ? "Connect with pgAdmin" : "Connect",
|
||||
description:
|
||||
userContext.apiType === "Postgres"
|
||||
? "Prefer using your own choice of tooling? Find the connection string you need to connect"
|
||||
: "Prefer PGadmin? Find your connection strings here",
|
||||
? "Prefer pgAdmin? Find your connection strings here"
|
||||
: "Prefer using your own choice of tooling? Find the connection string you need to connect",
|
||||
onClick: () => useTabs.getState().openAndActivateReactTab(ReactTabKind.Connect),
|
||||
};
|
||||
heroes.push(connectBtn);
|
||||
@@ -388,7 +496,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
{
|
||||
link: "https://aka.ms/mongodbintro",
|
||||
title: "What is the MongoDB API?",
|
||||
description: "Understand the Cosmos DB API for MongoDB and its features.",
|
||||
description: "Understand Azure Cosmos DB for MongoDB and its features.",
|
||||
},
|
||||
{
|
||||
link: "https://aka.ms/mongodbfeaturesupport",
|
||||
@@ -445,7 +553,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
{
|
||||
link: "https://aka.ms/tableintro",
|
||||
title: "What is the Table API?",
|
||||
description: "Understand the Table API in Cosmos DB and its features",
|
||||
description: "Understand Azure Cosmos DB for Table and its features",
|
||||
},
|
||||
{
|
||||
link: "https://aka.ms/tableimport",
|
||||
@@ -454,8 +562,8 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
},
|
||||
{
|
||||
link: "https://aka.ms/tablefaq",
|
||||
title: "Table API FAQs",
|
||||
description: "Common questions about the Table API",
|
||||
title: "Azure Cosmos DB for Table FAQs",
|
||||
description: "Common questions about Azure Cosmos DB for Table",
|
||||
},
|
||||
];
|
||||
break;
|
||||
@@ -473,7 +581,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
>
|
||||
{item.title}
|
||||
</Link>
|
||||
<Image src={LinkIcon} />
|
||||
<Image src={LinkIcon} alt=" " />
|
||||
</Stack>
|
||||
<Text>{item.description}</Text>
|
||||
</Stack>
|
||||
@@ -508,7 +616,17 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
}
|
||||
|
||||
private getLearningResourceItems(): JSX.Element {
|
||||
let items: { link: string; title: string; description: string }[];
|
||||
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[];
|
||||
switch (userContext.apiType) {
|
||||
case "SQL":
|
||||
case "Postgres":
|
||||
@@ -518,11 +636,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
title: "Get Started using an SDK",
|
||||
description: "Learn about the Azure Cosmos DB SDK.",
|
||||
},
|
||||
{
|
||||
link: "https://aka.ms/msl-complex-queries",
|
||||
title: "Master Complex Queries",
|
||||
description: "Learn how to author complex queries.",
|
||||
},
|
||||
cdbLiveTv,
|
||||
{
|
||||
link: "https://aka.ms/msl-move-data",
|
||||
title: "Migrate Your Data",
|
||||
@@ -542,11 +656,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
title: "Getting Started Guide",
|
||||
description: "Learn the basics to get started.",
|
||||
},
|
||||
{
|
||||
link: "http://aka.ms/mongodotnet",
|
||||
title: "Build a web API",
|
||||
description: "Create a web API with the.NET SDK.",
|
||||
},
|
||||
cdbLiveTv,
|
||||
];
|
||||
break;
|
||||
case "Cassandra":
|
||||
@@ -556,11 +666,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
title: "Create a Container",
|
||||
description: "Get to know the create a container options.",
|
||||
},
|
||||
{
|
||||
link: "https://aka.ms/cassandraserverdiagnostics",
|
||||
title: "Run Server Diagnostics",
|
||||
description: "Learn how to run server diagnostics.",
|
||||
},
|
||||
cdbLiveTv,
|
||||
{
|
||||
link: "https://aka.ms/Cassandrathroughput",
|
||||
title: "Provision Throughput",
|
||||
@@ -580,11 +686,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
title: "Import Graph Data",
|
||||
description: "Learn Bulk ingestion data using BulkExecutor",
|
||||
},
|
||||
{
|
||||
link: "https://aka.ms/graphoptimize",
|
||||
title: "Optimize your Queries",
|
||||
description: "Learn how to evaluate your Gremlin queries",
|
||||
},
|
||||
cdbLiveTv,
|
||||
];
|
||||
break;
|
||||
case "Tables":
|
||||
@@ -592,18 +694,14 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
{
|
||||
link: "https://aka.ms/tabledotnet",
|
||||
title: "Build a .NET App",
|
||||
description: "How to access Table API from a .NET app.",
|
||||
description: "How to access Azure Cosmos DB for Table from a .NET app.",
|
||||
},
|
||||
{
|
||||
link: "https://aka.ms/Tablejava",
|
||||
title: "Build a Java App",
|
||||
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",
|
||||
description: "Create a Azure Cosmos DB for Table app with Java SDK ",
|
||||
},
|
||||
cdbLiveTv,
|
||||
];
|
||||
break;
|
||||
}
|
||||
@@ -634,24 +732,24 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
private getNextStepItems(): JSX.Element {
|
||||
const items: { link: string; title: string; description: string }[] = [
|
||||
{
|
||||
link: "",
|
||||
title: "Performance tuning",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
link: "https://go.microsoft.com/fwlink/?linkid=2208312",
|
||||
title: "Data Modeling",
|
||||
description: "",
|
||||
},
|
||||
{
|
||||
link: "",
|
||||
title: "Join Citus community",
|
||||
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: "Useful diagnostic queries",
|
||||
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: "",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Stack style={{ minWidth: 124, maxWidth: 296 }}>
|
||||
{items.map((item, i) => (
|
||||
<Stack key={`nextStep${i}`} style={{ marginBottom: 26 }}>
|
||||
<Stack horizontal verticalAlign="center" style={{ fontSize: 14 }}>
|
||||
@@ -670,24 +768,24 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
private getTipsAndLearnMoreItems(): JSX.Element {
|
||||
const items: { link: string; title: string; description: string }[] = [
|
||||
{
|
||||
link: "",
|
||||
title: "Data modeling",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
link: "https://go.microsoft.com/fwlink/?linkid=2207226",
|
||||
title: "Performance Tuning",
|
||||
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=2208037",
|
||||
title: "Useful Diagnostic Queries",
|
||||
description: "",
|
||||
},
|
||||
{
|
||||
link: "",
|
||||
title: "Build apps with Python/ Java/ Django",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
link: "https://go.microsoft.com/fwlink/?linkid=2205270",
|
||||
title: "Distributed SQL Reference",
|
||||
description: "",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Stack style={{ minWidth: 124, maxWidth: 296 }}>
|
||||
{items.map((item, i) => (
|
||||
<Stack key={`tips${i}`} style={{ marginBottom: 26 }}>
|
||||
<Stack horizontal verticalAlign="center" style={{ fontSize: 14 }}>
|
||||
|
||||
50
src/Explorer/SplashScreen/SplashScreenButton.tsx
Normal file
50
src/Explorer/SplashScreen/SplashScreenButton.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Stack, Text } from "@fluentui/react";
|
||||
import React from "react";
|
||||
import { KeyCodes } from "../../Common/Constants";
|
||||
|
||||
interface SplashScreenButtonProps {
|
||||
imgSrc: string;
|
||||
title: string;
|
||||
description: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export const SplashScreenButton: React.FC<SplashScreenButtonProps> = ({
|
||||
imgSrc,
|
||||
title,
|
||||
description,
|
||||
onClick,
|
||||
}: SplashScreenButtonProps): JSX.Element => {
|
||||
return (
|
||||
<Stack
|
||||
horizontal
|
||||
style={{
|
||||
border: "1px solid #949494",
|
||||
boxSizing: "border-box",
|
||||
boxShadow: "0 4px 4px rgba(0, 0, 0, 0.25)",
|
||||
borderRadius: 4,
|
||||
padding: "32px 16px",
|
||||
backgroundColor: "#ffffff",
|
||||
width: "100%",
|
||||
minHeight: 150,
|
||||
}}
|
||||
onClick={onClick}
|
||||
onKeyPress={(event: React.KeyboardEvent) => {
|
||||
if (event.charCode === KeyCodes.Space || event.charCode === KeyCodes.Enter) {
|
||||
onClick();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
<div>
|
||||
<img src={imgSrc} />
|
||||
</div>
|
||||
<Stack style={{ marginLeft: 16 }}>
|
||||
<Text style={{ fontSize: 18, fontWeight: 600 }}>{title}</Text>
|
||||
<Text style={{ fontSize: 13 }}>{description}</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -516,7 +516,7 @@ export default class QueryBuilderViewModel {
|
||||
};
|
||||
|
||||
public onAddNewClauseKeyDown = (event: KeyboardEvent): boolean => {
|
||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
||||
if (event.key === "Enter" || event.key === "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, Platform } from "../../../ConfigContext";
|
||||
import { configContext } from "../../../ConfigContext";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
@@ -9,6 +9,8 @@ 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 {
|
||||
@@ -47,7 +49,6 @@ export default class MongoShellTabComponent extends Component<
|
||||
IMongoShellTabComponentProps,
|
||||
IMongoShellTabComponentStates
|
||||
> {
|
||||
private _runtimeEndpoint: string;
|
||||
private _logTraces: Map<string, number>;
|
||||
|
||||
constructor(props: IMongoShellTabComponentProps) {
|
||||
@@ -55,7 +56,7 @@ export default class MongoShellTabComponent extends Component<
|
||||
this._logTraces = new Map();
|
||||
|
||||
this.state = {
|
||||
url: this.getURL(),
|
||||
url: getMongoShellUrl(),
|
||||
};
|
||||
|
||||
props.onMongoShellTabAccessor({
|
||||
@@ -65,22 +66,6 @@ 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 {}
|
||||
|
||||
@@ -136,6 +121,7 @@ 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(
|
||||
{
|
||||
@@ -151,7 +137,7 @@ export default class MongoShellTabComponent extends Component<
|
||||
apiEndpoint: apiEndpoint,
|
||||
},
|
||||
},
|
||||
configContext.BACKEND_ENDPOINT
|
||||
targetOrigin
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
86
src/Explorer/Tabs/MongoShellTab/getMongoShellOrigin.test.ts
Normal file
86
src/Explorer/Tabs/MongoShellTab/getMongoShellOrigin.test.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
10
src/Explorer/Tabs/MongoShellTab/getMongoShellOrigin.ts
Normal file
10
src/Explorer/Tabs/MongoShellTab/getMongoShellOrigin.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { configContext } from "../../../ConfigContext";
|
||||
import { userContext } from "../../../UserContext";
|
||||
|
||||
export function getMongoShellOrigin(): string {
|
||||
if (userContext.features.loadLegacyMongoShellFromBE === true) {
|
||||
return configContext.BACKEND_ENDPOINT;
|
||||
}
|
||||
|
||||
return window.origin;
|
||||
}
|
||||
206
src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.test.ts
Normal file
206
src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.test.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
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");
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user