Compare commits

..

57 Commits

Author SHA1 Message Date
Victor Meng
af5fd699f9 Increase the video width 2022-09-16 18:25:02 -07:00
Victor Meng
d78610cd33 Remove feature flag 2022-09-14 18:27:09 -07:00
Victor Meng
bc99ca372b Hardcode API type to Postgres to test Postgres quick start 2022-09-14 17:21:26 -07:00
victor-meng
d54c896d74 Postgre quickstart UI (#1319) 2022-09-14 14:42:09 -07:00
Karthik chakravarthy
d6a58fd45f Gallery loading issue (#1321) 2022-09-14 14:57:32 -04:00
Srinath Narayanan
a851f78b1c Moved notebooks to latest API version (#1320)
* Moved notebooks to latest API version

* fixed lint errors
2022-08-31 02:38:52 +05:30
Karthik chakravarthy
a4061a96b1 Hide connect btn and only show when phoenix notebooks or features are used (#1318) 2022-08-22 19:07:01 -04:00
victor-meng
06c3b6fe94 Whitelist AGC endpoint in data explorer (#1317) 2022-08-16 10:15:26 -07:00
Karthik chakravarthy
89c7ebdd20 Hide connect button incase Phoenix Notebooks flight is false (#1314)
* Hide connect button id flight notebooks is false and show only if features are used
2022-08-10 09:29:23 -04:00
victor-meng
d0c2f72ed3 Add teaching bubbles for mongo quickstart (#1313) 2022-08-03 13:54:01 -07:00
Karthik chakravarthy
c2673ec707 Refactor text messages (#1312) 2022-07-27 12:05:56 -04:00
vchske
759a4ca5cf Reverting cosmos sdk to 3.16.2 (#1311) 2022-07-26 13:17:01 -07:00
victor-meng
9078878f44 Update params for creating collection via mongo proxy (#1310) 2022-07-25 16:15:48 -07:00
vchske
139a9cb22c Updating SDK to 3.16.3 (#1308)
* Updating SDK to 3.16.3

* Updating package-lock Cosmos DB SDK to 3.16.3

* Updating package-lock's Cosmos DB SDK to 3.16.3
2022-07-18 15:22:27 -07:00
victor-meng
c5ef0e608e Fix aggregated query metrics show incorrect number (#1302) 2022-07-12 10:39:40 -07:00
victor-meng
d66e85f431 Allow users to delete TablesDB database when enableSDKOperations feature flag is set (#1300) 2022-07-06 10:54:49 -07:00
victor-meng
0996489897 Improve quickstart teaching bubble telemetries and make the home page a tab (#1299) 2022-07-06 10:54:35 -07:00
victor-meng
f83634c50d Upgrade js sdk version (#1297) 2022-06-28 17:52:46 -07:00
Mathieu Tremblay
b9dffdd990 Rename properties from notebookService to phoenixService in phoenix c… (#1263)
* Rename properties from notebookService to phoenixService in phoenix client


Co-authored-by: kcheekuri <kcheekuri@microsoft.com>
2022-06-21 14:19:45 -04:00
Karthik chakravarthy
13811b1d44 Update allocate call (#1290) 2022-06-21 08:48:18 -04:00
Tanuj Mittal
1643ce4dbb Log errors by Schema Analyzer (#1293) 2022-06-21 05:07:49 +05:30
Karthik chakravarthy
7abd65ac4b Enable phoenix based of allowed subscription and flights (#1291)
* Enable phoenix based of allowed subscription and flights
2022-06-15 17:08:06 -04:00
Tanuj Mittal
c731eb9cf9 Fix Official Samples not loading when Gallery tab is opened (#1282) 2022-06-09 02:30:49 +05:30
siddjoshi-ms
c534b2d74b Sqlx az portal cost estimate (#1264)
* added isZoneRedundant to rp call

* Pass region item everywhere

* cost breakdown string added
2022-06-08 10:16:43 -07:00
victor-meng
98fb7a5fd6 Upgrade JS SDK version to 3.16.1 (#1287) 2022-06-03 13:19:29 -07:00
chandrasekhar gunturi
e34f68b162 adding Materializedviews under selfserve (#1247)
* adding Materializedviews under selfserve

* ran npm format & fixed few links

* modified required key & changed URLs

* modifying links to aka.ms

* modifying few descriptions

* Delete VSWorkspaceState.json

* Delete .suo

* modifying URLs & adding missing keys

Co-authored-by: Chandra sekhar Gunturi <chguntur@microsoft.com>
2022-06-03 12:51:54 +05:30
victor-meng
7ab57c9ec4 Add telemetry for new quick start (#1285) 2022-06-01 15:26:10 -07:00
victor-meng
7e1343e84f Add check of account creation time to show carousel (#1284)
Co-authored-by: artrejo <ato9000@users.noreply.github.com>
2022-06-01 15:25:56 -07:00
victor-meng
46ca952955 Add condition for showing quick start carousel (#1278)
* Add condition for showing quick start carousel

* Show coach mark when carousel is closed

* Add condition for showing quick start carousel and other UI changes

* Fix compile error

* Fix issue with coach mark

* Fix test

* Add new sample data, fix link url, fix e2e tests

* Fix e2e tests
2022-05-23 20:52:21 -07:00
Srinath Narayanan
d13b7a50ad phoenix errors added (#1272) 2022-05-23 16:28:45 +05:30
victor-meng
dfd5a7c698 Use undefined as the partition key value when deleting and updating documents (#1274) 2022-05-20 16:39:21 -07:00
victor-meng
2ab60a7a40 Add connect tab for new quick start (#1273)
* Add connect tab

* Error handling

* Add button to open quick start blade

* Handle scenario where user don't have write access
2022-05-20 16:38:38 -07:00
victor-meng
dc83bf6fa0 Update recent items UI and add to new home page (#1275)
* Update recent items UI and add to new home page

* Update text and links for different APIs

* Update home page text and carousel images
2022-05-20 16:37:50 -07:00
victor-meng
c2f3471afe Add carousel for quick start (#1271)
* Add carousel for quick start

* Put carousel behind feature flag

* Install type definition for react-youtube

* Install type definition for react-youtube

* Remove @types/youtube-player

* Move feature flag outside of quickstarttutorial component
2022-05-16 18:23:54 -07:00
victor-meng
60525f654b Add teaching bubbles after creating sample DB (#1270)
* Add teaching bubbles after creating sample DB

* Add teaching bubble while creating sample container

* Remove test code

* Update tests and always show teaching bubbles in add collection panel when launched from quick start

* Fix snapshot
2022-05-16 17:45:50 -07:00
victor-meng
37122acc33 Fix issue with reading and saving mongo documents with shard key (#1269) 2022-05-11 18:12:12 -07:00
victor-meng
0b6bb7a985 Fix stored procedures, UDF, and triggers sometimes don't show up in the resource tree after expanding (#1267) 2022-05-05 18:27:13 -07:00
Srinath Narayanan
fcfc52a80c Added support for multi line descriptions in self serve framework (#1266)
* Added support for multi line descriptions

* test snapshot change

* addresse pr comments
2022-05-06 00:01:06 +05:30
Armando Trejo Oliver
9cb4632f32 Update subscription for preview PRs (#1265)
* Update subscription for preview PRs

* Fix command line args

* Replace subid for sub name

* Remove --subscription from az storage commands

* revert other changes
2022-05-04 20:11:13 -07:00
victor-meng
ebbfc5f517 New quick start - create sample container (#1259)
* Update links and texts

* Remove unintended change

* Quick start - create sample container

* Small adjustments + fix test

* Hide coach mark behind feature flag

* Add snapshot test for AddCollectionPanel to increase coverage

* Fix snapshot

* Fix snapshot 2...

* Change runner account name

* Change portal runner account name
2022-05-04 18:24:34 -07:00
Armando Trejo Oliver
d05a05716f Fix Emulator Quick Start Links (#1255) 2022-05-04 15:30:48 -07:00
Srinath Narayanan
da1169d0e2 Added publicgallery flight (#1262)
* added publicgallery flag

* fixed PR comments
2022-05-04 03:50:44 +05:30
victor-meng
27423e2321 Create new home page (#1257)
* initial commit

* Remove test code

* Rename icon

* Fix feature flag

* Update links and texts

* Remove unintended change
2022-05-02 13:45:54 -07:00
victor-meng
56731ec051 Fix error when reading document with no partition key (#1256) 2022-04-27 11:12:57 -07:00
victor-meng
22f7d588a1 Add support for multi-partition key (#1252) 2022-04-21 13:37:02 -07:00
Rama krishnan Raghupathy
9e7bbcfab6 Change Headers meant to unblock merge prviate preview customers (#1251) 2022-04-14 18:01:11 -07:00
Karthik chakravarthy
cea3978ca6 Make Phoenix enabled based of config context (#1250)
* Have phoenix enabled based of config context irrespective of hosted or portal explorer

* Add telemetry start and success

* Add error code in failure case
2022-04-12 16:25:24 -04:00
victor-meng
2e3e547a46 Fix scale tab not showing the correct throughput mode and value and remove freetierautoscalethroughput feature flag (#1245) 2022-03-31 12:13:08 -07:00
Karthik chakravarthy
06f6df83ad Add UX for error Information for Phoenix workspace connection (#1234)
* Add UX for error Information

* update text messages
2022-03-30 08:59:37 -04:00
victor-meng
8b22027cb6 Fix settings tab shows no selected value (#1237) 2022-03-25 15:13:56 -07:00
Armando Trejo Oliver
496f596f38 Fix Parent Origin Regex (#1239)
Not all regex are escaped properly
2022-03-25 12:59:18 -07:00
Mathieu Tremblay
d1587ef033 Update Tab title from 'Items' to id + ' - Items' to make it easier to differentiate (#1233) 2022-03-11 15:50:44 -05:00
Karthik chakravarthy
5c8016ecd6 Enable phoenix for MPAC by default, and for PROD will enable phoenix only based on flight (#1232)
* Enable phoenix to MPAC by default and for prod based on flight

* phoenix flag setting based of configContext
2022-03-01 13:38:30 -05:00
victor-meng
605117c62d Update autoscale throughput limits (#1229)
* Change minimum autoscale throughput and default autoscale throughput for free tier account to 1000

* Fix unit tests

* Update snapshot

* Fix cassandra add collection panel

* Address comments

* Add feature flag/flight

* Remove console.log
2022-02-25 18:21:58 -08:00
Tanuj Mittal
7a809cd2bc Always schedule memory call (#1231)
* Always schedule memory call

* Memory heart beat issue-fix

Co-authored-by: kcheekuri <kcheekuri@microsoft.com>
2022-02-25 13:41:43 -05:00
Karthik chakravarthy
0a51e24b94 Phoenix UX fixes (#1230) 2022-02-25 13:41:13 -05:00
victor-meng
f36a881679 Add List, Map, and Set column types for cassandra (#1228) 2022-02-18 15:25:47 -08:00
155 changed files with 36370 additions and 1613 deletions

View File

@@ -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 --subscription cosmosdb-portalteam-generaldemo --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}"
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 --subscription cosmosdb-portalteam-generaldemo --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}"
env:
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
endtoendemulator:

View File

@@ -1,4 +1,5 @@
{
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com",
"isTerminalEnabled" : true
"isTerminalEnabled" : true,
"isPhoenixEnabled" : true
}

View File

@@ -1,4 +1,5 @@
{
"JUNO_ENDPOINT": "https://tools.cosmos.azure.com",
"isTerminalEnabled" : false
"isTerminalEnabled" : false,
"isPhoenixEnabled" : false
}

54
images/CarouselImage1.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

66
images/CarouselImage2.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

23
images/Connect_color.svg Normal file
View File

@@ -0,0 +1,23 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_480_185430)">
<path d="M35.4911 6.39638L33.4106 4.31591L37.559 0.167553C37.6611 0.0654497 37.7996 0.00808785 37.944 0.00808785C38.0884 0.00808801 38.2269 0.0654482 38.329 0.167551L39.641 1.47963C39.7431 1.58173 39.8005 1.72021 39.8005 1.86461C39.8005 2.009 39.7431 2.14749 39.641 2.24959L35.4927 6.39795L35.4911 6.39638Z" fill="#32BEDD"/>
<path d="M4.45313 33.6455L6.53988 35.7323L2.44494 39.8272C2.34367 39.9285 2.20632 39.9853 2.06311 39.9853C1.91989 39.9853 1.78254 39.9285 1.68127 39.8272L0.364478 38.5104C0.312974 38.4606 0.271905 38.401 0.243665 38.3351C0.215426 38.2692 0.20058 38.1984 0.199995 38.1267C0.19941 38.0551 0.213098 37.984 0.240258 37.9177C0.267419 37.8514 0.307509 37.7911 0.358193 37.7404L4.45313 33.6455Z" fill="#0078D4"/>
<path d="M5.09381 25.0287C3.78099 26.3415 3.04346 28.1221 3.04346 29.9787C3.04346 31.8353 3.78099 33.6159 5.09381 34.9287C6.40664 36.2415 8.1872 36.9791 10.0438 36.9791C11.9004 36.9791 13.681 36.2415 14.9938 34.9287L18.2027 31.7176L8.30492 21.8198L5.09381 25.0287Z" fill="url(#paint0_linear_480_185430)"/>
<path d="M17.4209 18.1581L17.6157 18.353C17.8133 18.5505 17.9242 18.8185 17.9242 19.0978C17.9242 19.3772 17.8133 19.6451 17.6157 19.8426L13.6009 23.8574L11.918 22.1745L15.9344 18.1581C16.1319 17.9606 16.3998 17.8496 16.6792 17.8496C16.9586 17.8496 17.2265 17.9606 17.424 18.1581L17.4209 18.1581Z" fill="#C3F1FF"/>
<path d="M21.5835 22.32L21.7783 22.5149C21.9759 22.7124 22.0868 22.9803 22.0868 23.2597C22.0868 23.539 21.9759 23.807 21.7783 24.0045L17.7588 28.024L16.0759 26.3411L20.097 22.32C20.2945 22.1225 20.5624 22.0115 20.8418 22.0115C21.1212 22.0115 21.3891 22.1225 21.5866 22.32L21.5835 22.32Z" fill="#C3F1FF"/>
<path d="M20.9363 30.0618L9.87241 18.9979C9.66673 18.7922 9.33327 18.7922 9.12759 18.9979L7.67566 20.4498C7.46999 20.6555 7.46999 20.989 7.67566 21.1946L18.7395 32.2585C18.9452 32.4642 19.2787 32.4642 19.4843 32.2585L20.9363 30.8066C21.1419 30.6009 21.1419 30.2674 20.9363 30.0618Z" fill="#5EA0EF"/>
<path d="M34.9067 14.9711C36.2196 13.6583 36.9571 11.8777 36.9571 10.0211C36.9571 8.1645 36.2196 6.38393 34.9067 5.07111C33.5939 3.75829 31.8134 3.02075 29.9567 3.02075C28.1001 3.02075 26.3196 3.75829 25.0067 5.07111L21.7979 8.28222L31.6956 18.18L34.9067 14.9711Z" fill="#ECF4FD"/>
<path d="M22.5828 21.8375L22.388 21.6426C22.1904 21.4451 22.0795 21.1772 22.0795 20.8978C22.0795 20.6184 22.1904 20.3505 22.388 20.153L26.4075 16.1335L28.092 17.8179L24.0677 21.8422C23.8698 22.0376 23.6025 22.1469 23.3243 22.146C23.0461 22.1451 22.7795 22.0342 22.5828 21.8375Z" fill="#ECF4FD"/>
<path d="M18.4178 17.6802L18.2229 17.4854C18.0254 17.2878 17.9144 17.0199 17.9144 16.7406C17.9144 16.4612 18.0254 16.1933 18.2229 15.9957L22.2409 11.9778L23.9254 13.6623L19.909 17.6787C19.7115 17.8762 19.4435 17.9872 19.1642 17.9872C18.8848 17.9872 18.6169 17.8762 18.4194 17.6787L18.4178 17.6802Z" fill="#ECF4FD"/>
<path d="M19.0642 9.93799L30.1281 21.0019C30.3338 21.2075 30.6672 21.2075 30.8729 21.0019L32.3248 19.5499C32.5305 19.3443 32.5305 19.0108 32.3248 18.8051L21.261 7.74125C21.0553 7.53557 20.7218 7.53557 20.5161 7.74125L19.0642 9.19317C18.8585 9.39885 18.8585 9.73232 19.0642 9.93799Z" fill="#ECF4FD"/>
</g>
<defs>
<linearGradient id="paint0_linear_480_185430" x1="10.6227" y1="21.8179" x2="10.6227" y2="36.9783" gradientUnits="userSpaceOnUse">
<stop stop-color="#5EA0EF"/>
<stop offset="0.997" stop-color="#0078D4"/>
</linearGradient>
<clipPath id="clip0_480_185430">
<rect width="40" height="40" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

8
images/Containers.svg Normal file
View File

@@ -0,0 +1,8 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M24 34.635C24 34.8143 23.9647 34.9918 23.8961 35.1574C23.8275 35.323 23.727 35.4734 23.6002 35.6002C23.4734 35.727 23.323 35.8275 23.1574 35.8961C22.9918 35.9647 22.8143 36 22.635 36H1.365C1.18575 36 1.00825 35.9647 0.842637 35.8961C0.677028 35.8275 0.526551 35.727 0.399799 35.6002C0.273047 35.4734 0.172502 35.323 0.103904 35.1574C0.0353068 34.9918 0 34.8143 0 34.635L0 13.365C0 13.003 0.143812 12.6558 0.399799 12.3998C0.655786 12.1438 1.00298 12 1.365 12H22.635C22.997 12 23.3442 12.1438 23.6002 12.3998C23.8562 12.6558 24 13.003 24 13.365V34.635Z" fill="#005BA1"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M30 28.635C30 28.997 29.8562 29.3442 29.6002 29.6002C29.3442 29.8562 28.997 30 28.635 30H7.365C7.00298 30 6.65579 29.8562 6.3998 29.6002C6.14381 29.3442 6 28.997 6 28.635V7.365C6 7.00298 6.14381 6.65579 6.3998 6.3998C6.65579 6.14381 7.00298 6 7.365 6H28.635C28.997 6 29.3442 6.14381 29.6002 6.3998C29.8562 6.65579 30 7.00298 30 7.365V28.635Z" fill="#5EA0EF"/>
<path d="M22.635 12H6V28.635C6 28.997 6.14381 29.3442 6.3998 29.6002C6.65579 29.8562 7.00298 30 7.365 30H24V13.365C24 13.003 23.8562 12.6558 23.6002 12.3998C23.3442 12.1438 22.997 12 22.635 12Z" fill="#0078D4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M36 22.635C36 22.8143 35.9647 22.9918 35.8961 23.1574C35.8275 23.323 35.727 23.4734 35.6002 23.6002C35.4734 23.727 35.323 23.8275 35.1574 23.8961C34.9918 23.9647 34.8143 24 34.635 24H13.365C13.003 24 12.6558 23.8562 12.3998 23.6002C12.1438 23.3442 12 22.997 12 22.635V1.365C12 1.00298 12.1438 0.655786 12.3998 0.399799C12.6558 0.143812 13.003 0 13.365 0L34.635 0C34.8143 0 34.9918 0.0353068 35.1574 0.103904C35.323 0.172502 35.4734 0.273047 35.6002 0.399799C35.727 0.526551 35.8275 0.677028 35.8961 0.842637C35.9647 1.00825 36 1.18575 36 1.365V22.635Z" fill="#E6E7E8"/>
<path d="M22.635 12H12V22.635C12 22.997 12.1438 23.3442 12.3998 23.6002C12.6558 23.8562 13.003 24 13.365 24H24V13.365C24 13.003 23.8562 12.6558 23.6002 12.3998C23.3442 12.1438 22.997 12 22.635 12Z" fill="#BCBEC0"/>
<path d="M28.635 6H12V12H22.635C22.997 12 23.3442 12.1438 23.6002 12.3998C23.8562 12.6558 24 13.003 24 13.365V24H30V7.365C30 7.00298 29.8562 6.65579 29.6002 6.3998C29.3442 6.14381 28.997 6 28.635 6Z" fill="#D1D3D4"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

3
images/Link_blue.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 6H11V11H0V0H5V1H1V10H10V6ZM11 0V5H10V1.71094L5.35156 6.35156L4.64844 5.64844L9.28906 1H6V0H11Z" fill="#0078D4"/>
</svg>

After

Width:  |  Height:  |  Size: 229 B

23
images/Notebooks.svg Normal file
View File

@@ -0,0 +1,23 @@
<svg width="32" height="36" viewBox="0 0 32 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M30.1809 0H5.6045C5.45725 -2.06304e-07 5.31144 0.0290347 5.17541 0.0854435C5.03939 0.141852 4.91583 0.224528 4.81179 0.32874C4.70775 0.432952 4.62528 0.556655 4.5691 0.692771C4.51292 0.828887 4.48413 0.974745 4.48438 1.122V31.7149C4.48438 32.0121 4.60234 32.2972 4.81236 32.5075C5.02238 32.7178 5.30729 32.8361 5.6045 32.8365H30.1809C30.4781 32.8362 30.7631 32.7179 30.9731 32.5076C31.1832 32.2972 31.3011 32.0121 31.301 31.7149V1.122C31.301 0.824752 31.1831 0.53965 30.973 0.329288C30.763 0.118926 30.4781 0.000496739 30.1809 0V0Z" fill="url(#paint0_linear_1311_8669)"/>
<path d="M16.1495 3.22485H2.17401C1.8837 3.22485 1.60528 3.34018 1.39999 3.54546C1.19471 3.75074 1.07939 4.02917 1.07939 4.31948V34.465C1.07815 34.6087 1.10524 34.7513 1.15912 34.8845C1.21299 35.0178 1.2926 35.1391 1.39338 35.2416C1.49416 35.3441 1.61414 35.4257 1.74648 35.4818C1.87881 35.5379 2.0209 35.5674 2.16464 35.5686H26.3926C26.6829 35.5686 26.9614 35.4533 27.1667 35.248C27.3719 35.0427 27.4873 34.7643 27.4873 34.474V14.5202C27.4874 14.3764 27.4591 14.234 27.4042 14.1011C27.3492 13.9682 27.2686 13.8475 27.1669 13.7457C27.0653 13.644 26.9446 13.5633 26.8117 13.5082C26.6788 13.4532 26.5364 13.4249 26.3926 13.4249H18.3549C18.0642 13.422 17.7865 13.3045 17.5819 13.098C17.3774 12.8915 17.2626 12.6126 17.2625 12.322V4.33185C17.2628 4.03998 17.1477 3.75982 16.9423 3.55245C16.7369 3.34508 16.4579 3.22733 16.166 3.22485H16.1495Z" fill="white"/>
<path d="M16.175 3.16357H1.99513C1.69791 3.16397 1.41301 3.28232 1.20299 3.49262C0.992965 3.70292 0.875 3.98799 0.875 4.2852V34.8781C0.875 35.1753 0.992953 35.4604 1.20296 35.6708C1.41297 35.8812 1.69788 35.9996 1.99513 36.0001H26.5715C26.8687 35.9996 27.1537 35.8812 27.3637 35.6708C27.5737 35.4604 27.6916 35.1753 27.6916 34.8781V14.6307C27.6916 14.3336 27.5736 14.0487 27.3635 13.8387C27.1535 13.6286 26.8686 13.5106 26.5715 13.5106H18.4134C18.1163 13.5106 17.8314 13.3926 17.6213 13.1825C17.4113 12.9724 17.2933 12.6875 17.2933 12.3905V4.2852C17.2924 3.98858 17.1744 3.70432 16.9649 3.49426C16.7555 3.2842 16.4716 3.16535 16.175 3.16357Z" fill="url(#paint1_linear_1311_8669)"/>
<path d="M27.2629 13.7335L16.9065 3.4082V11.8213C16.9035 12.3253 17.1007 12.8097 17.4548 13.1684C17.8088 13.527 18.2907 13.7304 18.7947 13.7338L27.2629 13.7335Z" fill="#83B9F9"/>
<path d="M17.744 16.4092H4.76108C4.47196 16.4092 4.23608 16.5543 4.23608 16.7332V17.5323C4.23608 17.7112 4.47046 17.8559 4.76108 17.8559H17.744C18.0331 17.8559 18.269 17.7112 18.269 17.5323V16.7332C18.2675 16.5543 18.0331 16.4092 17.744 16.4092Z" fill="#83B9F9"/>
<path d="M17.744 20.7498H4.76108C4.47196 20.7498 4.23608 20.8945 4.23608 21.0734V21.8725C4.23608 22.0514 4.47046 22.1965 4.76108 22.1965H17.744C18.0331 22.1965 18.269 22.0514 18.269 21.8725V21.0749C18.2675 20.8945 18.0331 20.7498 17.744 20.7498Z" fill="#83B9F9"/>
<path d="M17.744 25.0906H4.76108C4.47196 25.0906 4.23608 25.2353 4.23608 25.4142V26.2126C4.23608 26.3915 4.47046 26.5366 4.76108 26.5366H17.744C18.0331 26.5366 18.269 26.3915 18.269 26.2126V25.4142C18.2675 25.2353 18.0331 25.0906 17.744 25.0906Z" fill="#83B9F9"/>
<path d="M12.4729 29.4312H4.90167C4.53492 29.4312 4.23755 29.5759 4.23755 29.7548V30.5539C4.23755 30.7328 4.53492 30.8779 4.90167 30.8779H12.4729C12.8397 30.8779 13.137 30.7328 13.137 30.5539V29.7548C13.1374 29.5759 12.8397 29.4312 12.4729 29.4312Z" fill="#83B9F9"/>
<defs>
<linearGradient id="paint0_linear_1311_8669" x1="17.8925" y1="2.19225" x2="17.8925" y2="35.1225" gradientUnits="userSpaceOnUse">
<stop stop-color="#DCDCDC"/>
<stop offset="1" stop-color="#AAAAAA"/>
</linearGradient>
<linearGradient id="paint1_linear_1311_8669" x1="14.2831" y1="5.35582" x2="14.2831" y2="38.2857" gradientUnits="userSpaceOnUse">
<stop stop-color="#0078D7"/>
<stop offset="0.327" stop-color="#0076D4"/>
<stop offset="0.576" stop-color="#0071CA"/>
<stop offset="0.799" stop-color="#0068BA"/>
<stop offset="1" stop-color="#005BA4"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="8" fill="#0078D4"/>
<path d="M9.18652 4.8418V12H7.64844V6.58008C7.5638 6.65495 7.46289 6.72656 7.3457 6.79492C7.23177 6.86003 7.1097 6.92025 6.97949 6.97559C6.84928 7.02767 6.71419 7.07324 6.57422 7.1123C6.43424 7.14811 6.2959 7.17415 6.15918 7.19043V5.8916C6.55957 5.77441 6.93717 5.62467 7.29199 5.44238C7.64681 5.26009 7.96745 5.0599 8.25391 4.8418H9.18652Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 505 B

4
images/Pivot2.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="7.5" stroke="#949494"/>
<path d="M7.21875 10.7207H10.1875V12H5.5293V11.4727C5.5293 11.1146 5.58952 10.7939 5.70996 10.5107C5.8304 10.2243 5.98177 9.96875 6.16406 9.74414C6.34635 9.51628 6.54492 9.31608 6.75977 9.14355C6.97786 8.96777 7.18457 8.8099 7.37988 8.66992C7.58496 8.52344 7.764 8.38346 7.91699 8.25C8.07324 8.11654 8.20345 7.9847 8.30762 7.85449C8.41504 7.72103 8.49479 7.58757 8.54688 7.4541C8.59896 7.31738 8.625 7.17253 8.625 7.01953C8.625 6.72005 8.54036 6.49382 8.37109 6.34082C8.20182 6.18783 7.94303 6.11133 7.59473 6.11133C6.99251 6.11133 6.41634 6.35059 5.86621 6.8291V5.47168C6.47493 5.0778 7.16178 4.88086 7.92676 4.88086C8.28158 4.88086 8.59896 4.92806 8.87891 5.02246C9.16211 5.11361 9.40137 5.24544 9.59668 5.41797C9.79199 5.59049 9.9401 5.80046 10.041 6.04785C10.1452 6.29199 10.1973 6.56543 10.1973 6.86816C10.1973 7.19043 10.1468 7.47689 10.0459 7.72754C9.94824 7.97819 9.81641 8.20605 9.65039 8.41113C9.48763 8.61621 9.29883 8.80501 9.08398 8.97754C8.86914 9.14681 8.64616 9.3112 8.41504 9.4707C8.25879 9.58138 8.10742 9.69206 7.96094 9.80273C7.81771 9.91016 7.69076 10.0176 7.58008 10.125C7.4694 10.2292 7.38151 10.3317 7.31641 10.4326C7.2513 10.5335 7.21875 10.6296 7.21875 10.7207Z" fill="#949494"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="8" fill="#0078D4"/>
<path d="M7.21875 10.7207H10.1875V12H5.5293V11.4727C5.5293 11.1146 5.58952 10.7939 5.70996 10.5107C5.8304 10.2243 5.98177 9.96875 6.16406 9.74414C6.34635 9.51628 6.54492 9.31608 6.75977 9.14355C6.97786 8.96777 7.18457 8.8099 7.37988 8.66992C7.58496 8.52344 7.764 8.38346 7.91699 8.25C8.07324 8.11654 8.20345 7.9847 8.30762 7.85449C8.41504 7.72103 8.49479 7.58757 8.54688 7.4541C8.59896 7.31738 8.625 7.17253 8.625 7.01953C8.625 6.72005 8.54036 6.49382 8.37109 6.34082C8.20182 6.18783 7.94303 6.11133 7.59473 6.11133C6.99251 6.11133 6.41634 6.35059 5.86621 6.8291V5.47168C6.47493 5.0778 7.16178 4.88086 7.92676 4.88086C8.28158 4.88086 8.59896 4.92806 8.87891 5.02246C9.16211 5.11361 9.40137 5.24544 9.59668 5.41797C9.79199 5.59049 9.9401 5.80046 10.041 6.04785C10.1452 6.29199 10.1973 6.56543 10.1973 6.86816C10.1973 7.19043 10.1468 7.47689 10.0459 7.72754C9.94824 7.97819 9.81641 8.20605 9.65039 8.41113C9.48763 8.61621 9.29883 8.80501 9.08398 8.97754C8.86914 9.14681 8.64616 9.3112 8.41504 9.4707C8.25879 9.58138 8.10742 9.69206 7.96094 9.80273C7.81771 9.91016 7.69076 10.0176 7.58008 10.125C7.4694 10.2292 7.38151 10.3317 7.31641 10.4326C7.2513 10.5335 7.21875 10.6296 7.21875 10.7207Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

4
images/Pivot3.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="7.5" stroke="#949494"/>
<path d="M5.69531 11.7705V10.4277C6.16406 10.7695 6.71094 10.9404 7.33594 10.9404C7.72982 10.9404 8.03581 10.8558 8.25391 10.6865C8.47526 10.5173 8.58594 10.2812 8.58594 9.97852C8.58594 9.66602 8.44922 9.42513 8.17578 9.25586C7.9056 9.08659 7.53288 9.00195 7.05762 9.00195H6.4082V7.82031H7.00879C7.92025 7.82031 8.37598 7.51758 8.37598 6.91211C8.37598 6.34245 8.02604 6.05762 7.32617 6.05762C6.85742 6.05762 6.40169 6.20898 5.95898 6.51172V5.25195C6.45052 5.00456 7.02344 4.88086 7.67773 4.88086C8.39388 4.88086 8.95052 5.04199 9.34766 5.36426C9.74805 5.68652 9.94824 6.10482 9.94824 6.61914C9.94824 7.53385 9.48438 8.10677 8.55664 8.33789V8.3623C9.05143 8.42415 9.44206 8.60482 9.72852 8.9043C10.015 9.20052 10.1582 9.5651 10.1582 9.99805C10.1582 10.6523 9.91895 11.1699 9.44043 11.5508C8.96191 11.9316 8.30111 12.1221 7.45801 12.1221C6.73535 12.1221 6.14779 12.0049 5.69531 11.7705Z" fill="#949494"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="8" fill="#0078D4"/>
<path d="M5.69531 11.7705V10.4277C6.16406 10.7695 6.71094 10.9404 7.33594 10.9404C7.72982 10.9404 8.03581 10.8558 8.25391 10.6865C8.47526 10.5173 8.58594 10.2812 8.58594 9.97852C8.58594 9.66602 8.44922 9.42513 8.17578 9.25586C7.9056 9.08659 7.53288 9.00195 7.05762 9.00195H6.4082V7.82031H7.00879C7.92025 7.82031 8.37598 7.51758 8.37598 6.91211C8.37598 6.34245 8.02604 6.05762 7.32617 6.05762C6.85742 6.05762 6.40169 6.20898 5.95898 6.51172V5.25195C6.45052 5.00456 7.02344 4.88086 7.67773 4.88086C8.39388 4.88086 8.95052 5.04199 9.34766 5.36426C9.74805 5.68652 9.94824 6.10482 9.94824 6.61914C9.94824 7.53385 9.48438 8.10677 8.55664 8.33789V8.3623C9.05143 8.42415 9.44206 8.60482 9.72852 8.9043C10.015 9.20052 10.1582 9.5651 10.1582 9.99805C10.1582 10.6523 9.91895 11.1699 9.44043 11.5508C8.96191 11.9316 8.30111 12.1221 7.45801 12.1221C6.73535 12.1221 6.14779 12.0049 5.69531 11.7705Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

4
images/Pivot4.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="7.5" stroke="#949494"/>
<path d="M9.77246 4.99805V9.41211H10.6123V10.5645H9.77246V12H8.36621V10.5645H5.31445V9.3584C5.58464 9.05566 5.86458 8.72526 6.1543 8.36719C6.44401 8.00586 6.72396 7.63477 6.99414 7.25391C7.26432 6.87305 7.51497 6.49056 7.74609 6.10645C7.98047 5.71908 8.17904 5.34961 8.3418 4.99805H9.77246ZM6.69629 9.41211H8.36621V6.96582C8.25228 7.17741 8.12858 7.39225 7.99512 7.61035C7.86165 7.8252 7.72168 8.03841 7.5752 8.25C7.42871 8.45833 7.2806 8.66178 7.13086 8.86035C6.98112 9.05566 6.83626 9.23958 6.69629 9.41211Z" fill="#949494"/>
</svg>

After

Width:  |  Height:  |  Size: 680 B

View File

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="8" fill="#0078D4"/>
<path d="M9.77246 4.99805V9.41211H10.6123V10.5645H9.77246V12H8.36621V10.5645H5.31445V9.3584C5.58464 9.05566 5.86458 8.72526 6.1543 8.36719C6.44401 8.00586 6.72396 7.63477 6.99414 7.25391C7.26432 6.87305 7.51497 6.49056 7.74609 6.10645C7.98047 5.71908 8.17904 5.34961 8.3418 4.99805H9.77246ZM6.69629 9.41211H8.36621V6.96582C8.25228 7.17741 8.12858 7.39225 7.99512 7.61035C7.86165 7.8252 7.72168 8.03841 7.5752 8.25C7.42871 8.45833 7.2806 8.66178 7.13086 8.86035C6.98112 9.05566 6.83626 9.23958 6.69629 9.41211Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 674 B

4
images/Pivot5.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="7.5" stroke="#949494"/>
<path d="M5.81738 11.8193V10.501C6.2959 10.7939 6.80534 10.9404 7.3457 10.9404C7.7526 10.9404 8.06999 10.8444 8.29785 10.6523C8.52897 10.457 8.64453 10.1934 8.64453 9.86133C8.64453 9.16797 8.15462 8.82129 7.1748 8.82129C6.81348 8.82129 6.39681 8.84896 5.9248 8.9043L6.18848 4.99805H9.89453V6.25781H7.36523L7.26758 7.65918C7.51823 7.63965 7.7347 7.62988 7.91699 7.62988C8.63639 7.62988 9.19954 7.81868 9.60645 8.19629C10.0133 8.57389 10.2168 9.08171 10.2168 9.71973C10.2168 10.4261 9.97428 11.0039 9.48926 11.4531C9.00423 11.8991 8.34668 12.1221 7.5166 12.1221C6.84277 12.1221 6.27637 12.0212 5.81738 11.8193Z" fill="#949494"/>
</svg>

After

Width:  |  Height:  |  Size: 779 B

View File

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="8" fill="#0078D4"/>
<path d="M5.81738 11.8193V10.501C6.2959 10.7939 6.80534 10.9404 7.3457 10.9404C7.7526 10.9404 8.06999 10.8444 8.29785 10.6523C8.52897 10.457 8.64453 10.1934 8.64453 9.86133C8.64453 9.16797 8.15462 8.82129 7.1748 8.82129C6.81348 8.82129 6.39681 8.84896 5.9248 8.9043L6.18848 4.99805H9.89453V6.25781H7.36523L7.26758 7.65918C7.51823 7.63965 7.7347 7.62988 7.91699 7.62988C8.63639 7.62988 9.19954 7.81868 9.60645 8.19629C10.0133 8.57389 10.2168 9.08171 10.2168 9.71973C10.2168 10.4261 9.97428 11.0039 9.48926 11.4531C9.00423 11.8991 8.34668 12.1221 7.5166 12.1221C6.84277 12.1221 6.27637 12.0212 5.81738 11.8193Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 773 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.9 10.4C2.86142 9.92426 2.92976 9.44591 3.1 9C3.2722 8.57315 3.54698 8.19534 3.9 7.9L5.6 6.1L10.6 11.1L8.8 12.8C8.50466 13.153 8.12685 13.4278 7.7 13.6C7.29358 13.7932 6.84997 13.8955 6.4 13.9L5.2 13.7L4.3 13.2L1.7 15.7L1 15L3.5 12.4C3.29618 12.1222 3.12819 11.8198 3 11.5C2.92162 11.1388 2.88804 10.7694 2.9 10.4ZM6.4 12.9L7.3 12.7C7.60584 12.5584 7.87842 12.354 8.1 12.1L9.2 11.1L5.6 7.5L4.6 8.6L4 9.4C3.9125 9.72567 3.87872 10.0634 3.9 10.4C3.88428 10.7034 3.91806 11.0074 4 11.3L4.6 12.1L5.4 12.7L6.4 12.9ZM13.2 4.3C13.4038 4.5778 13.5718 4.88018 13.7 5.2C13.8153 5.59037 13.8824 5.99335 13.9 6.4C13.8955 6.84997 13.7932 7.29358 13.6 7.7C13.4278 8.12685 13.153 8.50466 12.8 8.8L11.1 10.6L6.1 5.6L7.9 3.9C8.19534 3.54698 8.57315 3.2722 9 3.1C9.44591 2.92976 9.92426 2.86142 10.4 2.9H11.5L12.4 3.4L15 1L15.7 1.7L13.2 4.3ZM12.1 8.1C12.354 7.87842 12.5584 7.60584 12.7 7.3C12.8142 7.01258 12.8818 6.70875 12.9 6.4C12.8909 6.0577 12.8232 5.71948 12.7 5.4L12.1 4.6L11.3 4.1C11.0284 3.9405 10.7136 3.87053 10.4 3.9H9.4L8.6 4.5L7.5 5.5L11.1 9.1L12.1 8.1Z" fill="#0078D4"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

15
images/PowerShell.svg Normal file
View File

@@ -0,0 +1,15 @@
<svg width="34" height="34" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.944458 10.9331H33.0556V28.836C33.0556 29.1205 32.9425 29.3934 32.7413 29.5946C32.5401 29.7958 32.2672 29.9089 31.9827 29.9089H2.01735C1.7328 29.9089 1.45991 29.7958 1.2587 29.5946C1.05749 29.3934 0.944458 29.1205 0.944458 28.836V10.9331Z" fill="url(#paint0_linear_1497_136679)"/>
<path d="M2.02301 4.09132H31.977C32.1179 4.09132 32.2574 4.11907 32.3876 4.17299C32.5178 4.22691 32.636 4.30594 32.7357 4.40557C32.8353 4.50519 32.9143 4.62347 32.9682 4.75364C33.0222 4.8838 33.0499 5.02332 33.0499 5.16421V10.9329H0.944458V5.16421C0.944456 5.02284 0.972394 4.88286 1.02667 4.75232C1.08094 4.62178 1.16047 4.50326 1.2607 4.40356C1.36093 4.30385 1.47987 4.22494 1.6107 4.17136C1.74152 4.11778 1.88164 4.09058 2.02301 4.09132Z" fill="#0078D4"/>
<path d="M5.33553 15.0706L6.03309 14.371C6.09216 14.3118 6.17235 14.2785 6.25601 14.2783C6.33967 14.2782 6.41995 14.3113 6.47919 14.3704L11.6394 19.5161C11.6982 19.5747 11.7449 19.6444 11.7769 19.7211C11.8088 19.7979 11.8252 19.8801 11.8254 19.9632C11.8255 20.0463 11.8092 20.1286 11.7775 20.2055C11.7458 20.2823 11.6993 20.3521 11.6407 20.4109L10.9444 21.1091L5.3375 15.518C5.27826 15.4589 5.24491 15.3788 5.24479 15.2951C5.24467 15.2114 5.27779 15.1312 5.33687 15.0719L5.33553 15.0706Z" fill="#F2F2F2"/>
<path d="M6.1367 25.5068L5.43717 24.8092C5.37793 24.7501 5.34459 24.67 5.34447 24.5863C5.34435 24.5026 5.37747 24.4224 5.43654 24.3631L10.9463 18.8378L11.6445 19.534C11.7633 19.6525 11.8302 19.8133 11.8305 19.9812C11.8307 20.149 11.7643 20.31 11.6457 20.4289L6.57747 25.5115C6.5184 25.5707 6.43821 25.6041 6.35455 25.6042C6.27089 25.6043 6.19061 25.5712 6.13137 25.5121L6.1367 25.5068Z" fill="#E6E6E6"/>
<path d="M22.1019 23.8755H14.1893C13.8857 23.8755 13.6396 24.1216 13.6396 24.4252V25.2355C13.6396 25.5391 13.8857 25.7852 14.1893 25.7852H22.1019C22.4054 25.7852 22.6515 25.5391 22.6515 25.2355V24.4252C22.6515 24.1216 22.4054 23.8755 22.1019 23.8755Z" fill="#F2F2F2"/>
<defs>
<linearGradient id="paint0_linear_1497_136679" x1="17" y1="29.9089" x2="17" y2="10.9331" gradientUnits="userSpaceOnUse">
<stop stop-color="#32BEDD"/>
<stop offset="0.175" stop-color="#32CAEA"/>
<stop offset="0.41" stop-color="#32D2F2"/>
<stop offset="0.775" stop-color="#32D4F5"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,127 @@
<svg width="68" height="72" viewBox="0 0 68 72" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<circle cx="34" cy="35" r="32" fill="#BFBFBF"/>
<g style="mix-blend-mode:multiply" filter="url(#filter0_ddd_1497_137613)">
<path d="M18 21.3999C18 19.1908 19.7909 17.3999 22 17.3999H43.6L50 23.7999V48.5999C50 50.809 48.2091 52.5999 46 52.5999H22C19.7909 52.5999 18 50.809 18 48.5999V21.3999Z" fill="white"/>
</g>
<g filter="url(#filter1_b_1497_137613)">
<path d="M18 21.3999C18 19.1908 19.7909 17.3999 22 17.3999H43.6L50 23.7999V48.5999C50 50.809 48.2091 52.5999 46 52.5999H22C19.7909 52.5999 18 50.809 18 48.5999V21.3999Z" fill="url(#paint0_linear_1497_137613)"/>
</g>
<g style="mix-blend-mode:hard-light" filter="url(#filter2_ii_1497_137613)">
<path d="M18 21.3999C18 19.1908 19.7909 17.3999 22 17.3999H43.6L50 23.7999V48.5999C50 50.809 48.2091 52.5999 46 52.5999H22C19.7909 52.5999 18 50.809 18 48.5999V21.3999Z" fill="#808080"/>
<path d="M18 21.3999C18 19.1908 19.7909 17.3999 22 17.3999H43.6L50 23.7999V48.5999C50 50.809 48.2091 52.5999 46 52.5999H22C19.7909 52.5999 18 50.809 18 48.5999V21.3999Z" fill="url(#pattern0)" fill-opacity="0.02"/>
</g>
<g style="mix-blend-mode:multiply" filter="url(#filter3_ddd_1497_137613)">
<path d="M43.6006 17.3999L50.0006 23.7999H47.6006C45.3914 23.7999 43.6006 22.009 43.6006 19.7999V17.3999Z" fill="white"/>
</g>
<g filter="url(#filter4_b_1497_137613)">
<path d="M43.6006 17.3999L50.0006 23.7999H47.6006C45.3914 23.7999 43.6006 22.009 43.6006 19.7999V17.3999Z" fill="url(#paint1_linear_1497_137613)"/>
</g>
<g style="mix-blend-mode:hard-light" filter="url(#filter5_ii_1497_137613)">
<path d="M43.6006 17.3999L50.0006 23.7999H47.6006C45.3914 23.7999 43.6006 22.009 43.6006 19.7999V17.3999Z" fill="#808080"/>
<path d="M43.6006 17.3999L50.0006 23.7999H47.6006C45.3914 23.7999 43.6006 22.009 43.6006 19.7999V17.3999Z" fill="url(#pattern1)" fill-opacity="0.02"/>
</g>
<circle cx="33.9994" cy="34.9999" r="9.6" fill="#BFBFBF"/>
<path d="M30.7998 34.9998L33.3598 37.5598L37.8398 32.7598" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M41.6793 34.9998C41.6793 39.2414 38.2409 42.6798 33.9993 42.6798C29.7578 42.6798 26.3193 39.2414 26.3193 34.9998C26.3193 30.7583 29.7578 27.3198 33.9993 27.3198C38.2409 27.3198 41.6793 30.7583 41.6793 34.9998ZM33.9993 42.0398C37.8874 42.0398 41.0393 38.8879 41.0393 34.9998C41.0393 31.1117 37.8874 27.9598 33.9993 27.9598C30.1113 27.9598 26.9593 31.1117 26.9593 34.9998C26.9593 38.8879 30.1113 42.0398 33.9993 42.0398Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.3899 22.3199C30.0451 22.3769 29.7748 22.6471 29.7179 22.9919C29.661 22.6471 29.3907 22.3769 29.0459 22.3199C29.3907 22.263 29.661 21.9928 29.7179 21.6479C29.7748 21.9928 30.0451 22.263 30.3899 22.3199Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.5508 19.4641C30.2404 19.5779 30.781 20.1185 30.8948 20.8081C31.0086 20.1185 31.5491 19.5779 32.2388 19.4641C31.5491 19.3503 31.0086 18.8098 30.8948 18.1201C30.781 18.8098 30.2404 19.3503 29.5508 19.4641Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.20776 22.3199C7.55259 22.3769 7.82285 22.6471 7.87976 22.9919C7.93667 22.6471 8.20693 22.3769 8.55176 22.3199C8.20693 22.263 7.93667 21.9928 7.87976 21.6479C7.82285 21.9928 7.55259 22.263 7.20776 22.3199Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.04688 19.4641C7.35721 19.5779 6.81669 20.1185 6.70287 20.8081C6.58906 20.1185 6.04854 19.5779 5.35887 19.4641C6.04854 19.3503 6.58906 18.8098 6.70287 18.1201C6.81669 18.8098 7.35721 19.3503 8.04688 19.4641Z" fill="white"/>
<defs>
<filter id="filter0_ddd_1497_137613" x="0" y="0.399902" width="68" height="71.2002" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="3"/>
<feGaussianBlur stdDeviation="2.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.25098 0 0 0 0 0.25098 0 0 0 0 0.25098 0 0 0 0.16 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1497_137613"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="9"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.25098 0 0 0 0 0.25098 0 0 0 0 0.25098 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_1497_137613" result="effect2_dropShadow_1497_137613"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="6"/>
<feGaussianBlur stdDeviation="5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.25098 0 0 0 0 0.25098 0 0 0 0 0.25098 0 0 0 0.14 0"/>
<feBlend mode="normal" in2="effect2_dropShadow_1497_137613" result="effect3_dropShadow_1497_137613"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_1497_137613" result="shape"/>
</filter>
<filter id="filter1_b_1497_137613" x="16" y="15.3999" width="36" height="39.2002" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feGaussianBlur in="BackgroundImage" stdDeviation="1"/>
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_1497_137613"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_1497_137613" result="shape"/>
</filter>
<filter id="filter2_ii_1497_137613" x="18" y="17.3999" width="32" height="35.2002" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.94902 0 0 0 0 0.94902 0 0 0 0 0.94902 0 0 0 0.5 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_1497_137613"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="3"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.94902 0 0 0 0 0.94902 0 0 0 0 0.94902 0 0 0 1 0"/>
<feBlend mode="normal" in2="effect1_innerShadow_1497_137613" result="effect2_innerShadow_1497_137613"/>
</filter>
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1.875" height="1.70455">
<use xlink:href="#image0_1497_137613" transform="scale(0.03125 0.0284091)"/>
</pattern>
<filter id="filter3_ddd_1497_137613" x="25.6006" y="0.399902" width="42.4004" height="42.3999" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="3"/>
<feGaussianBlur stdDeviation="2.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.25098 0 0 0 0 0.25098 0 0 0 0 0.25098 0 0 0 0.16 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1497_137613"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="9"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.25098 0 0 0 0 0.25098 0 0 0 0 0.25098 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_1497_137613" result="effect2_dropShadow_1497_137613"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="6"/>
<feGaussianBlur stdDeviation="5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.25098 0 0 0 0 0.25098 0 0 0 0 0.25098 0 0 0 0.14 0"/>
<feBlend mode="normal" in2="effect2_dropShadow_1497_137613" result="effect3_dropShadow_1497_137613"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_1497_137613" result="shape"/>
</filter>
<filter id="filter4_b_1497_137613" x="41.6006" y="15.3999" width="10.4004" height="10.3999" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feGaussianBlur in="BackgroundImage" stdDeviation="1"/>
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_1497_137613"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_1497_137613" result="shape"/>
</filter>
<filter id="filter5_ii_1497_137613" x="43.6006" y="17.3999" width="6.40039" height="6.3999" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.94902 0 0 0 0 0.94902 0 0 0 0 0.94902 0 0 0 0.5 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_1497_137613"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="3"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.94902 0 0 0 0 0.94902 0 0 0 0 0.94902 0 0 0 1 0"/>
<feBlend mode="normal" in2="effect1_innerShadow_1497_137613" result="effect2_innerShadow_1497_137613"/>
</filter>
<pattern id="pattern1" patternContentUnits="objectBoundingBox" width="9.375" height="9.375">
<use xlink:href="#image0_1497_137613" transform="scale(0.15625)"/>
</pattern>
<linearGradient id="paint0_linear_1497_137613" x1="34" y1="52.5999" x2="34" y2="17.3999" gradientUnits="userSpaceOnUse">
<stop stop-color="#F2F2F2" stop-opacity="0.5"/>
<stop offset="1" stop-color="#F2F2F2"/>
</linearGradient>
<linearGradient id="paint1_linear_1497_137613" x1="46.8006" y1="23.7999" x2="46.8006" y2="17.3999" gradientUnits="userSpaceOnUse">
<stop stop-color="#F2F2F2" stop-opacity="0.5"/>
<stop offset="1" stop-color="#F2F2F2"/>
</linearGradient>
<image id="image0_1497_137613" width="60" height="60" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAM9ElEQVRoQ+WbS6iO3RvG1zv/HCcbiRhR2mJESlJbjBwmGwOH0laUHAaEJLXFwCG1FROHgcPEYbRFSaJtRKQYyCltTBxi/v77rc/v+e53efic+fo/k733+z7Peta67+u+7uu+19qNlFKz2Wwmr/fv36e//vorNRqN1NbWll68eJFWrlyZDh06lG/h856enrRq1ap0//79NH78eAbIn3d1deX7+HzcuHH5uSFDhqRdu3alhQsXpkGDBqXDhw/ncXzG927cuDHfV16My707d+5MW7Zsyb9znTx5Mi1atCiPe/r06Wo8xzl48GBavXp1y3Dt7e2pwWp56NSpU3nSXEz8xo0bqbOzM23evDlNnDgxL2Tq1Km1k+3u7k7Lly9PT548SW/evEkPHz7MBtFAdQu8d+9eNsqwYcOyUb2YC9/duXOneteFCxfS3r1708WLFz8a0+eYI89gEO5//fp1unLlSlq3bl3lFNbECrPJuPH58+dpxIgRlRVnzZqVX6K19LjeZIA4MV56/fr1NGDAgDyPjo6O/LyoiBbnfZs2baq8yj19fX1pypQpGRkgQRREBOqciB4dVaKD50Rhb29vdkS1YLyJl7HO4MGD0+XLl7N3gc6RI0eqiTsJ4CECNIgTixPw/hMnTqTFixe3IMTvWKjo0fjRc7t3706zZ8/OhovjAF++8xkd5LOEAWuIKMqQjhN89+5dOn78eMZ/tKyxVOcVJwu0jTPuZyy97SQcUzRhOIwsspxLNKjvNl4/CvQPCPVzx+jv70/Dhw9vCYNGT09Pk/iM+MeaWkUIAV/gFi8GBrY8Sxz698uXL/NnkNvYsWPTnDlzsvEw1s2bN9OBAwfSjBkzEvdJSBDP7du3M19w8TtXJCU9Vs5BD4O0JUuWZNLlAs4gFT7h3aAhQxpPMAm8Ey9wD2yYjGzM9yys9KYLEmJ4yEn7TLzH+OY7Gdh3C1vev2zZsvwxxnFhn+Iax/kc1+QF/2or8z5IZ/To0Wny5MkVAliQ6UYYx3DDyDDvvHnzsievXbuWn/UyhFywiAJtR48e/ZsHOjo6mmPGjKlYkcBnEuREXs53+/fvzy/CyqQMyArvleQUYyjGP+GBxy9dupQhbAqJKOFdkhqxt3Tp0gx7UEdoyCm+M44vWfEew6TUCDq1ysPANl7fwtpYG0P9yazdaGtra0I4JaUbd8ZNhBjW4n48Rkp59OhRTl2ID7xk/kNY4LlJkyZlDnCsFst++IPxZVXfheqD5UuOQRjhfREBhPWsOX/Hjh3p1q1bmf3hAuAMshpdXV1NYgnoMLCyMnpYliXutm/f3pJCmKQpRQka0wKLZTJOKhIPKcN7CZWRI0fmBZaCg3skQaD6+PHjKi8TFuRaWRjCO3/+fItyjA7KkMZbeoeBo3pSUzPpmFejAPF+BUQUIMZsjPlSoGDcuXPn5nDwvihY8CYeYg56TSaPjMwzIMpxTGOm1oxaIU3cQVaICAYDopIMi1aUlyJe65vHhSaeNTfrXYsOoMXCIJlt27alq1evZo/UhZEe5DsLG3OzaUsGVpJqDBELKrhAUKO3t7eJGIg52LSBIGEBwK2MQVHBQMat8GSBa9asqcaMOdkcavEQ41mmLlFmLEeol4a36MCQzI3Uxe979uypNEaultrb23PxwJfKRqSecatVzZHCxXtdJN6bOXNmjnEGJq6ix/gdj1DFYCCIJ5aKpqpoABdYR3LRKHXP+CyG3bdvX34XKM0xzAIV58aXrI3VYWEKCiSaLEjiVyczKESiCHAMIAaBRJXE5Bj72LFj+Z1CG/hj3GfPnuWxuCwzZW0NGHMwnxmjhMi5c+dyWMYFi04MnmNYSieOgTE5mZTiBQxhVLwKgWApjIQ3WZD1rF7XWLEQIMYhp1isx5RXpiyeFQVMHi2P0QkVuCVmB9DBfCLrR/hjCMIshygeVpXwUpnWuDWnxs4GL4OUtCZw9mJQvGbXIzYXuA+CAmJldeQETYexaGBBFC5kEn7yN8al42HBEtWi8tQCyLAk7lsaAN9DDixUIiG+5AVhKHNKiBb4kSPwEuixzuZZ5aIGjfk8xi7jYwAFEQw9bdq0KsXqzJbi4Uf0jEACsBUhpiI8YUcjavA6whGuos1c+yNSZwXpsm78nRUNRmBx9sVMV8Z0LCa4j0ahuiFygWQnpxBm2cNfIypUW8bcv4kK4jl2PCNRxU7ljxAVymLegVGePn2aVVds8LW0eBQIsVBwgU6uLM+IMYp0Cn+L+s/lVJQT+dq2kMwd+1r+zngwMhe/K3+j+lL6RqET05akRjMRovuItMpAjypJQrADSI0aCw7jMXqfOpq45hl+37p1a0ufq+w78Tf3WQkxpjFMakJBlXmdhSAvyRpohvisi1fONvr6+ppQvbm27DQYy7wYZo1dQxaB2ICg4IC6Ij0igglHUVAaKMZfbBZGweF4zENBRNjQf47awWf46TzhgLxgJ+EEzJ0xl5aCwclBGvPnz88vfPXqVe6QELOOESsWPGdBUZaAsSNZVx6a+33euRKrvA+nvX37NlddpCdJr1SROYZLC1owx8/5HQOws/A1iJBRo6W/BxGxHPwWRDQ6OzubeMaghtUspmU7oYq36G4SQxqlbpcA6FL2xfISOFG52EK1X4bxREFdj8wQI+Toczkmc1MolbkctHBNnz49x3VUYRVLl5UJZGWngoeBBpDEILZto4Uj9TMRmvkWG/G+qKycaCwO+MzOSUvh/mFTLcZmWcbyN+Nv2LAh801s8ltdNbq7u5t4jdiQVFRHDG4slOmonAxQw6vuH1nvWgTQRsJQFBEyP0bCqLGJT3HCFcvLWChopG/e5Sy3S7+m5YkUZaEQhdqYCdmWHTp0aO5kfGp30i7nr9ydrCAdC3IW8eDBg5wv3fON8EMEGBdCRZEgOohRvEXJJnKMe4wqm4ucKGwUO3W7FzFemSc9LutquIcGg8qOe4n9s2fP/pMygTS9rP9yjUu4xFqYharWYmcEhZZZOm6GS14xNUWp6HYHrB7jKJZ9piB+ooCsnhAGqrUyLgkD5WHMwyV3MKbziUWEmoH7ESWIEeSuY4FUmhUteVg2jenhd+3UI4YsTGKDXr2u4YR42fZhDeh0LhxaScv+/v4m3YefUXtq3T+pwqo8LDt/yZYjC1iwYEE6c+bMFx9viHu7tn49+CLD1533wDvferzB8tBeeY5ztlp+V7sFlWWeBnolu4K8UkUZbsDVwy8gydZyeX/LcYdG45827Zee6/gv7BCW5zpYNBekWLG0RxrqzlEQ3x5y4UFyLIwH+7rHq6jXwpAEn61fvz5L0oEDB2b1pGwsO6XUudbOP7NTmhsAdvyiTnXikfoVEDAlKooFlydwGAtRYUfDFMGChHCZDWTYsnjwb8VPhG3U/vwuCzM2AmTUqFGV2LAnlp+J57Qc0O5ibNvieSQkXqDqiduacQEMyguoj2NnIhYIHj7hyAIqiIY+hGLlxXimn5hf7a7EZoVHl2JruGxiMJ7FUNXEQ5LZ6I6BXx4g0YJUTbH7EDfUypzIMxhvxYoVLbuEThYDwyE06Lk88KZKgpwwCoxtk93QiOInGj4WL7G533LkAauycz5hwoTKg1iYM5Juf7pgVExdM436k8nTxyIc3IJVKztBJgxSPJjiuwkFDMD35Sm/eEAtanwQw44GIeYxKreP4rZPhjQdj7iJ/L27guXpuF+1K+hxKZzgxh/OscVDSK5du/bvBf/JBbvQjMeZWEhsJrYk63BulMOuHqYDBaCvti8tW8cDYm6nxj5yPJknUaCosLCNANnUOEVdyfagQfaWiHy3pWHcW2IsOcV4jWmQgsH2EqGk17mn2vLhBMDXHu5igK/ZCuF+YyxOtG4rpDzxV9bjEiC9sXgmWiRgEC7LRQmUWCau88E0zyHHDWmMYDzQqfQsZIQPEy4bAHomniLQa/HoBN7lqEXZCCh3DWKOLnNv2QtzbrFvFhsMZKKqeNBysCaTQSXFk3PAxdMAtHacvAornmP2KIMT+JMOuVULLvdpPeMRC3D3kfAMxohNAr3IzygzSWvm0CgIyi5p3PyK7zS2eTfNBOYljyhr4wGZKHvrjlll4VHqYHIg2xj0pNC/ETplH9rOhd60Vfon7lFlp9DT+n84NmwN39LTiv0qvBoLAzwXd+Bl2zqCiN6O8pM9H686eMc+VzyHZQUHlBEP36MEqyMPTORndz3Kfx8w9n7mP3WUXY+PtlpIK7+jyHdLE8N/yz9vxCLffz3yDBhc5GG5iqXVvPG0zZe2Pr/3+DGsj+z7FcePq+KBOtcNKCxTV2zD5ljLPraiwONGQNYyEU/ZuOMnwgUpat/KDkss+n/Ff9RUaclyirxHcX/37t3ctRBeLJZ6lQXZIKh6vR/+oy32gSWzWC19TkXVFQCQFedHovS1Vx1P6cUT+BrwU+dV/gf5nFrpDQ6oIQAAAABJRU5ErkJggg=="/>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,11 @@
<svg width="30" height="34" viewBox="0 0 30 34" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.6457 18.9089H0.67782C0.520018 18.9215 0.361625 18.8893 0.235445 18.819C0.109264 18.7487 0.0249634 18.6456 0 18.5312C0.000766863 18.4748 0.0212497 18.4196 0.0595033 18.3706L14.4179 0.229509C14.4859 0.156313 14.5784 0.0970502 14.6866 0.0573734C14.7949 0.0176966 14.9152 -0.00107025 15.0362 0.00286318H29.1877C29.3456 -0.0102086 29.5043 0.0218054 29.6306 0.0922084C29.757 0.162611 29.8411 0.26595 29.8655 0.380607C29.8655 0.463721 29.823 0.54385 29.7465 0.605364L12.8941 14.7991H29.3222C29.48 14.7865 29.6384 14.8187 29.7646 14.889C29.8907 14.9593 29.975 15.0624 30 15.1768C29.998 15.2265 29.9814 15.2752 29.9516 15.3198C29.9217 15.3645 29.8791 15.4039 29.8267 15.4356L2.51725 33.5994C2.25854 33.6957 0.447568 34.6608 1.33236 33.1952L12.6457 18.9089Z" fill="url(#paint0_radial_480_182017)"/>
<defs>
<radialGradient id="paint0_radial_480_182017" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(15.0039 17.0718) scale(23.6442 13.4446)">
<stop offset="0.196" stop-color="#FFD70F"/>
<stop offset="0.438" stop-color="#FFCB12"/>
<stop offset="0.873" stop-color="#FEAC19"/>
<stop offset="1" stop-color="#FEA11B"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -37,8 +37,8 @@ module.exports = {
global: {
branches: 25,
functions: 25,
lines: 29,
statements: 29,
lines: 28,
statements: 28,
},
},

32212
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
"main": "index.js",
"dependencies": {
"@azure/arm-cosmosdb": "9.1.0",
"@azure/cosmos": "3.10.5",
"@azure/cosmos": "3.16.2",
"@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "1.2.1",
"@azure/ms-rest-nodeauth": "3.0.7",
@@ -91,6 +91,7 @@
"react-notification-system": "0.2.17",
"react-redux": "7.1.3",
"react-splitter-layout": "4.0.0",
"react-youtube": "9.0.1",
"redux": "4.0.4",
"reflect-metadata": "0.1.13",
"rx-jupyter": "5.5.12",
@@ -130,6 +131,7 @@
"@types/sinon": "2.3.3",
"@types/styled-components": "5.1.1",
"@types/underscore": "1.7.36",
"@types/youtube-player": "5.5.6",
"@typescript-eslint/eslint-plugin": "4.22.0",
"@typescript-eslint/parser": "4.22.0",
"@webpack-cli/serve": "1.5.2",
@@ -230,4 +232,4 @@
"prettier": {
"printWidth": 120
}
}
}

View File

@@ -4,7 +4,7 @@
"description": "",
"main": "index.js",
"scripts": {
"deploy": "az webapp up -n cosmos-explorer-preview --subscription cosmosdb-portalteam-generaldemo -g stfaul",
"deploy": "az webapp up --name \"cosmos-explorer-preview\" --subscription \"cosmosdb-portalteam-generaltest-msft\" --resource-group \"stfaul\"",
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
@@ -15,4 +15,4 @@
"http-proxy-middleware": "^1.1.0",
"node-fetch": "^2.6.1"
}
}
}

View File

@@ -1,26 +1,25 @@
{
"databaseId": "SampleDB",
"offerThroughput": 400,
"databaseLevelThroughput": false,
"collectionId": "Persons",
"createNewDatabase": true,
"partitionKey": { "kind": "Hash", "paths": ["/firstname"], "version": 1 },
"data": [
{
"firstname": "Eva",
"age": 44
},
{
"firstname": "Véronique",
"age": 50
},
{
"firstname": "亜妃子",
"age": 5
},
{
"firstname": "John",
"age": 23
}
{ "address": "2007, NE 37TH PL" },
{ "address": "11635, SE MAY CREEK PARK DR" },
{ "address": "8923, 133RD AVE SE" },
{ "address": "1124, N 33RD ST" },
{ "address": "4288, 131ST PL SE" },
{ "address": "10900, SE 66TH ST" },
{ "address": "6260, 139TH AVE NE" },
{ "address": "13427, NE SPRING BLVD" },
{ "address": "13812, NE SPRING BLVD" },
{ "address": "5029, 159TH PL SE" },
{ "address": "8604, 117TH AVE SE" },
{ "address": "1561, 139TH LN NE" },
{ "address": "1575, 139TH CT NE" },
{ "address": "13901, NE 15TH CT" },
{ "address": "16365, NE 12TH PL" },
{ "address": "12226, NE 37TH ST" },
{ "address": "4021, 129TH CT SE" },
{ "address": "1455, 159TH PL NE" },
{ "address": "15825, NE 14TH RD" },
{ "address": "1418, 157TH CT NE" },
{ "address": "889, 131ST PL NE" }
]
}

View File

@@ -99,6 +99,7 @@ export class Flights {
public static readonly PhoenixNotebooks = "phoenixnotebooks";
public static readonly PhoenixFeatures = "phoenixfeatures";
public static readonly NotebooksDownBanner = "notebooksdownbanner";
public static readonly PublicGallery = "publicgallery";
}
export class AfecFeatures {
@@ -353,6 +354,10 @@ export enum ContainerStatusType {
Disconnected = "Disconnected",
}
export enum PoolIdType {
DefaultPoolId = "default",
}
export const EmulatorMasterKey =
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";

View File

@@ -1,4 +1,4 @@
import { ResourceType } from "@azure/cosmos/dist-esm/common/constants";
import { ResourceType } from "@azure/cosmos";
import { Platform, resetConfigContext, updateConfigContext } from "../ConfigContext";
import { updateUserContext } from "../UserContext";
import { endpoint, getTokenFromAuthService, requestPlugin, tokenProvider } from "./CosmosClient";

View File

@@ -1,6 +1,4 @@
import * as Cosmos from "@azure/cosmos";
import { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos";
import { CosmosHeaders } from "@azure/cosmos/dist-esm";
import { configContext, Platform } from "../ConfigContext";
import { userContext } from "../UserContext";
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
@@ -9,7 +7,7 @@ import { getErrorMessage } from "./ErrorHandlingUtils";
const _global = typeof self === "undefined" ? window : self;
export const tokenProvider = async (requestInfo: RequestInfo) => {
export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
const { verb, resourceId, resourceType, headers } = requestInfo;
if (userContext.features.enableAadDataPlane && userContext.aadToken) {
@@ -20,13 +18,13 @@ export const tokenProvider = async (requestInfo: RequestInfo) => {
if (configContext.platform === Platform.Emulator) {
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
await setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
await Cosmos.setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
return decodeURIComponent(headers.authorization);
}
if (userContext.masterKey) {
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
await setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
await Cosmos.setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
return decodeURIComponent(headers.authorization);
}
@@ -89,8 +87,8 @@ let _client: Cosmos.CosmosClient;
export function client(): Cosmos.CosmosClient {
if (_client) return _client;
let _defaultHeaders: CosmosHeaders = {};
_defaultHeaders["x-ms-cosmos-sdk-supported-capabilities"] =
let _defaultHeaders: Cosmos.CosmosHeaders = {};
_defaultHeaders["x-ms-cosmos-sdk-supportedcapabilities"] =
SDKSupportedCapabilities.None | SDKSupportedCapabilities.PartitionMerge;
const options: Cosmos.CosmosClientOptions = {

View File

@@ -25,12 +25,12 @@ const fetchMock = () => {
});
};
const partitionKeyProperty = "pk";
const partitionKeyProperties = ["pk"];
const collection = {
id: () => "testCollection",
rid: "testCollectionrid",
partitionKeyProperty,
partitionKeyProperties,
partitionKey: {
paths: ["/pk"],
kind: "Hash",
@@ -41,7 +41,7 @@ const collection = {
const documentId = ({
partitionKeyHeader: () => "[]",
self: "db/testDB/db/testCollection/docs/testId",
partitionKeyProperty,
partitionKeyProperties,
partitionKey: {
paths: ["/pk"],
kind: "Hash",

View File

@@ -76,7 +76,7 @@ export function queryDocuments(
dba: databaseAccount.name,
pk:
collection && collection.partitionKey && !collection.partitionKey.systemKey
? collection.partitionKeyProperty
? collection.partitionKeyProperties?.[0]
: "",
};
@@ -139,7 +139,7 @@ export function readDocument(
dba: databaseAccount.name,
pk:
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
? documentId.partitionKeyProperty
? documentId.partitionKeyProperties?.[0]
: "",
};
@@ -225,7 +225,7 @@ export function updateDocument(
dba: databaseAccount.name,
pk:
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
? documentId.partitionKeyProperty
? documentId.partitionKeyProperties?.[0]
: "",
};
const endpoint = getFeatureEndpointOrDefault("updateDocument");
@@ -266,7 +266,7 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
dba: databaseAccount.name,
pk:
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
? documentId.partitionKeyProperty
? documentId.partitionKeyProperties?.[0]
: "",
};
const endpoint = getFeatureEndpointOrDefault("deleteDocument");
@@ -299,7 +299,7 @@ export function createMongoCollectionWithProxy(
db: params.databaseId,
coll: params.collectionId,
pk: shardKey,
offerThroughput: params.offerThroughput,
offerThroughput: params.autoPilotMaxThroughput || params.offerThroughput,
cd: params.createNewDatabase,
st: params.databaseLevelThroughput,
is: !!shardKey,
@@ -309,7 +309,6 @@ export function createMongoCollectionWithProxy(
rg: userContext.resourceGroup,
dba: databaseAccount.name,
isAutoPilot: !!params.autoPilotMaxThroughput,
autoPilotThroughput: params.autoPilotMaxThroughput?.toString(),
};
const endpoint = getFeatureEndpointOrDefault("createCollectionWithProxy");

View File

@@ -149,10 +149,10 @@ export class QueriesClient {
const documentId = new DocumentId(
{
partitionKey: QueriesClient.PartitionKey,
partitionKeyProperty: "id",
partitionKeyProperties: ["id"],
} as DocumentsTab,
query,
query.queryName
[query.queryName]
); // TODO: Remove DocumentId's dependency on DocumentsTab
const options: any = { partitionKey: query.resourceId };
return deleteDocument(queriesCollection, documentId)

View File

@@ -1,7 +1,4 @@
import { ContainerResponse, DatabaseResponse } from "@azure/cosmos";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
import { ContainerRequest, ContainerResponse, DatabaseRequest, DatabaseResponse, RequestOptions } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import * as DataModels from "../../Contracts/DataModels";
import { useDatabases } from "../../Explorer/useDatabases";
@@ -27,7 +24,7 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
);
try {
let collection: DataModels.Collection;
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) {
if (userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations) {
if (params.createNewDatabase) {
const createDatabaseParams: DataModels.CreateDatabaseParams = {
autoPilotMaxThroughput: params.autoPilotMaxThroughput,

View File

@@ -1,5 +1,4 @@
import { DatabaseResponse } from "@azure/cosmos";
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
import { DatabaseRequest, DatabaseResponse } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import * as DataModels from "../../Contracts/DataModels";
import { useDatabases } from "../../Explorer/useDatabases";
@@ -26,7 +25,8 @@ export async function createDatabase(params: DataModels.CreateDatabaseParams): P
if (userContext.apiType === "Tables") {
throw new Error("Creating database resources is not allowed for tables accounts");
}
const database: DataModels.Database = await (userContext.authType === AuthType.AAD && !userContext.useSDKOperations
const database: DataModels.Database = await (userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations
? createDatabaseWithARM(params)
: createDatabaseWithSDK(params));

View File

@@ -20,7 +20,11 @@ export async function createStoredProcedure(
): Promise<StoredProcedureDefinition & Resource> {
const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType === "SQL"
) {
try {
const getResponse = await getSqlStoredProcedure(
userContext.subscriptionId,

View File

@@ -14,7 +14,11 @@ export async function createTrigger(
): Promise<TriggerDefinition | SqlTriggerResource> {
const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType === "SQL"
) {
try {
const getResponse = await getSqlTrigger(
userContext.subscriptionId,

View File

@@ -20,7 +20,11 @@ export async function createUserDefinedFunction(
): Promise<UserDefinedFunctionDefinition & Resource> {
const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType === "SQL"
) {
try {
const getResponse = await getSqlUserDefinedFunction(
userContext.subscriptionId,

View File

@@ -12,7 +12,7 @@ import { handleError } from "../ErrorHandlingUtils";
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) {
if (userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations) {
await deleteCollectionWithARM(databaseId, collectionId);
} else {
await client().database(databaseId).container(collectionId).delete();

View File

@@ -12,10 +12,7 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
try {
if (userContext.apiType === "Tables") {
throw new Error("Deleting database resources is not allowed for tables accounts");
}
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) {
if (userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations) {
await deleteDatabaseWithARM(databaseId);
} else {
await client().database(databaseId).delete();

View File

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

View File

@@ -12,7 +12,11 @@ export async function deleteStoredProcedure(
): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType === "SQL"
) {
await deleteSqlStoredProcedure(
userContext.subscriptionId,
userContext.resourceGroup,

View File

@@ -8,7 +8,11 @@ import { handleError } from "../ErrorHandlingUtils";
export async function deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType === "SQL"
) {
await deleteSqlTrigger(
userContext.subscriptionId,
userContext.resourceGroup,

View File

@@ -8,7 +8,11 @@ import { handleError } from "../ErrorHandlingUtils";
export async function deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType === "SQL"
) {
await deleteSqlUserDefinedFunction(
userContext.subscriptionId,
userContext.resourceGroup,

View File

@@ -14,7 +14,11 @@ export const readCollectionOffer = async (params: ReadCollectionOfferParams): Pr
const clearMessage = logConsoleProgress(`Querying offer for collection ${params.collectionId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType !== "Tables"
) {
return await readCollectionOfferWithARM(params.databaseId, params.collectionId);
}

View File

@@ -13,7 +13,11 @@ import { handleError } from "../ErrorHandlingUtils";
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType !== "Tables"
) {
return await readCollectionsWithARM(databaseId);
}

View File

@@ -13,7 +13,11 @@ export const readDatabaseOffer = async (params: ReadDatabaseOfferParams): Promis
const clearMessage = logConsoleProgress(`Querying offer for database ${params.databaseId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType !== "Tables"
) {
return await readDatabaseOfferWithARM(params.databaseId);
}

View File

@@ -13,7 +13,11 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
let databases: DataModels.Database[];
const clearMessage = logConsoleProgress(`Querying databases`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType !== "Tables"
) {
databases = await readDatabasesWithARM();
} else {
const sdkResponse = await client().databases.readAll().fetchAll();

View File

@@ -1,21 +1,29 @@
import { Item } from "@azure/cosmos";
import { Item, RequestOptions } from "@azure/cosmos";
import { CollectionBase } from "../../Contracts/ViewModels";
import DocumentId from "../../Explorer/Tree/DocumentId";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { HttpHeaders } from "../Constants";
import { client } from "../CosmosClient";
import { getEntityName } from "../DocumentUtility";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import DocumentId from "../../Explorer/Tree/DocumentId";
export const readDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<Item> => {
const entityName = getEntityName();
const clearMessage = logConsoleProgress(`Reading ${entityName} ${documentId.id()}`);
try {
const options: RequestOptions =
documentId.partitionKey.kind === "MultiHash"
? {
[HttpHeaders.partitionKey]: documentId.partitionKeyValue,
}
: {};
const response = await client()
.database(collection.databaseId)
.container(collection.id())
.item(documentId.id(), documentId.partitionKeyValue)
.read();
// use undefined if the partitionKeyValue is empty
.item(documentId.id(), documentId.partitionKeyValue?.length === 0 ? undefined : documentId.partitionKeyValue)
.read(options);
return response?.resource;
} catch (error) {

View File

@@ -1,6 +1,6 @@
import { HttpHeaders } from "../Constants";
import { RequestOptions } from "@azure/cosmos";
import { Offer } from "../../Contracts/DataModels";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { HttpHeaders } from "../Constants";
import { client } from "../CosmosClient";
import { parseSDKOfferResponse } from "../OfferUtility";
import { readOffers } from "./readOffers";

View File

@@ -12,7 +12,11 @@ export async function readStoredProcedures(
): Promise<(StoredProcedureDefinition & Resource)[]> {
const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType === "SQL"
) {
const rpResponse = await listSqlStoredProcedures(
userContext.subscriptionId,
userContext.resourceGroup,

View File

@@ -13,7 +13,11 @@ export async function readTriggers(
): Promise<SqlTriggerResource[] | TriggerDefinition[]> {
const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType === "SQL"
) {
const rpResponse = await listSqlTriggers(
userContext.subscriptionId,
userContext.resourceGroup,

View File

@@ -11,9 +11,9 @@ export async function readUserDefinedFunctions(
collectionId: string
): Promise<(UserDefinedFunctionDefinition & Resource)[]> {
const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`);
const { authType, useSDKOperations, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
const { authType, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
try {
if (authType === AuthType.AAD && !useSDKOperations && apiType === "SQL") {
if (authType === AuthType.AAD && !userContext.features.enableSDKoperations && apiType === "SQL") {
const rpResponse = await listSqlUserDefinedFunctions(
subscriptionId,
resourceGroup,

View File

@@ -1,5 +1,4 @@
import { ContainerDefinition } from "@azure/cosmos";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { ContainerDefinition, RequestOptions } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { Collection } from "../../Contracts/DataModels";
import { userContext } from "../../UserContext";
@@ -34,7 +33,11 @@ export async function updateCollection(
const clearMessage = logConsoleProgress(`Updating container ${collectionId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType !== "Tables"
) {
collection = await updateCollectionWithARM(databaseId, collectionId, newCollection);
} else {
const sdkResponse = await client()

View File

@@ -1,10 +1,11 @@
import { Item, RequestOptions } from "@azure/cosmos";
import { HttpHeaders } from "Common/Constants";
import { CollectionBase } from "../../Contracts/ViewModels";
import { Item } from "@azure/cosmos";
import DocumentId from "../../Explorer/Tree/DocumentId";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { getEntityName } from "../DocumentUtility";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import DocumentId from "../../Explorer/Tree/DocumentId";
export const updateDocument = async (
collection: CollectionBase,
@@ -15,11 +16,17 @@ export const updateDocument = async (
const clearMessage = logConsoleProgress(`Updating ${entityName} ${documentId.id()}`);
try {
const options: RequestOptions =
documentId.partitionKey.kind === "MultiHash"
? {
[HttpHeaders.partitionKey]: documentId.partitionKeyValue,
}
: {};
const response = await client()
.database(collection.databaseId)
.container(collection.id())
.item(documentId.id(), documentId.partitionKeyValue)
.replace(newDocument);
.item(documentId.id(), documentId.partitionKeyValue?.length === 0 ? undefined : documentId.partitionKeyValue)
.replace(newDocument, options);
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
return response?.resource;

View File

@@ -1,5 +1,4 @@
import { OfferDefinition } from "@azure/cosmos";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { OfferDefinition, RequestOptions } from "@azure/cosmos";
import { AuthType } from "../../AuthType";
import { Offer, SDKOfferDefinition, UpdateOfferParams } from "../../Contracts/DataModels";
import { userContext } from "../../UserContext";
@@ -57,7 +56,7 @@ export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> =>
const clearMessage = logConsoleProgress(`Updating offer for ${offerResourceText}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) {
if (userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations) {
if (params.collectionId) {
updatedOffer = await updateCollectionOfferWithARM(params);
} else if (userContext.apiType === "Tables") {

View File

@@ -20,9 +20,9 @@ export async function updateStoredProcedure(
): Promise<StoredProcedureDefinition & Resource> {
const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`);
try {
const { authType, useSDKOperations, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
const { authType, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
if (authType === AuthType.AAD && !useSDKOperations && apiType === "SQL") {
if (authType === AuthType.AAD && !userContext.features.enableSDKoperations && apiType === "SQL") {
const getResponse = await getSqlStoredProcedure(
subscriptionId,
resourceGroup,

View File

@@ -13,9 +13,9 @@ export async function updateTrigger(
trigger: SqlTriggerResource
): Promise<SqlTriggerResource | TriggerDefinition> {
const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`);
const { authType, useSDKOperations, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
const { authType, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
try {
if (authType === AuthType.AAD && !useSDKOperations && apiType === "SQL") {
if (authType === AuthType.AAD && !userContext.features.enableSDKoperations && apiType === "SQL") {
const getResponse = await getSqlTrigger(
subscriptionId,
resourceGroup,

View File

@@ -19,9 +19,9 @@ export async function updateUserDefinedFunction(
userDefinedFunction: UserDefinedFunctionDefinition
): Promise<UserDefinedFunctionDefinition & Resource> {
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`);
const { authType, useSDKOperations, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
const { authType, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
try {
if (authType === AuthType.AAD && !useSDKOperations && apiType === "SQL") {
if (authType === AuthType.AAD && !userContext.features.enableSDKoperations && apiType === "SQL") {
const getResponse = await getSqlUserDefinedFunction(
subscriptionId,
resourceGroup,

View File

@@ -40,6 +40,7 @@ export interface ConfigContext {
GITHUB_TEST_ENV_CLIENT_ID: string;
GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it.
isTerminalEnabled: boolean;
isPhoenixEnabled: boolean;
hostedExplorerURL: string;
armAPIVersion?: string;
msalRedirectURI?: string;
@@ -51,10 +52,10 @@ let configContext: Readonly<ConfigContext> = {
allowedParentFrameOrigins: [
`^https:\\/\\/cosmos\\.azure\\.(com|cn|us)$`,
`^https:\\/\\/[\\.\\w]*portal\\.azure\\.(com|cn|us)$`,
`^https:\\/\\/[\\.\\w]*portal\\.microsoftazure.de$`,
`^https:\\/\\/[\\.\\w]*portal\\.microsoftazure\\.de$`,
`^https:\\/\\/[\\.\\w]*ext\\.azure\\.(com|cn|us)$`,
`^https:\\/\\/[\\.\\w]*\\.ext\\.microsoftazure\\.de$`,
`^https://cosmos-db-dataexplorer-germanycentral.azurewebsites.de$`,
`^https:\\/\\/cosmos-db-dataexplorer-germanycentral\\.azurewebsites\\.de$`,
], // Webpack injects this at build time
gitSha: process.env.GIT_SHA,
hostedExplorerURL: "https://cosmos.azure.com/",
@@ -71,6 +72,7 @@ let configContext: Readonly<ConfigContext> = {
JUNO_ENDPOINT: "https://tools.cosmos.azure.com",
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
isTerminalEnabled: false,
isPhoenixEnabled: false,
};
export function resetConfigContext(): void {

View File

@@ -7,6 +7,11 @@ export interface DatabaseAccount {
type: string;
kind: string;
properties: DatabaseAccountExtendedProperties;
systemData?: DatabaseAccountSystemData;
}
export interface DatabaseAccountSystemData {
createdAt: string;
}
export interface DatabaseAccountExtendedProperties {
@@ -433,26 +438,55 @@ export interface NotebookWorkspaceConnectionInfo {
export interface ContainerInfo {
durationLeftInMinutes: number;
notebookServerInfo: NotebookWorkspaceConnectionInfo;
phoenixServerInfo: NotebookWorkspaceConnectionInfo;
status: ContainerStatusType;
}
export interface IProvisionData {
cosmosEndpoint: string;
poolId: string;
}
export interface IContainerData {
forwardingId: string;
}
export interface IDbAccountAllow {
status: number;
message?: string;
type?: string;
}
export interface IResponse<T> {
status: number;
data: T;
}
export interface IPhoenixError {
message: string;
type: string;
}
export interface IMaxAllocationTimeExceeded extends IPhoenixError {
earliestAllocationTimestamp: string;
maxAllocationTimePerDayPerUserInMinutes: string;
}
export interface IMaxDbAccountsPerUserExceeded extends IPhoenixError {
maxSimultaneousConnectionsPerUser: string;
}
export interface IMaxUsersPerDbAccountExceeded extends IPhoenixError {
maxSimultaneousUsersPerDbAccount: string;
}
export interface IPhoenixConnectionInfoResult {
readonly notebookAuthToken?: string;
readonly notebookServerUrl?: string;
readonly phoenixServiceInfo?: IPhoenixServiceInfo;
}
export interface IPhoenixServiceInfo {
readonly authToken?: string;
readonly phoenixServiceUrl?: string;
readonly forwardingId?: string;
}
@@ -531,3 +565,15 @@ export interface ContainerConnectionInfo {
status: ConnectionStatusType;
//need to add ram and rom info
}
export enum PhoenixErrorType {
MaxAllocationTimeExceeded = "MaxAllocationTimeExceeded",
MaxDbAccountsPerUserExceeded = "MaxDbAccountsPerUserExceeded",
MaxUsersPerDbAccountExceeded = "MaxUsersPerDbAccountExceeded",
AllocationValidationResult = "AllocationValidationResult",
RegionNotServicable = "RegionNotServicable",
SubscriptionNotAllowed = "SubscriptionNotAllowed",
UnknownError = "UnknownError",
PhoenixFlightFallback = "PhoenixFlightFallback",
UserMissingPermissionsError = "UserMissingPermissionsError",
}

View File

@@ -34,6 +34,7 @@ export enum MessageTypes {
CreateSparkPool,
RefreshDatabaseAccount,
CloseTab,
OpenQuickstartBlade,
}
export { Versions, ActionContracts, Diagnostics };

View File

@@ -86,6 +86,7 @@ export interface Database extends TreeNode {
offer: ko.Observable<DataModels.Offer>;
isDatabaseExpanded: ko.Observable<boolean>;
isDatabaseShared: ko.Computed<boolean>;
isSampleDB?: boolean;
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
@@ -106,12 +107,13 @@ export interface CollectionBase extends TreeNode {
self: string;
rawDataModel: DataModels.Collection;
partitionKey: DataModels.PartitionKey;
partitionKeyProperty: string;
partitionKeyPropertyHeader: string;
partitionKeyProperties: string[];
partitionKeyPropertyHeaders: string[];
id: ko.Observable<string>;
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
children: ko.ObservableArray<TreeNode>;
isCollectionExpanded: ko.Observable<boolean>;
isSampleCollection?: boolean;
onDocumentDBDocumentsClick(): void;
onNewQueryClick(source: any, event?: MouseEvent, queryText?: string): void;

View File

@@ -39,12 +39,12 @@ export const createDatabaseContextMenu = (container: Explorer, databaseId: strin
const items: TreeNodeMenuItem[] = [
{
iconSrc: AddCollectionIcon,
onClick: () => container.onNewCollectionClicked(databaseId),
onClick: () => container.onNewCollectionClicked({ databaseId }),
label: `New ${getCollectionName()}`,
},
];
if (userContext.apiType !== "Tables") {
if (userContext.apiType !== "Tables" || userContext.features.enableSDKoperations) {
items.push({
iconSrc: DeleteDatabaseIcon,
onClick: () =>

View File

@@ -21,6 +21,7 @@ import {
Text,
} from "@fluentui/react";
import * as React from "react";
import { userContext } from "UserContext";
import { HttpStatusCodes } from "../../../Common/Constants";
import { handleError } from "../../../Common/ErrorHandlingUtils";
import { IGalleryItem, IJunoResponse, IPublicGalleryData, JunoClient } from "../../../Juno/JunoClient";
@@ -148,18 +149,23 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
public render(): JSX.Element {
this.traceViewGallery();
const tabs: GalleryTabInfo[] = [
this.createPublicGalleryTab(
GalleryTab.PublicGallery,
this.state.publicNotebooks,
this.state.isCodeOfConductAccepted
),
this.createSamplesTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks),
];
const tabs: GalleryTabInfo[] = [];
if (userContext.features.publicGallery) {
tabs.push(
this.createPublicGalleryTab(
GalleryTab.PublicGallery,
this.state.publicNotebooks,
this.state.isCodeOfConductAccepted
)
);
}
tabs.push(this.createSamplesTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks));
if (this.props.container) {
tabs.push(this.createFavoritesTab(GalleryTab.Favorites, this.state.favoriteNotebooks));
tabs.push(this.createPublishedNotebooksTab(GalleryTab.Published, this.state.publishedNotebooks));
if (userContext.features.publicGallery) {
tabs.push(this.createPublishedNotebooksTab(GalleryTab.Published, this.state.publishedNotebooks));
}
}
const pivotProps: IPivotProps = {

View File

@@ -8,95 +8,6 @@ exports[`GalleryViewerComponent renders 1`] = `
onLinkClick={[Function]}
selectedKey="OfficialSamples"
>
<PivotItem
headerText="Public gallery"
itemKey="PublicGallery"
key="PublicGallery"
style={
Object {
"marginTop": 20,
}
}
>
<div
className="publicGalleryTabContainer"
>
<Stack
tokens={
Object {
"childrenGap": 10,
}
}
>
<Stack
horizontal={true}
tokens={
Object {
"childrenGap": 20,
"padding": 10,
}
}
wrap={true}
>
<StackItem
grow={true}
>
<StyledSearchBox
onChange={[Function]}
placeholder="Search"
/>
</StackItem>
<StackItem>
<StyledLabelBase>
Sort by
</StyledLabelBase>
</StackItem>
<StackItem
styles={
Object {
"root": Object {
"minWidth": 200,
},
}
}
>
<Dropdown
onChange={[Function]}
options={
Array [
Object {
"key": 0,
"text": "Most viewed",
},
Object {
"key": 1,
"text": "Most downloaded",
},
Object {
"key": 3,
"text": "Most recent",
},
Object {
"key": 2,
"text": "Most favorited",
},
]
}
selectedKey={0}
/>
</StackItem>
<StackItem>
<InfoComponent />
</StackItem>
</Stack>
<StackItem>
<StyledSpinnerBase
size={3}
/>
</StackItem>
</Stack>
</div>
</PivotItem>
<PivotItem
headerText="Official samples"
itemKey="OfficialSamples"

View File

@@ -149,7 +149,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.offer = this.database?.offer();
}
this.state = {
const initialState: SettingsComponentState = {
throughput: undefined,
throughputBaseline: undefined,
autoPilotThroughput: undefined,
@@ -199,6 +199,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
selectedTab: SettingsV2TabTypes.ScaleTab,
};
this.state = {
...initialState,
...this.getBaselineValues(),
...this.getAutoscaleBaselineValues(),
};
this.saveSettingsButton = {
isEnabled: this.isSaveSettingsButtonEnabled,
isVisible: () => {
@@ -225,7 +231,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.loadMongoIndexes();
}
this.setAutoPilotStates();
this.setBaseline();
if (this.props.settingsTab.isActive()) {
useCommandBar.getState().setContextButtons(this.getTabsButtons());
@@ -286,17 +291,24 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
);
};
private setAutoPilotStates = (): void => {
private getAutoscaleBaselineValues = (): Partial<SettingsComponentState> => {
const autoscaleMaxThroughput = this.offer?.autoscaleMaxThroughput;
if (autoscaleMaxThroughput && AutoPilotUtils.isValidAutoPilotThroughput(autoscaleMaxThroughput)) {
this.setState({
return {
isAutoPilotSelected: true,
wasAutopilotOriginallySet: true,
autoPilotThroughput: autoscaleMaxThroughput,
autoPilotThroughputBaseline: autoscaleMaxThroughput,
});
};
}
return {
isAutoPilotSelected: false,
wasAutopilotOriginallySet: false,
autoPilotThroughput: undefined,
autoPilotThroughputBaseline: undefined,
};
};
public hasProvisioningTypeChanged = (): boolean =>
@@ -561,21 +573,25 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
};
public setBaseline = (): void => {
const baselineValues = this.getBaselineValues();
const autoscaleBaselineValues = this.getAutoscaleBaselineValues();
this.setState({ ...baselineValues, ...autoscaleBaselineValues } as SettingsComponentState);
};
private getBaselineValues = (): Partial<SettingsComponentState> => {
const offerThroughput = this.offer?.manualThroughput;
if (!this.isCollectionSettingsTab) {
this.setState({
return {
throughput: offerThroughput,
throughputBaseline: offerThroughput,
});
return;
};
}
const defaultTtl = this.collection.defaultTtl();
let timeToLive: TtlType = this.state.timeToLive;
let timeToLiveSeconds = this.state.timeToLiveSeconds;
let timeToLive: TtlType;
let timeToLiveSeconds: number;
switch (defaultTtl) {
case undefined:
case 0:
@@ -620,7 +636,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
(this.collection.geospatialConfig && this.collection.geospatialConfig()?.type) || GeospatialConfigType.Geometry;
const geoSpatialConfigType = GeospatialConfigType[geospatialConfigTypeString as keyof typeof GeospatialConfigType];
this.setState({
return {
throughput: offerThroughput,
throughputBaseline: offerThroughput,
changeFeedPolicy: changeFeedPolicy,
@@ -643,7 +659,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
conflictResolutionPolicyProcedureBaseline: conflictResolutionPolicyProcedure,
geospatialConfigType: geoSpatialConfigType,
geospatialConfigTypeBaseline: geoSpatialConfigType,
});
};
};
private getTabsButtons = (): CommandButtonComponentProps[] => {

View File

@@ -65,8 +65,8 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
constructor(props: SubSettingsComponentProps) {
super(props);
this.geospatialVisible = userContext.apiType === "SQL";
this.partitionKeyValue = "/" + this.props.collection.partitionKeyProperty;
this.partitionKeyName = userContext.apiType === "Mongo" ? "Shard key" : "Partition key";
this.partitionKeyValue = this.getPartitionKeyValue();
}
componentDidMount(): void {
@@ -291,6 +291,14 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
);
};
private getPartitionKeyValue = (): string => {
if (userContext.apiType === "Mongo") {
return this.props.collection.partitionKeyProperties?.[0] || "";
}
return (this.props.collection.partitionKeyProperties || []).map((property) => "/" + property).join(", ");
};
private getPartitionKeyComponent = (): JSX.Element => (
<Stack {...titleAndInputStackProps}>
{this.getPartitionKeyVisible() && (
@@ -310,7 +318,8 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
if (
userContext.apiType === "Cassandra" ||
userContext.apiType === "Tables" ||
!this.props.collection.partitionKeyProperty ||
!this.props.collection.partitionKeyProperties ||
this.props.collection.partitionKeyProperties.length === 0 ||
(userContext.apiType === "Mongo" && this.props.collection.partitionKey.systemKey)
) {
return false;

View File

@@ -19,7 +19,7 @@ import { Action, ActionModifiers } from "../../../../../Shared/Telemetry/Telemet
import * as TelemetryProcessor from "../../../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../../../UserContext";
import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
import { minAutoPilotThroughput } from "../../../../../Utils/AutoPilotUtils";
import { autoPilotThroughput1K } from "../../../../../Utils/AutoPilotUtils";
import { calculateEstimateNumber, usageInGB } from "../../../../../Utils/PricingUtils";
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
import {
@@ -540,7 +540,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
step={AutoPilotUtils.autoPilotIncrementStep}
value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()}
onChange={this.onAutoPilotThroughputChange}
min={minAutoPilotThroughput}
min={autoPilotThroughput1K}
errorMessage={this.props.throughputError}
/>
{!this.overrideWithProvisionedThroughputSettings() && this.getAutoPilotUsageCost()}

View File

@@ -145,7 +145,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
id="autopilotInput"
key="auto pilot throughput input"
label="Max RU/s"
min={4000}
min={1000}
onChange={[Function]}
required={true}
step={1000}

View File

@@ -39,7 +39,7 @@ export const collection = ({
kind: "hash",
version: 2,
},
partitionKeyProperty: "partitionKey",
partitionKeyProperties: ["partitionKey"],
readSettings: () => {
return;
},

View File

@@ -64,7 +64,9 @@ exports[`SettingsComponent renders 1`] = `
"paths": Array [],
"version": 2,
},
"partitionKeyProperty": "partitionKey",
"partitionKeyProperties": Array [
"partitionKey",
],
"readSettings": [Function],
"uniqueKeyPolicy": Object {},
"usageSizeInKB": [Function],
@@ -138,7 +140,9 @@ exports[`SettingsComponent renders 1`] = `
"paths": Array [],
"version": 2,
},
"partitionKeyProperty": "partitionKey",
"partitionKeyProperties": Array [
"partitionKey",
],
"readSettings": [Function],
"uniqueKeyPolicy": Object {},
"usageSizeInKB": [Function],

View File

@@ -181,7 +181,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
const descriptionElement = (
<Stack>
{labelElement}
<Text id={`${dataFieldName}-text-display`} aria-labelledby={labelId}>
<Text id={`${dataFieldName}-text-display`} aria-labelledby={labelId} style={{ whiteSpace: "pre-line" }}>
{this.props.getTranslation(description.textTKey)}{" "}
{description.link && (
<Link target="_blank" href={description.link.href}>

View File

@@ -27,6 +27,11 @@ exports[`SmartUiComponent disable all inputs 1`] = `
<Text
aria-labelledby="description-label"
id="description-text-display"
style={
Object {
"whiteSpace": "pre-line",
}
}
>
this is an example description text.
@@ -341,6 +346,11 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
<Text
aria-labelledby="description-label"
id="description-text-display"
style={
Object {
"whiteSpace": "pre-line",
}
}
>
this is an example description text.

View File

@@ -5,6 +5,7 @@ const props = {
isDatabase: false,
showFreeTierExceedThroughputTooltip: true,
isSharded: true,
isFreeTier: false,
setThroughputValue: () => jest.fn(),
setIsAutoscale: () => jest.fn(),
setIsThroughputCapExceeded: () => jest.fn(),

View File

@@ -14,7 +14,9 @@ import "./ThroughputInput.less";
export interface ThroughputInputProps {
isDatabase: boolean;
isSharded: boolean;
isFreeTier: boolean;
showFreeTierExceedThroughputTooltip: boolean;
isQuickstart?: boolean;
setThroughputValue: (throughput: number) => void;
setIsAutoscale: (isAutoscale: boolean) => void;
setIsThroughputCapExceeded: (isThroughputCapExceeded: boolean) => void;
@@ -23,15 +25,19 @@ export interface ThroughputInputProps {
export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
isDatabase,
isSharded,
isFreeTier,
showFreeTierExceedThroughputTooltip,
isQuickstart,
setThroughputValue,
setIsAutoscale,
setIsThroughputCapExceeded,
isSharded,
onCostAcknowledgeChange,
}: ThroughputInputProps) => {
const [isAutoscaleSelected, setIsAutoScaleSelected] = useState<boolean>(true);
const [throughput, setThroughput] = useState<number>(AutoPilotUtils.minAutoPilotThroughput);
const [throughput, setThroughput] = useState<number>(
isFreeTier || isQuickstart ? AutoPilotUtils.autoPilotThroughput1K : AutoPilotUtils.autoPilotThroughput4K
);
const [isCostAcknowledged, setIsCostAcknowledged] = useState<boolean>(false);
const [throughputError, setThroughputError] = useState<string>("");
const [totalThroughputUsed, setTotalThroughputUsed] = useState<number>(0);
@@ -151,11 +157,14 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
const handleOnChangeMode = (event: React.ChangeEvent<HTMLInputElement>, mode: string): void => {
if (mode === "Autoscale") {
setThroughput(AutoPilotUtils.minAutoPilotThroughput);
const defaultThroughput = isFreeTier
? AutoPilotUtils.autoPilotThroughput1K
: AutoPilotUtils.autoPilotThroughput4K;
setThroughput(defaultThroughput);
setIsAutoScaleSelected(true);
setThroughputValue(AutoPilotUtils.minAutoPilotThroughput);
setThroughputValue(defaultThroughput);
setIsAutoscale(true);
checkThroughputCap(AutoPilotUtils.minAutoPilotThroughput);
checkThroughputCap(defaultThroughput);
} else {
setThroughput(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
setIsAutoScaleSelected(false);
@@ -219,6 +228,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
</Stack>
<TextField
id="autoscaleRUValueField"
type="number"
styles={{
fieldGroup: { width: 300, height: 27 },
@@ -226,7 +236,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
}}
onChange={(event, newInput?: string) => onThroughputValueChange(newInput)}
step={AutoPilotUtils.autoPilotIncrementStep}
min={AutoPilotUtils.minAutoPilotThroughput}
min={AutoPilotUtils.autoPilotThroughput1K}
value={throughput.toString()}
aria-label="Max request units per second"
required={true}

View File

@@ -3,6 +3,7 @@
exports[`ThroughputInput Pane should render Default properly 1`] = `
<ThroughputInput
isDatabase={false}
isFreeTier={false}
isSharded={true}
onCostAcknowledgeChange={[Function]}
setIsAutoscale={[Function]}
@@ -1636,8 +1637,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
<StyledTextFieldBase
aria-label="Max request units per second"
errorMessage=""
id="autoscaleRUValueField"
key=".0:$.2"
min={4000}
min={1000}
onChange={[Function]}
required={true}
step={1000}
@@ -1659,7 +1661,8 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
aria-label="Max request units per second"
deferredValidationTime={200}
errorMessage=""
min={4000}
id="autoscaleRUValueField"
min={1000}
onChange={[Function]}
required={true}
resizable={true}
@@ -1954,8 +1957,8 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
<input
aria-invalid={false}
className="ms-TextField-field field-64"
id="TextField2"
min={4000}
id="autoscaleRUValueField"
min={1000}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}

View File

@@ -173,6 +173,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
onClick={(event: React.MouseEvent<HTMLDivElement>) => this.onNodeClick(event, node)}
onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) => this.onNodeKeyPress(event, node)}
role="treeitem"
id={node.id}
>
<div
className={`treeNodeHeader ${this.state.isMenuShowing ? "showingMenu" : ""}`}

View File

@@ -137,6 +137,7 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`] = `
<div
className="nodeClassname main12 nodeItem "
id="id"
onClick={[Function]}
onKeyPress={[Function]}
role="treeitem"
@@ -359,6 +360,7 @@ exports[`TreeNodeComponent renders loading icon 1`] = `
exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents separated 1`] = `
<div
className="nodeClassname main12 nodeItem "
id="id"
onClick={[Function]}
onKeyPress={[Function]}
role="treeitem"

View File

@@ -1,5 +1,6 @@
import { createCollection } from "../../Common/dataAccess/createCollection";
import { createDocument } from "../../Common/dataAccess/createDocument";
import { createDocument as createMongoDocument } from "../../Common/MongoProxyClient";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import { userContext } from "../../UserContext";
@@ -28,7 +29,7 @@ export class ContainerSampleGenerator {
dataFileContent = await import(
/* webpackChunkName: "gremlinSampleJsonData" */ "../../../sampleData/gremlinSampleData.json"
);
} else if (userContext.apiType === "SQL") {
} else if (userContext.apiType === "SQL" || userContext.apiType === "Mongo") {
dataFileContent = await import(
/* webpackChunkName: "sqlSampleJsonData" */ "../../../sampleData/sqlSampleData.json"
);
@@ -68,11 +69,10 @@ export class ContainerSampleGenerator {
return database.findCollectionWithId(this.sampleDataFile.collectionId);
}
private async populateContainerAsync(collection: ViewModels.Collection): Promise<void> {
public async populateContainerAsync(collection: ViewModels.Collection, shardKey?: string): Promise<void> {
if (!collection) {
throw new Error("No container to populate");
}
const promises: Q.Promise<any>[] = [];
if (userContext.apiType === "Gremlin") {
// For Gremlin, all queries are executed sequentially, because some queries might be dependent on other queries
@@ -100,7 +100,9 @@ export class ContainerSampleGenerator {
await Promise.all(
this.sampleDataFile.data.map(async (doc) => {
try {
await createDocument(collection, doc);
userContext.apiType === "Mongo"
? await createMongoDocument(collection.databaseId, collection, shardKey, doc)
: await createDocument(collection, doc);
} catch (error) {
NotificationConsoleUtils.logConsoleError(error);
}

View File

@@ -9,19 +9,14 @@ import shallow from "zustand/shallow";
import { AuthType } from "../AuthType";
import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer";
import * as Constants from "../Common/Constants";
import { Areas, ConnectionStatusType, HttpStatusCodes, Notebook } from "../Common/Constants";
import { Areas, ConnectionStatusType, HttpStatusCodes, Notebook, PoolIdType } from "../Common/Constants";
import { readCollection } from "../Common/dataAccess/readCollection";
import { readDatabases } from "../Common/dataAccess/readDatabases";
import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils";
import * as Logger from "../Common/Logger";
import { QueriesClient } from "../Common/QueriesClient";
import * as DataModels from "../Contracts/DataModels";
import {
ContainerConnectionInfo,
IPhoenixConnectionInfoResult,
IProvisionData,
IResponse,
} from "../Contracts/DataModels";
import { ContainerConnectionInfo, IPhoenixServiceInfo, IProvisionData, IResponse } from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels";
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
import { useSidePanel } from "../hooks/useSidePanel";
@@ -190,7 +185,9 @@ export default class Explorer {
useNotebook.getState().setNotebookBasePath(userContext.features.notebookBasePath);
}
this.refreshExplorer();
if (userContext.apiType !== "Postgres") {
this.refreshExplorer();
}
}
public async initiateAndRefreshNotebookList(): Promise<void> {
@@ -298,7 +295,7 @@ export default class Explorer {
db1.id().localeCompare(db2.id())
);
useDatabases.setState({ databases: updatedDatabases });
await this.refreshAndExpandNewDatabases(deltaDatabases.toAdd, currentDatabases);
await this.refreshAndExpandNewDatabases(deltaDatabases.toAdd, updatedDatabases);
} catch (error) {
const errorMessage = getErrorMessage(error);
TelemetryProcessor.traceFailure(
@@ -357,22 +354,21 @@ export default class Explorer {
) {
const provisionData: IProvisionData = {
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
poolId: PoolIdType.DefaultPoolId,
};
const connectionStatus: ContainerConnectionInfo = {
status: ConnectionStatusType.Connecting,
};
useNotebook.getState().setConnectionInfo(connectionStatus);
let connectionInfo;
try {
TelemetryProcessor.traceStart(Action.PhoenixConnection, {
dataExplorerArea: Areas.Notebook,
});
useNotebook.getState().setIsAllocating(true);
const connectionInfo = await this.phoenixClient.allocateContainer(provisionData);
if (connectionInfo.status !== HttpStatusCodes.OK) {
throw new Error(`Received status code: ${connectionInfo?.status}`);
}
if (!connectionInfo?.data?.notebookServerUrl) {
throw new Error(`NotebookServerUrl is invalid!`);
connectionInfo = await this.phoenixClient.allocateContainer(provisionData);
if (!connectionInfo?.data?.phoenixServiceUrl) {
throw new Error(`PhoenixServiceUrl is invalid!`);
}
await this.setNotebookInfo(connectionInfo, connectionStatus);
TelemetryProcessor.traceSuccess(Action.PhoenixConnection, {
@@ -381,11 +377,22 @@ export default class Explorer {
} catch (error) {
TelemetryProcessor.traceFailure(Action.PhoenixConnection, {
dataExplorerArea: Areas.Notebook,
status: error.status,
error: getErrorMessage(error),
errorStack: getErrorStack(error),
});
connectionStatus.status = ConnectionStatusType.Failed;
useNotebook.getState().resetContainerConnection(connectionStatus);
if (error?.status === HttpStatusCodes.Forbidden && error.message) {
useDialog.getState().showOkModalDialog("Connection Failed", `${error.message}`);
} else {
useDialog
.getState()
.showOkModalDialog(
"Connection Failed",
"We are unable to connect to the temporary workspace. Please try again in a few minutes. If the error persists, file a support ticket."
);
}
throw error;
} finally {
useNotebook.getState().setIsAllocating(false);
@@ -397,7 +404,7 @@ export default class Explorer {
}
private async setNotebookInfo(
connectionInfo: IResponse<IPhoenixConnectionInfoResult>,
connectionInfo: IResponse<IPhoenixServiceInfo>,
connectionStatus: DataModels.ContainerConnectionInfo
) {
const containerData = {
@@ -412,8 +419,8 @@ export default class Explorer {
notebookServerEndpoint:
(validateEndpoint(userContext.features.notebookServerUrl, allowedNotebookServerUrls) &&
userContext.features.notebookServerUrl) ||
connectionInfo.data.notebookServerUrl,
authToken: userContext.features.notebookServerToken || connectionInfo.data.notebookAuthToken,
connectionInfo.data.phoenixServiceUrl,
authToken: userContext.features.notebookServerToken || connectionInfo.data.authToken,
forwardingId: connectionInfo.data.forwardingId,
});
this.notebookManager?.notebookClient
@@ -488,8 +495,8 @@ export default class Explorer {
if (connectionInfo?.status !== HttpStatusCodes.OK) {
throw new Error(`Reset Workspace: Received status code- ${connectionInfo?.status}`);
}
if (!connectionInfo?.data?.notebookServerUrl) {
throw new Error(`Reset Workspace: NotebookServerUrl is invalid!`);
if (!connectionInfo?.data?.phoenixServiceUrl) {
throw new Error(`Reset Workspace: PhoenixServiceUrl is invalid!`);
}
if (useNotebook.getState().isPhoenixNotebooks) {
await this.setNotebookInfo(connectionInfo, connectionStatus);
@@ -1110,7 +1117,7 @@ export default class Explorer {
account: userContext.databaseAccount,
container: this,
junoClient: this.notebookManager?.junoClient,
selectedTab: selectedTab || GalleryTabKind.PublicGallery,
selectedTab: selectedTab || GalleryTabKind.OfficialSamples,
notebookUrl,
galleryItem,
isFavorite,
@@ -1120,7 +1127,12 @@ export default class Explorer {
}
}
public async onNewCollectionClicked(databaseId?: string): Promise<void> {
public async onNewCollectionClicked(
options: {
databaseId?: string;
isQuickstart?: boolean;
} = {}
): Promise<void> {
if (userContext.apiType === "Cassandra") {
useSidePanel
.getState()
@@ -1135,7 +1147,7 @@ export default class Explorer {
: await useDatabases.getState().loadDatabaseOffers();
useSidePanel
.getState()
.openSidePanel("New " + getCollectionName(), <AddCollectionPanel explorer={this} databaseId={databaseId} />);
.openSidePanel("New " + getCollectionName(), <AddCollectionPanel explorer={this} {...options} />);
}
}
@@ -1238,7 +1250,10 @@ export default class Explorer {
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
// TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount
const isNotebookEnabled = userContext.features.notebooksDownBanner || useNotebook.getState().isPhoenixNotebooks;
const isNotebookEnabled =
userContext.features.notebooksDownBanner ||
useNotebook.getState().isPhoenixNotebooks ||
useNotebook.getState().isPhoenixFeatures;
useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled);
useNotebook
.getState()

View File

@@ -6,8 +6,9 @@
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
import { useNotebook } from "Explorer/Notebook/useNotebook";
import * as React from "react";
import { userContext } from "UserContext";
import create, { UseStore } from "zustand";
import { StyleConstants } from "../../../Common/Constants";
import { ConnectionStatusType, StyleConstants } from "../../../Common/Constants";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer";
import { useSelectedNode } from "../../useSelectedNode";
@@ -33,6 +34,22 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
const buttons = useCommandBar((state) => state.contextButtons);
const backgroundColor = StyleConstants.BaseLight;
if (userContext.apiType === "Postgres") {
const buttons = CommandBarComponentButtonFactory.createPostgreButtons(container);
return (
<div className="commandBarContainer">
<FluentCommandBar
ariaLabel="Use left and right arrow keys to navigate between commands"
items={CommandBarUtil.convertButton(buttons, backgroundColor)}
styles={{
root: { backgroundColor: backgroundColor },
}}
overflowButtonProps={{ ariaLabel: "More commands" }}
/>
</div>
);
}
const staticButtons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(container, selectedNodeState);
const contextButtons = (buttons || []).concat(
CommandBarComponentButtonFactory.createContextCommandBarButtons(container, selectedNodeState)
@@ -53,7 +70,12 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
if (useNotebook.getState().isPhoenixNotebooks || useNotebook.getState().isPhoenixFeatures) {
const connectionInfo = useNotebook((state) => state.connectionInfo);
if (
(useNotebook.getState().isPhoenixNotebooks || useNotebook.getState().isPhoenixFeatures) &&
connectionInfo?.status !== ConnectionStatusType.Connect
) {
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus(container, "connectionStatus"));
}

View File

@@ -104,6 +104,10 @@ export function createStaticCommandBarButtons(
if (!useNotebook.getState().isPhoenixFeatures) {
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.mongoShellTemporarilyDownMsg);
}
} else if (btn.commandButtonLabel.indexOf("Open Terminal") !== -1) {
if (!useNotebook.getState().isPhoenixFeatures) {
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg);
}
} else if (!useNotebook.getState().isPhoenixNotebooks) {
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg);
}
@@ -309,6 +313,7 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
const label = "New SQL Query";
return {
id: "newQueryBtn",
iconSrc: AddSqlQueryIcon,
iconAlt: label,
onCommandClick: () => {
@@ -323,6 +328,7 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB
} else if (userContext.apiType === "Mongo") {
const label = "New Query";
return {
id: "newQueryBtn",
iconSrc: AddSqlQueryIcon,
iconAlt: label,
onCommandClick: () => {
@@ -409,6 +415,7 @@ function applyNotebooksTemporarilyDownStyle(buttonProps: CommandButtonComponentP
function createNewNotebookButton(container: Explorer): CommandButtonComponentProps {
const label = "New Notebook";
return {
id: "newNotebookBtn",
iconSrc: NewNotebookIcon,
iconAlt: label,
onCommandClick: () => container.onNewNotebookClicked(),
@@ -578,3 +585,18 @@ function createStaticCommandBarButtonsForResourceToken(
return [newSqlQueryBtn, openQueryBtn];
}
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,
};
return [openPostgreShellBtn];
}

View File

@@ -1,14 +1,15 @@
/**
* Notebook container related stuff
*/
import { useDialog } from "Explorer/Controls/Dialog";
import promiseRetry, { AbortError } from "p-retry";
import { PhoenixClient } from "Phoenix/PhoenixClient";
import * as Constants from "../../Common/Constants";
import { ConnectionStatusType, HttpHeaders, HttpStatusCodes, Notebook } from "../../Common/Constants";
import { ConnectionStatusType, HttpHeaders, HttpStatusCodes, Notebook, PoolIdType } from "../../Common/Constants";
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
import * as Logger from "../../Common/Logger";
import * as DataModels from "../../Contracts/DataModels";
import { IPhoenixConnectionInfoResult, IProvisionData, IResponse } from "../../Contracts/DataModels";
import { IPhoenixServiceInfo, IProvisionData, IResponse } from "../../Contracts/DataModels";
import { userContext } from "../../UserContext";
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
@@ -19,6 +20,7 @@ export class NotebookContainerClient {
private isResettingWorkspace: boolean;
private phoenixClient: PhoenixClient;
private retryOptions: promiseRetry.Options;
private scheduleTimerId: NodeJS.Timeout;
constructor(private onConnectionLost: () => void) {
this.phoenixClient = new PhoenixClient();
@@ -27,34 +29,34 @@ export class NotebookContainerClient {
maxTimeout: Notebook.retryAttemptDelayMs,
minTimeout: Notebook.retryAttemptDelayMs,
};
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
if (notebookServerInfo?.notebookServerEndpoint) {
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
} else {
const unsub = useNotebook.subscribe(
(newServerInfo: DataModels.NotebookWorkspaceConnectionInfo) => {
if (newServerInfo?.notebookServerEndpoint) {
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
}
unsub();
},
(state) => state.notebookServerInfo
);
}
this.initHeartbeat(Constants.Notebook.heartbeatDelayMs);
}
/**
* Heartbeat: each ping schedules another ping
*/
private scheduleHeartbeat(delayMs: number): void {
setTimeout(async () => {
const memoryUsageInfo = await this.getMemoryUsage();
useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo);
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
if (notebookServerInfo?.notebookServerEndpoint) {
this.scheduleHeartbeat(Constants.Notebook.heartbeatDelayMs);
}
}, delayMs);
private initHeartbeat(delayMs: number): void {
this.scheduleHeartbeat(delayMs);
useNotebook.subscribe(
() => this.scheduleHeartbeat(delayMs),
(state) => state.notebookServerInfo
);
}
private scheduleHeartbeat(delayMs: number) {
if (this.scheduleTimerId) {
clearInterval(this.scheduleTimerId);
}
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
if (notebookServerInfo?.notebookServerEndpoint) {
this.scheduleTimerId = setInterval(async () => {
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
if (notebookServerInfo?.notebookServerEndpoint) {
const memoryUsageInfo = await this.getMemoryUsage();
useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo);
}
}, delayMs);
}
}
public async getMemoryUsage(): Promise<DataModels.MemoryUsageInfo> {
@@ -127,9 +129,9 @@ export class NotebookContainerClient {
);
}
public async resetWorkspace(): Promise<IResponse<IPhoenixConnectionInfoResult>> {
public async resetWorkspace(): Promise<IResponse<IPhoenixServiceInfo>> {
this.isResettingWorkspace = true;
let response: IResponse<IPhoenixConnectionInfoResult>;
let response: IResponse<IPhoenixServiceInfo>;
try {
response = await this._resetWorkspace();
} catch (error) {
@@ -140,7 +142,7 @@ export class NotebookContainerClient {
return response;
}
private async _resetWorkspace(): Promise<IResponse<IPhoenixConnectionInfoResult>> {
private async _resetWorkspace(): Promise<IResponse<IPhoenixServiceInfo>> {
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
const error = "No server endpoint detected";
@@ -152,12 +154,23 @@ export class NotebookContainerClient {
if (useNotebook.getState().isPhoenixNotebooks) {
const provisionData: IProvisionData = {
cosmosEndpoint: userContext.databaseAccount.properties.documentEndpoint,
poolId: PoolIdType.DefaultPoolId,
};
return await this.phoenixClient.resetContainer(provisionData);
}
return null;
} catch (error) {
Logger.logError(getErrorMessage(error), "NotebookContainerClient/resetWorkspace");
if (error?.status === HttpStatusCodes.Forbidden && error.message) {
useDialog.getState().showOkModalDialog("Connection Failed", `${error.message}`);
} else {
useDialog
.getState()
.showOkModalDialog(
"Connection Failed",
"We are unable to connect to the temporary workspace. Please try again in a few minutes. If the error persists, file a support ticket."
);
}
throw error;
}
}

View File

@@ -303,8 +303,8 @@ export class NotebookContentClient {
private getServerConfig(): ServerConfig {
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
return {
endpoint: notebookServerInfo.notebookServerEndpoint,
token: notebookServerInfo.authToken,
endpoint: notebookServerInfo?.notebookServerEndpoint,
token: notebookServerInfo?.authToken,
crossDomain: true,
};
}

View File

@@ -5,6 +5,7 @@ import Immutable from "immutable";
import * as React from "react";
import { connect } from "react-redux";
import { Dispatch } from "redux";
import * as Logger from "../../../Common/Logger";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
import loadTransform from "../NotebookComponent/loadTransform";
@@ -100,6 +101,7 @@ export class SchemaAnalyzer extends React.Component<SchemaAnalyzerProps, SchemaA
// Only in cases where CosmosMongoKernel runs into an error we get a single output
if (outputs.size === 1) {
traceFailure(Action.SchemaAnalyzerClickAnalyze, data, this.clickAnalyzeTelemetryStartKey);
Logger.logError(`Failed to analyze schema: ${JSON.stringify(data)}`, "SchemaAnalyzer/traceClickAnalyzeComplete");
} else {
traceSuccess(Action.SchemaAnalyzerClickAnalyze, data, this.clickAnalyzeTelemetryStartKey);
}

View File

@@ -4,12 +4,12 @@ import { PhoenixClient } from "Phoenix/PhoenixClient";
import create, { UseStore } from "zustand";
import { AuthType } from "../../AuthType";
import * as Constants from "../../Common/Constants";
import { ConnectionStatusType } from "../../Common/Constants";
import { ConnectionStatusType, HttpStatusCodes } from "../../Common/Constants";
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
import * as Logger from "../../Common/Logger";
import { configContext } from "../../ConfigContext";
import * as DataModels from "../../Contracts/DataModels";
import { ContainerConnectionInfo, ContainerInfo } from "../../Contracts/DataModels";
import { ContainerConnectionInfo, ContainerInfo, PhoenixErrorType } from "../../Contracts/DataModels";
import { useTabs } from "../../hooks/useTabs";
import { IPinnedRepo } from "../../Juno/JunoClient";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
@@ -96,7 +96,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
containerStatus: {
status: undefined,
durationLeftInMinutes: undefined,
notebookServerInfo: undefined,
phoenixServerInfo: undefined,
},
isPhoenixNotebooks: undefined,
isPhoenixFeatures: undefined,
@@ -296,22 +296,30 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
useNotebook.getState().setContainerStatus({
status: undefined,
durationLeftInMinutes: undefined,
notebookServerInfo: undefined,
phoenixServerInfo: undefined,
});
},
setIsRefreshed: (isRefreshed: boolean) => set({ isRefreshed }),
setContainerStatus: (containerStatus: ContainerInfo) => set({ containerStatus }),
getPhoenixStatus: async () => {
if (get().isPhoenixNotebooks === undefined || get().isPhoenixFeatures === undefined) {
let isPhoenix = false;
if (userContext.features.phoenixNotebooks || userContext.features.phoenixFeatures) {
const phoenixClient = new PhoenixClient();
isPhoenix = isPublicInternetAccessAllowed() && (await phoenixClient.isDbAcountWhitelisted());
let isPhoenixNotebooks = false;
let isPhoenixFeatures = false;
const isPublicInternetAllowed = isPublicInternetAccessAllowed();
const phoenixClient = new PhoenixClient();
const dbAccountAllowedInfo = await phoenixClient.getDbAccountAllowedStatus();
if (dbAccountAllowedInfo.status === HttpStatusCodes.OK) {
if (dbAccountAllowedInfo?.type === PhoenixErrorType.PhoenixFlightFallback) {
isPhoenixNotebooks = isPublicInternetAllowed && userContext.features.phoenixNotebooks === true;
isPhoenixFeatures = isPublicInternetAllowed && userContext.features.phoenixFeatures === true;
} else {
isPhoenixNotebooks = isPhoenixFeatures = isPublicInternetAllowed;
}
} else {
isPhoenixNotebooks = isPhoenixFeatures = false;
}
const isPhoenixNotebooks = userContext.features.phoenixNotebooks && isPhoenix;
const isPhoenixFeatures = userContext.features.phoenixFeatures && isPhoenix;
set({ isPhoenixNotebooks: isPhoenixNotebooks });
set({ isPhoenixFeatures: isPhoenixFeatures });
}

View File

@@ -8,23 +8,23 @@ import { CassandraAddCollectionPane } from "../Panes/CassandraAddCollectionPane/
import { SettingsPane } from "../Panes/SettingsPane/SettingsPane";
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
function generateQueryText(action: ActionContracts.OpenQueryTab, partitionKeyProperty: string): string {
function generateQueryText(action: ActionContracts.OpenQueryTab, partitionKeyProperties: string[]): string {
if (!action.query) {
return "SELECT * FROM c";
} else if (action.query.text) {
return action.query.text;
} else if (!!action.query.partitionKeys && action.query.partitionKeys.length > 0) {
} else if (action.query.partitionKeys?.length > 0 && partitionKeyProperties?.length > 0) {
let query = "SELECT * FROM c WHERE";
for (let i = 0; i < action.query.partitionKeys.length; i++) {
const partitionKey = action.query.partitionKeys[i];
if (!partitionKey) {
// null partition key case
query = query.concat(` c.${partitionKeyProperty} = ${action.query.partitionKeys[i]}`);
query = query.concat(` c.${partitionKeyProperties[i]} = ${action.query.partitionKeys[i]}`);
} else if (typeof partitionKey !== "string") {
// Undefined partition key case
query = query.concat(` NOT IS_DEFINED(c.${partitionKeyProperty})`);
query = query.concat(` NOT IS_DEFINED(c.${partitionKeyProperties[i]})`);
} else {
query = query.concat(` c.${partitionKeyProperty} = "${action.query.partitionKeys[i]}"`);
query = query.concat(` c.${partitionKeyProperties[i]} = "${action.query.partitionKeys[i]}"`);
}
if (i !== action.query.partitionKeys.length - 1) {
query = query.concat(" OR");
@@ -109,7 +109,7 @@ function openCollectionTab(
collection.onNewQueryClick(
collection,
undefined,
generateQueryText(action as ActionContracts.OpenQueryTab, collection.partitionKeyProperty)
generateQueryText(action as ActionContracts.OpenQueryTab, collection.partitionKeyProperties)
);
break;
}

View File

@@ -0,0 +1,15 @@
import { shallow } from "enzyme";
import React from "react";
import Explorer from "../Explorer";
import { AddCollectionPanel } from "./AddCollectionPanel";
const props = {
explorer: new Explorer(),
};
describe("AddCollectionPanel", () => {
it("should render Default properly", () => {
const wrapper = shallow(<AddCollectionPanel {...props} />);
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -8,8 +8,10 @@ import {
IconButton,
IDropdownOption,
Link,
ProgressIndicator,
Separator,
Stack,
TeachingBubble,
Text,
TooltipHost,
} from "@fluentui/react";
@@ -20,6 +22,7 @@ import { configContext, Platform } from "ConfigContext";
import * as DataModels from "Contracts/DataModels";
import { SubscriptionType } from "Contracts/SubscriptionType";
import { useSidePanel } from "hooks/useSidePanel";
import { useTeachingBubble } from "hooks/useTeachingBubble";
import React from "react";
import { CollectionCreation } from "Shared/Constants";
import { Action } from "Shared/Telemetry/TelemetryConstants";
@@ -30,6 +33,7 @@ import { isCapabilityEnabled, isServerlessAccount } from "Utils/CapabilityUtils"
import { getUpsellMessage } from "Utils/PricingUtils";
import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent";
import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput";
import { ContainerSampleGenerator } from "../DataSamples/ContainerSampleGenerator";
import Explorer from "../Explorer";
import { useDatabases } from "../useDatabases";
import { PanelFooterComponent } from "./PanelFooterComponent";
@@ -39,6 +43,7 @@ import { PanelLoadingScreen } from "./PanelLoadingScreen";
export interface AddCollectionPanelProps {
explorer: Explorer;
databaseId?: string;
isQuickstart?: boolean;
}
const SharedDatabaseDefault: DataModels.IndexingPolicy = {
@@ -93,6 +98,7 @@ export interface AddCollectionPanelState {
showErrorDetails: boolean;
isExecuting: boolean;
isThroughputCapExceeded: boolean;
teachingBubbleStep: number;
}
export class AddCollectionPanel extends React.Component<AddCollectionPanelProps, AddCollectionPanelState> {
@@ -107,11 +113,11 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
this.state = {
createNewDatabase: userContext.apiType !== "Tables" && !this.props.databaseId,
newDatabaseId: "",
newDatabaseId: props.isQuickstart ? this.getSampleDBName() : "",
isSharedThroughputChecked: this.getSharedThroughputDefault(),
selectedDatabaseId:
userContext.apiType === "Tables" ? CollectionCreation.TablesAPIDefaultDatabase : this.props.databaseId,
collectionId: "",
collectionId: props.isQuickstart ? `Sample${getCollectionName()}` : "",
enableIndexing: true,
isSharded: userContext.apiType !== "Tables",
partitionKey: this.getPartitionKey(),
@@ -124,9 +130,16 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
showErrorDetails: false,
isExecuting: false,
isThroughputCapExceeded: false,
teachingBubbleStep: 0,
};
}
componentDidMount(): void {
if (this.state.teachingBubbleStep === 0 && this.props.isQuickstart) {
this.setState({ teachingBubbleStep: 1 });
}
}
render(): JSX.Element {
const isFirstResourceCreated = useDatabases.getState().isFirstResourceCreated();
@@ -150,6 +163,89 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
/>
)}
{this.state.teachingBubbleStep === 1 && (
<TeachingBubble
headline="Create sample database"
target={"#newDatabaseId"}
calloutProps={{ gapSpace: 16 }}
primaryButtonProps={{ text: "Next", onClick: () => this.setState({ teachingBubbleStep: 2 }) }}
secondaryButtonProps={{ text: "Cancel", onClick: () => this.setState({ teachingBubbleStep: 0 }) }}
onDismiss={() => this.setState({ teachingBubbleStep: 0 })}
footerContent="Step 1 of 4"
>
<Stack>
<Text style={{ color: "white" }}>
Database is the parent of a container. You can create a new database or use an existing one. In this
tutorial we are creating a new database named SampleDB.
</Text>
<Link
style={{ color: "white", fontWeight: 600 }}
target="_blank"
href="https://aka.ms/TeachingbubbleResources"
>
Learn more about resources.
</Link>
</Stack>
</TeachingBubble>
)}
{this.state.teachingBubbleStep === 2 && (
<TeachingBubble
headline="Setting throughput"
target={"#autoscaleRUValueField"}
calloutProps={{ gapSpace: 16 }}
primaryButtonProps={{ text: "Next", onClick: () => this.setState({ teachingBubbleStep: 3 }) }}
secondaryButtonProps={{ text: "Previous", onClick: () => this.setState({ teachingBubbleStep: 1 }) }}
onDismiss={() => this.setState({ teachingBubbleStep: 0 })}
footerContent="Step 2 of 4"
>
<Stack>
<Text style={{ color: "white" }}>
Cosmos DB recommends sharing throughput across database. Autoscale will give you a flexible amount of
throughput based on the max RU/s set (Request Units).
</Text>
<Link style={{ color: "white", fontWeight: 600 }} target="_blank" href="https://aka.ms/teachingbubbleRU">
Learn more about RU/s.
</Link>
</Stack>
</TeachingBubble>
)}
{this.state.teachingBubbleStep === 3 && (
<TeachingBubble
headline="Naming container"
target={"#collectionId"}
calloutProps={{ gapSpace: 16 }}
primaryButtonProps={{ text: "Next", onClick: () => this.setState({ teachingBubbleStep: 4 }) }}
secondaryButtonProps={{ text: "Previous", onClick: () => this.setState({ teachingBubbleStep: 2 }) }}
onDismiss={() => this.setState({ teachingBubbleStep: 0 })}
footerContent="Step 3 of 4"
>
Name your container
</TeachingBubble>
)}
{this.state.teachingBubbleStep === 4 && (
<TeachingBubble
headline="Setting partition key"
target={"#addCollection-partitionKeyValue"}
calloutProps={{ gapSpace: 16 }}
primaryButtonProps={{
text: "Create container",
onClick: () => {
this.setState({ teachingBubbleStep: 5 });
this.submit();
},
}}
secondaryButtonProps={{ text: "Previous", onClick: () => this.setState({ teachingBubbleStep: 2 }) }}
onDismiss={() => this.setState({ teachingBubbleStep: 0 })}
footerContent="Step 4 of 4"
>
Last step - you will need to define a partition key for your collection. /address was chosen for this
particular example. A good partition key should have a wide range of possible value
</TeachingBubble>
)}
<div className="panelMainContent">
<Stack hidden={userContext.apiType === "Tables"}>
<Stack horizontal>
@@ -249,6 +345,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated}
isDatabase={true}
isSharded={this.state.isSharded}
isFreeTier={this.isFreeTierAccount()}
isQuickstart={this.props.isQuickstart}
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
setIsThroughputCapExceeded={(isThroughputCapExceeded: boolean) =>
@@ -483,6 +581,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated}
isDatabase={false}
isSharded={this.state.isSharded}
isFreeTier={this.isFreeTierAccount()}
isQuickstart={this.props.isQuickstart}
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)}
setIsThroughputCapExceeded={(isThroughputCapExceeded: boolean) =>
@@ -686,7 +786,35 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
<PanelFooterComponent buttonLabel="OK" isButtonDisabled={this.state.isThroughputCapExceeded} />
{this.state.isExecuting && <PanelLoadingScreen />}
{this.state.isExecuting && (
<div>
<PanelLoadingScreen />
{this.state.teachingBubbleStep === 5 && (
<TeachingBubble
headline="Creating sample container"
target={"#loadingScreen"}
onDismiss={() => this.setState({ teachingBubbleStep: 0 })}
styles={{ footer: { width: "100%" } }}
>
A sample container is now being created and we are adding sample data for you. It should take about 1
minute.
<br />
<br />
Once the sample container is created, review your sample dataset and follow next steps
<br />
<br />
<ProgressIndicator
styles={{
itemName: { color: "white" },
progressTrack: { backgroundColor: "#A6A6A6" },
progressBar: { background: "white" },
}}
label="Adding sample data set"
/>
</TeachingBubble>
)}
</div>
)}
</form>
);
}
@@ -830,6 +958,9 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
if (userContext.features.partitionKeyDefault2) {
return userContext.apiType === "SQL" ? "/pk" : "pk";
}
if (this.props.isQuickstart) {
return userContext.apiType === "SQL" ? "/address" : "address";
}
return "";
}
@@ -897,8 +1028,11 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
}
private isSynapseLinkEnabled(): boolean {
const { properties } = userContext.databaseAccount;
if (!userContext.databaseAccount) {
return false;
}
const { properties } = userContext.databaseAccount;
if (!properties) {
return false;
}
@@ -994,8 +1128,25 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
document.getElementById("collapsibleSectionContent")?.scrollIntoView();
}
private async submit(event: React.FormEvent<HTMLFormElement>): Promise<void> {
event.preventDefault();
private getSampleDBName(): string {
const existingSampleDBs = useDatabases
.getState()
.databases?.filter((database) => database.id().startsWith("SampleDB"));
const existingSampleDBNames = existingSampleDBs?.map((database) => database.id());
if (!existingSampleDBNames || existingSampleDBNames.length === 0) {
return "SampleDB";
}
let i = 1;
while (existingSampleDBNames.indexOf(`SampleDB${i}`) !== -1) {
i++;
}
return `SampleDB${i}`;
}
private async submit(event?: React.FormEvent<HTMLFormElement>): Promise<void> {
event?.preventDefault();
if (!this.validateInputs()) {
return;
@@ -1044,6 +1195,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
subscriptionQuotaId: userContext.quotaId,
dataExplorerArea: Constants.Areas.ContextualPane,
useIndexingForSharedThroughput: this.state.enableIndexing,
isQuickstart: !!this.props.isQuickstart,
};
const startKey: number = TelemetryProcessor.traceStart(Action.CreateCollection, telemetryData);
@@ -1088,8 +1240,27 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
try {
await createCollection(createCollectionParams);
await this.props.explorer.refreshAllDatabases();
if (this.props.isQuickstart) {
const database = useDatabases.getState().findDatabaseWithId(databaseId);
if (database) {
database.isSampleDB = true;
// populate sample container with sample data
await database.loadCollections();
const collection = database.findCollectionWithId(collectionId);
collection.isSampleCollection = true;
useTeachingBubble.getState().setSampleCollection(collection);
const sampleGenerator = await ContainerSampleGenerator.createSampleGeneratorAsync(this.props.explorer);
await sampleGenerator.populateContainerAsync(collection, partitionKeyString);
// auto-expand sample database + container and show teaching bubble
await database.expandDatabase();
collection.expandCollection();
useDatabases.getState().updateDatabase(database);
useTeachingBubble.getState().setIsSampleDBExpanded(true);
TelemetryProcessor.traceOpen(Action.LaunchUITour);
}
}
this.setState({ isExecuting: false });
this.props.explorer.refreshAllDatabases();
TelemetryProcessor.traceSuccess(Action.CreateCollection, telemetryData, startKey);
useSidePanel.getState().closeSidePanel();
} catch (error) {

View File

@@ -147,7 +147,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
if (isAutoscaleSelected) {
if (!AutoPilotUtils.isValidAutoPilotThroughput(throughput)) {
setFormErrors(
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
`Please enter a value greater than ${AutoPilotUtils.autoPilotThroughput1K} for autopilot throughput`
);
return false;
}
@@ -241,6 +241,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()}
isDatabase={true}
isSharded={databaseCreateNewShared}
isFreeTier={isFreeTierAccount}
setThroughputValue={(newThroughput: number) => (throughput = newThroughput)}
setIsAutoscale={(isAutoscale: boolean) => (isAutoscaleSelected = isAutoscale)}
setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}

View File

@@ -262,6 +262,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
}
isDatabase
isSharded
isFreeTier={isFreeTierAccount}
setThroughputValue={(throughput: number) => (newKeySpaceThroughput = throughput)}
setIsAutoscale={(isAutoscale: boolean) => (isNewKeySpaceAutoscale = isAutoscale)}
setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}
@@ -335,6 +336,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()}
isDatabase={false}
isSharded
isFreeTier={isFreeTierAccount}
setThroughputValue={(throughput: number) => (tableThroughput = throughput)}
setIsAutoscale={(isAutoscale: boolean) => (isTableAutoscale = isAutoscale)}
setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}

View File

@@ -10,7 +10,6 @@ import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUti
import Explorer from "../../Explorer";
import { NotebookContentItem, NotebookContentItemType } from "../../Notebook/NotebookContentItem";
import { useNotebook } from "../../Notebook/useNotebook";
import { ResourceTreeAdapter } from "../../Tree/ResourceTreeAdapter";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent";
@@ -104,11 +103,14 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
switch (location.type) {
case "MyNotebooks":
parent = {
name: ResourceTreeAdapter.MyNotebooksTitle,
name: useNotebook.getState().notebookFolderName,
path: useNotebook.getState().notebookBasePath,
type: NotebookContentItemType.Directory,
};
isGithubTree = false;
if (useNotebook.getState().isPhoenixNotebooks) {
await container.allocateContainer();
}
break;
case "GitHub":

View File

@@ -2,7 +2,7 @@ import React from "react";
import LoadingIndicator_3Squares from "../../../images/LoadingIndicator_3Squares.gif";
export const PanelLoadingScreen: React.FunctionComponent = () => (
<div className="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer">
<div id="loadingScreen" className="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer">
<img className="dataExplorerLoader" src={LoadingIndicator_3Squares} />
</div>
);

View File

@@ -35,6 +35,60 @@ const {
Smallint,
Tinyint,
Timestamp,
// List
List_Ascii,
List_Bigint,
List_Blob,
List_Boolean,
List_Date,
List_Decimal,
List_Double,
List_Float,
List_Int,
List_Text,
List_Timestamp,
List_Uuid,
List_Varchar,
List_Varint,
List_Inet,
List_Smallint,
List_Tinyint,
// Map
Map_Ascii,
Map_Bigint,
Map_Blob,
Map_Boolean,
Map_Date,
Map_Decimal,
Map_Double,
Map_Float,
Map_Int,
Map_Text,
Map_Timestamp,
Map_Uuid,
Map_Varchar,
Map_Varint,
Map_Inet,
Map_Smallint,
Map_Tinyint,
// Set
Set_Ascii,
Set_Bigint,
Set_Blob,
Set_Boolean,
Set_Date,
Set_Decimal,
Set_Double,
Set_Float,
Set_Int,
Set_Text,
Set_Timestamp,
Set_Uuid,
Set_Varchar,
Set_Varint,
Set_Inet,
Set_Smallint,
Set_Tinyint,
} = TableConstants.CassandraType;
export const cassandraOptions = [
{ key: Text, text: Text },
@@ -54,6 +108,60 @@ export const cassandraOptions = [
{ key: Smallint, text: Smallint },
{ key: Tinyint, text: Tinyint },
{ key: Timestamp, text: Timestamp },
// List
{ key: List_Ascii, text: List_Ascii },
{ key: List_Bigint, text: List_Bigint },
{ key: List_Blob, text: List_Blob },
{ key: List_Boolean, text: List_Boolean },
{ key: List_Date, text: List_Date },
{ key: List_Decimal, text: List_Decimal },
{ key: List_Double, text: List_Double },
{ key: List_Float, text: List_Float },
{ key: List_Int, text: List_Int },
{ key: List_Text, text: List_Text },
{ key: List_Timestamp, text: List_Timestamp },
{ key: List_Uuid, text: List_Uuid },
{ key: List_Varchar, text: List_Varchar },
{ key: List_Varint, text: List_Varint },
{ key: List_Inet, text: List_Inet },
{ key: List_Smallint, text: List_Smallint },
{ key: List_Tinyint, text: List_Tinyint },
// Map
{ key: Map_Ascii, text: Map_Ascii },
{ key: Map_Bigint, text: Map_Bigint },
{ key: Map_Blob, text: Map_Blob },
{ key: Map_Boolean, text: Map_Boolean },
{ key: Map_Date, text: Map_Date },
{ key: Map_Decimal, text: Map_Decimal },
{ key: Map_Double, text: Map_Double },
{ key: Map_Float, text: Map_Float },
{ key: Map_Int, text: Map_Int },
{ key: Map_Text, text: Map_Text },
{ key: Map_Timestamp, text: Map_Timestamp },
{ key: Map_Uuid, text: Map_Uuid },
{ key: Map_Varchar, text: Map_Varchar },
{ key: Map_Varint, text: Map_Varint },
{ key: Map_Inet, text: Map_Inet },
{ key: Map_Smallint, text: Map_Smallint },
{ key: Map_Tinyint, text: Map_Tinyint },
// Set
{ key: Set_Ascii, text: Set_Ascii },
{ key: Set_Bigint, text: Set_Bigint },
{ key: Set_Blob, text: Set_Blob },
{ key: Set_Boolean, text: Set_Boolean },
{ key: Set_Date, text: Set_Date },
{ key: Set_Decimal, text: Set_Decimal },
{ key: Set_Double, text: Set_Double },
{ key: Set_Float, text: Set_Float },
{ key: Set_Int, text: Set_Int },
{ key: Set_Text, text: Set_Text },
{ key: Set_Timestamp, text: Set_Timestamp },
{ key: Set_Uuid, text: Set_Uuid },
{ key: Set_Varchar, text: Set_Varchar },
{ key: Set_Varint, text: Set_Varint },
{ key: Set_Inet, text: Set_Inet },
{ key: Set_Smallint, text: Set_Smallint },
{ key: Set_Tinyint, text: Set_Tinyint },
];
export const imageProps: IImageProps = {

View File

@@ -0,0 +1,427 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AddCollectionPanel should render Default properly 1`] = `
<form
className="panelFormWrapper"
onSubmit={[Function]}
>
<div
className="panelMainContent"
>
<Stack
hidden={false}
>
<Stack
horizontal={true}
>
<span
className="mandatoryStar"
>
* 
</span>
<Text
className="panelTextBold"
variant="small"
>
Database
id
</Text>
<StyledTooltipHostBase
content="A database is analogous to a namespace. It is the unit of management for a set of containers."
directionalHint={4}
>
<Icon
className="panelInfoIcon"
iconName="Info"
tabIndex={0}
/>
</StyledTooltipHostBase>
</Stack>
<Stack
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"
>
Create new
</span>
<input
aria-checked={false}
aria-label="Use existing database"
checked={false}
className="panelRadioBtn"
name="databaseType"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="panelRadioBtnLabel"
>
Use existing
</span>
</Stack>
<Stack
className="panelGroupSpacing"
>
<input
aria-label="New database id"
aria-required={true}
autoComplete="off"
autoFocus={true}
className="panelTextField"
id="newDatabaseId"
name="newDatabaseId"
onChange={[Function]}
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"
placeholder="Type a new database id"
required={true}
size={40}
tabIndex={0}
title="May not end with space nor contain characters '\\\\' '/' '#' '?'"
type="text"
value=""
/>
<Stack
horizontal={true}
>
<StyledCheckboxBase
checked={true}
label="Share throughput across containers"
onChange={[Function]}
styles={
Object {
"checkbox": Object {
"height": 12,
"width": 12,
},
"label": Object {
"alignItems": "center",
"padding": 0,
},
"text": Object {
"fontSize": 12,
},
}
}
/>
<StyledTooltipHostBase
content="Throughput configured at the database level will be shared across all containers within the database."
directionalHint={4}
>
<Icon
className="panelInfoIcon"
iconName="Info"
tabIndex={0}
/>
</StyledTooltipHostBase>
</Stack>
<ThroughputInput
isDatabase={true}
isSharded={true}
onCostAcknowledgeChange={[Function]}
setIsAutoscale={[Function]}
setIsThroughputCapExceeded={[Function]}
setThroughputValue={[Function]}
/>
</Stack>
<Separator
className="panelSeparator"
/>
</Stack>
<Stack>
<Stack
horizontal={true}
>
<span
className="mandatoryStar"
>
* 
</span>
<Text
className="panelTextBold"
variant="small"
>
Container id
</Text>
<StyledTooltipHostBase
content="Unique identifier for the container and used for id-based routing through REST and all SDKs."
directionalHint={4}
>
<Icon
className="panelInfoIcon"
iconName="Info"
tabIndex={0}
/>
</StyledTooltipHostBase>
</Stack>
<input
aria-label="Container id"
aria-required={true}
autoComplete="off"
className="panelTextField"
id="collectionId"
name="collectionId"
onChange={[Function]}
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"
placeholder="e.g., Container1"
required={true}
size={40}
title="May not end with space nor contain characters '\\\\' '/' '#' '?'"
type="text"
value=""
/>
</Stack>
<Stack>
<Stack
horizontal={true}
>
<span
className="mandatoryStar"
>
* 
</span>
<Text
className="panelTextBold"
variant="small"
>
Partition key
</Text>
<StyledTooltipHostBase
content="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."
directionalHint={4}
>
<Icon
className="panelInfoIcon"
iconName="Info"
tabIndex={0}
/>
</StyledTooltipHostBase>
</Stack>
<Text
aria-label="pkDescription"
variant="small"
/>
<input
aria-label="Partition key"
aria-required={true}
className="panelTextField"
id="addCollection-partitionKeyValue"
onChange={[Function]}
pattern=".*"
placeholder="e.g., /address/zipCode"
required={true}
size={40}
title=""
type="text"
value=""
/>
</Stack>
<Stack>
<Stack
horizontal={true}
>
<Text
className="panelTextBold"
variant="small"
>
Unique keys
</Text>
<StyledTooltipHostBase
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."
directionalHint={4}
>
<Icon
className="panelInfoIcon"
iconName="Info"
tabIndex={0}
/>
</StyledTooltipHostBase>
</Stack>
<CustomizedActionButton
iconProps={
Object {
"iconName": "Add",
}
}
onClick={[Function]}
styles={
Object {
"label": Object {
"fontSize": 12,
},
"root": Object {
"padding": 0,
},
}
}
>
Add unique key
</CustomizedActionButton>
</Stack>
<Stack
className="panelGroupSpacing"
>
<Stack
horizontal={true}
>
<Text
className="panelTextBold"
variant="small"
>
Analytical store
</Text>
<StyledTooltipHostBase
content={
<Text
variant="small"
>
Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads.
<StyledLinkBase
href="https://aka.ms/analytical-store-overview"
target="_blank"
>
Learn more
</StyledLinkBase>
</Text>
}
directionalHint={4}
>
<Icon
className="panelInfoIcon"
iconName="Info"
tabIndex={0}
/>
</StyledTooltipHostBase>
</Stack>
<Stack
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"
>
On
</span>
<input
aria-checked={true}
aria-label="Disable analytical store"
checked={true}
className="panelRadioBtn"
disabled={true}
id="disableAnalyticalStoreBtn"
name="analyticalStore"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="panelRadioBtnLabel"
>
Off
</span>
</Stack>
<Stack
className="panelGroupSpacing"
>
<Text
variant="small"
>
Azure Synapse Link is required for creating an analytical store
container
. Enable Synapse Link for this Cosmos DB account.
<StyledLinkBase
href="https://aka.ms/cosmosdb-synapselink"
target="_blank"
>
Learn more
</StyledLinkBase>
</Text>
<CustomizedDefaultButton
onClick={[Function]}
style={
Object {
"height": 27,
"width": 80,
}
}
styles={
Object {
"label": Object {
"fontSize": 12,
},
}
}
text="Enable"
/>
</Stack>
</Stack>
<CollapsibleSectionComponent
isExpandedByDefault={false}
onExpand={[Function]}
title="Advanced"
>
<Stack
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>
</CollapsibleSectionComponent>
</div>
<PanelFooterComponent
buttonLabel="OK"
isButtonDisabled={false}
/>
</form>
`;

View File

@@ -0,0 +1,103 @@
import { DefaultButton, IconButton, Image, Modal, PrimaryButton, Stack, Text } from "@fluentui/react";
import { useCarousel } from "hooks/useCarousel";
import React, { useState } from "react";
import Youtube from "react-youtube";
import { Action } from "Shared/Telemetry/TelemetryConstants";
import { traceSuccess } from "Shared/Telemetry/TelemetryProcessor";
import { userContext } from "UserContext";
import Image1 from "../../../images/CarouselImage1.svg";
import Image2 from "../../../images/CarouselImage2.svg";
interface QuickstartCarouselProps {
isOpen: boolean;
}
export const QuickstartCarousel: React.FC<QuickstartCarouselProps> = ({
isOpen,
}: QuickstartCarouselProps): JSX.Element => {
const [page, setPage] = useState<number>(1);
return (
<Modal
styles={{ main: { width: 640 } }}
isOpen={isOpen && page < 4}
onDismissed={() => userContext.apiType === "SQL" && useCarousel.getState().setShowCoachMark(true)}
>
<Stack>
<Stack horizontal horizontalAlign="space-between" style={{ padding: 16 }}>
<Text variant="xLarge">{getHeaderText(page)}</Text>
<IconButton iconProps={{ iconName: "Cancel" }} onClick={() => setPage(4)} />
</Stack>
{getContent(page)}
<Text variant="medium" style={{ padding: "0 16px" }}>
{getDescriptionText(page)}
</Text>
<Stack horizontal horizontalAlign="end">
{page !== 1 && (
<DefaultButton text="Previous" style={{ margin: "16px 8px 16px 0" }} onClick={() => setPage(page - 1)} />
)}
<PrimaryButton
style={{ margin: "16px 16px 16px 0" }}
text={page === 3 ? "Finish" : "Next"}
onClick={() => {
if (
userContext.apiType === "Cassandra" ||
userContext.apiType === "Tables" ||
userContext.apiType === "Gremlin"
) {
setPage(page + 2);
} else {
if (page === 3 && userContext.apiType === "SQL") {
useCarousel.getState().setShowCoachMark(true);
}
setPage(page + 1);
}
if (page === 3) {
traceSuccess(Action.CompleteCarousel);
}
}}
/>
</Stack>
</Stack>
</Modal>
);
};
const getHeaderText = (page: number): string => {
switch (page) {
case 1:
return "Welcome! What is Cosmos DB?";
case 2:
return "Get Started with Sample Data";
case 3:
return "Connect to your database";
default:
return "";
}
};
const getContent = (page: number): JSX.Element => {
switch (page) {
case 1:
return <Youtube videoId="Jvgh64rvdXU" onPlay={() => traceSuccess(Action.PlayCarouselVideo)} />;
case 2:
return <Image style={{ width: 640 }} src={Image1} />;
case 3:
return <Image style={{ width: 640 }} src={Image2} />;
default:
return <></>;
}
};
const getDescriptionText = (page: number): string => {
switch (page) {
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.";
case 3:
return "Already have an existing app? Connect your database to an app, or tooling of your choice from Data Explorer.";
default:
return "";
}
};

View File

@@ -0,0 +1,290 @@
import {
DefaultButton,
Icon,
IconButton,
Image,
IPivotItemProps,
Pivot,
PivotItem,
PrimaryButton,
Stack,
Text,
TextField,
} from "@fluentui/react";
import React, { useState } from "react";
import Youtube from "react-youtube";
import Pivot1SelectedIcon from "../../../images/Pivot1_selected.svg";
import Pivot2Icon from "../../../images/Pivot2.svg";
import Pivot2SelectedIcon from "../../../images/Pivot2_selected.svg";
import Pivot3Icon from "../../../images/Pivot3.svg";
import Pivot3SelectedIcon from "../../../images/Pivot3_selected.svg";
import Pivot4Icon from "../../../images/Pivot4.svg";
import Pivot4SelectedIcon from "../../../images/Pivot4_selected.svg";
import Pivot5Icon from "../../../images/Pivot5.svg";
import Pivot5SelectedIcon from "../../../images/Pivot5_selected.svg";
import CompleteIcon from "../../../images/QuickstartComplete.svg";
import { ReactTabKind, useTabs } from "../../hooks/useTabs";
enum GuideSteps {
Login,
NewTable,
DistributeTable,
LoadData,
Query,
}
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);
textfield.select();
document.execCommand("copy");
};
const getPivotHeaderIcon = (step: number): string => {
switch (step) {
case 0:
return Pivot1SelectedIcon;
case 1:
return step === currentStep ? Pivot2SelectedIcon : Pivot2Icon;
case 2:
return step === currentStep ? Pivot3SelectedIcon : Pivot3Icon;
case 3:
return step === currentStep ? Pivot4SelectedIcon : Pivot4Icon;
case 4:
return step === currentStep ? Pivot5SelectedIcon : Pivot5Icon;
default:
return "";
}
};
const customPivotHeaderRenderer = (
link: IPivotItemProps,
defaultRenderer: (link?: IPivotItemProps) => JSX.Element | null,
step: number
): JSX.Element | null => {
if (!link || !defaultRenderer) {
return null;
}
return (
<Stack horizontal verticalAlign="center">
{currentStep > step ? (
<Icon iconName="CompletedSolid" style={{ color: "#57A300", marginRight: 8 }} />
) : (
<Image style={{ marginRight: 8 }} src={getPivotHeaderIcon(step)} />
)}
{defaultRenderer({ ...link, itemIcon: undefined })}
</Stack>
);
};
return (
<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]}>
<PivotItem
headerText="Login"
onRenderItemLink={(props, defaultRenderer) => customPivotHeaderRenderer(props, defaultRenderer, 0)}
itemKey={GuideSteps[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
</Text>
<Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "80%" }} />
</Stack>
</PivotItem>
<PivotItem
headerText="New table"
onRenderItemLink={(props, defaultRenderer) => customPivotHeaderRenderer(props, defaultRenderer, 1)}
itemKey={GuideSteps[1]}
onClick={() => setCurrentStep(1)}
>
<Stack style={{ marginTop: 20 }}>
<Text>
After logging in, lets create some new tables for storing data. We will start with two sample tables
- one for storing github users and one for storing github events
</Text>
<DefaultButton style={{ marginTop: 16, width: 110 }}>New table</DefaultButton>
<Stack horizontal style={{ marginTop: 16 }}>
<TextField
id="newTableCommand"
multiline
rows={6}
readOnly
defaultValue={newTableCommand}
styles={{ root: { width: "90%" } }}
/>
<IconButton
iconProps={{
iconName: "Copy",
}}
onClick={() => onCopyBtnClicked("#newTableCommand")}
/>
</Stack>
<Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "60%" }} />
</Stack>
</PivotItem>
<PivotItem
headerText="Distribute table"
onRenderItemLink={(props, defaultRenderer) => customPivotHeaderRenderer(props, defaultRenderer, 2)}
itemKey={GuideSteps[2]}
onClick={() => setCurrentStep(2)}
>
<Stack style={{ marginTop: 20 }}>
<Text>
Congratulations, you have now created your first 2 tables.
<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
</Text>
<DefaultButton style={{ marginTop: 16, width: 150 }}>Distribute table</DefaultButton>
<Stack horizontal style={{ marginTop: 16 }}>
<TextField
id="distributeTableCommand"
multiline
rows={2}
readOnly
defaultValue={distributeTableCommand}
styles={{ root: { width: "90%" } }}
/>
<IconButton
iconProps={{
iconName: "Copy",
}}
onClick={() => onCopyBtnClicked("#distributeTableCommand")}
/>
</Stack>
<Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "60%" }} />
</Stack>
</PivotItem>
<PivotItem
headerText="Load data"
onRenderItemLink={(props, defaultRenderer) => customPivotHeaderRenderer(props, defaultRenderer, 3)}
itemKey={GuideSteps[3]}
onClick={() => setCurrentStep(3)}
>
<Stack style={{ marginTop: 20 }}>
<Text>
We&apos;re ready to fill the tables with sample data.
<br />
<br />
For this quick start, we&apos;ll use a dataset previously captured from the GitHub API. Run the
command below to load the data
</Text>
<DefaultButton style={{ marginTop: 16, width: 110 }}>Load data</DefaultButton>
<Stack horizontal style={{ marginTop: 16 }}>
<TextField
id="loadDataCommand"
multiline
rows={4}
readOnly
defaultValue={loadDataCommand}
styles={{ root: { width: "90%" } }}
/>
<IconButton
iconProps={{
iconName: "Copy",
}}
onClick={() => onCopyBtnClicked("#loadDataCommand")}
/>
</Stack>
<Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "60%" }} />
</Stack>
</PivotItem>
<PivotItem
headerText="Query"
onRenderItemLink={(props, defaultRenderer) => customPivotHeaderRenderer(props, defaultRenderer, 4)}
itemKey={GuideSteps[4]}
onClick={() => setCurrentStep(4)}
>
<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. Lets try a
query.
</Text>
<DefaultButton style={{ marginTop: 16, width: 110 }}>Try query</DefaultButton>
<Stack horizontal style={{ marginTop: 16 }}>
<TextField
id="queryCommand"
multiline
rows={6}
readOnly
defaultValue={queryCommand}
styles={{ root: { width: "90%" } }}
/>
<IconButton
iconProps={{
iconName: "Copy",
}}
onClick={() => onCopyBtnClicked("#queryCommand")}
/>
</Stack>
<Youtube videoId="Jvgh64rvdXU" style={{ margin: "20px 0" }} opts={{ width: "60%" }} />
</Stack>
</PivotItem>
</Pivot>
)}
{currentStep === 5 && (
<Stack style={{ margin: "auto" }} horizontalAlign="center">
<Image src={CompleteIcon} />
<Text variant="mediumPlus" style={{ fontWeight: 600, marginTop: 7 }}>
You are all set!
</Text>
<Text variant="mediumPlus" style={{ marginTop: 8 }}>
You have completed the quick start guide.{" "}
</Text>
</Stack>
)}
</Stack>
<Stack horizontal style={{ padding: "16px 20px", boxShadow: "inset 0px 1px 0px rgba(204, 204, 204, 0.8)" }}>
<DefaultButton disabled={currentStep === 0} onClick={() => setCurrentStep(currentStep - 1)}>
Previous
</DefaultButton>
{currentStep < 5 && (
<PrimaryButton onClick={() => setCurrentStep(currentStep + 1)} style={{ marginLeft: 8 }}>
Next
</PrimaryButton>
)}
{currentStep === 5 && (
<PrimaryButton
onClick={() => useTabs.getState().closeReactTab(ReactTabKind.Quickstart)}
style={{ marginLeft: 8 }}
>
Close
</PrimaryButton>
)}
</Stack>
</Stack>
);
};

View File

@@ -0,0 +1,202 @@
import { Link, Stack, TeachingBubble, Text } from "@fluentui/react";
import { ReactTabKind, useTabs } from "hooks/useTabs";
import { useTeachingBubble } from "hooks/useTeachingBubble";
import React from "react";
import { Action } from "Shared/Telemetry/TelemetryConstants";
import { traceCancel, traceSuccess } from "Shared/Telemetry/TelemetryProcessor";
import { userContext } from "UserContext";
export const MongoQuickstartTutorial: React.FC = (): JSX.Element => {
const { step, isSampleDBExpanded, isDocumentsTabOpened, sampleCollection, setStep } = useTeachingBubble();
const onDimissTeachingBubble = (): void => {
setStep(0);
traceCancel(Action.CancelUITour, { step });
};
if (userContext.apiType !== "Mongo") {
return <></>;
}
switch (step) {
case 1:
return isSampleDBExpanded ? (
<TeachingBubble
headline="View sample data"
target={"#sampleItems"}
hasCloseButton
primaryButtonProps={{
text: "Open Items",
onClick: () => {
sampleCollection.openTab();
setStep(2);
},
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent="Step 1 of 8"
>
Start viewing and working with your data by opening Documents under Data
</TeachingBubble>
) : (
<></>
);
case 2:
return isDocumentsTabOpened ? (
<TeachingBubble
headline="View Documents"
target={".documentsGridHeaderContainer"}
hasCloseButton
primaryButtonProps={{
text: "Next",
onClick: () => setStep(3),
}}
secondaryButtonProps={{
text: "Previous",
onClick: () => setStep(1),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent="Step 2 of 8"
>
View documents here using the documents window. You can also use your favorite MongoDB tools and drivers to do
so.
</TeachingBubble>
) : (
<></>
);
case 3:
return (
<TeachingBubble
headline="Add new document"
target={"#mongoNewDocumentBtn"}
hasCloseButton
primaryButtonProps={{
text: "Next",
onClick: () => setStep(4),
}}
secondaryButtonProps={{
text: "Previous",
onClick: () => setStep(2),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent="Step 3 of 8"
>
Add new document by copy / pasting JSON or uploading a JSON. You can also use your favorite MongoDB tools and
drivers to do so.
</TeachingBubble>
);
case 4:
return (
<TeachingBubble
headline="Run a query"
target={".querydropdown"}
hasCloseButton
primaryButtonProps={{
text: "Next",
onClick: () => setStep(5),
}}
secondaryButtonProps={{
text: "Previous",
onClick: () => setStep(3),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent="Step 4 of 8"
>
Query your data using the filter function. Azure Cosmos DB API for MongoDB provides comprehensive support for
MongoDB query language constructs. You can also use your favorite MongoDB tools and drivers to do so.
</TeachingBubble>
);
case 5:
return (
<TeachingBubble
headline="Scale throughput"
target={"#sampleScaleSettings"}
hasCloseButton
primaryButtonProps={{
text: "Next",
onClick: () => setStep(6),
}}
secondaryButtonProps={{
text: "Previous",
onClick: () => setStep(4),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent="Step 5 of 8"
>
Change throughput provisioned to your collection according to your needs
</TeachingBubble>
);
case 6:
return (
<TeachingBubble
headline="Indexing Policy"
target={"#sampleSettings"}
hasCloseButton
primaryButtonProps={{
text: "Next",
onClick: () => setStep(7),
}}
secondaryButtonProps={{
text: "Previous",
onClick: () => setStep(5),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent="Step 6 of 8"
>
Use the indexing policy editor to create and edit your indexes.
</TeachingBubble>
);
case 7:
return (
<TeachingBubble
headline="Create notebook"
target={"#newNotebookBtn"}
hasCloseButton
primaryButtonProps={{
text: "Next",
onClick: () => setStep(8),
}}
secondaryButtonProps={{
text: "Previous",
onClick: () => setStep(6),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent="Step 7 of 8"
>
Visualize your data, store queries in an interactive document
</TeachingBubble>
);
case 8:
return (
<TeachingBubble
headline="Congratulations!"
target={"#newNotebookBtn"}
hasCloseButton
primaryButtonProps={{
text: "Launch connect",
onClick: () => {
traceSuccess(Action.CompleteUITour);
useTabs.getState().openAndActivateReactTab(ReactTabKind.Connect);
},
}}
secondaryButtonProps={{
text: "Previous",
onClick: () => setStep(7),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent="Step 8 of 8"
>
<Stack>
<Text style={{ color: "white" }}>
You have finished the tour in data explorer. For next steps, you may want to launch connect and start
connecting with your current app.
</Text>
<Link style={{ color: "white", fontWeight: 600 }} target="_blank" href="https://aka.ms/cosmosdbsurvey">
Share your feedback
</Link>
</Stack>
</TeachingBubble>
);
default:
return <></>;
}
};

View File

@@ -0,0 +1,180 @@
import { Link, Stack, TeachingBubble, Text } from "@fluentui/react";
import { ReactTabKind, useTabs } from "hooks/useTabs";
import { useTeachingBubble } from "hooks/useTeachingBubble";
import React from "react";
import { Action } from "Shared/Telemetry/TelemetryConstants";
import { traceCancel, traceSuccess } from "Shared/Telemetry/TelemetryProcessor";
import { userContext } from "UserContext";
export const SQLQuickstartTutorial: React.FC = (): JSX.Element => {
const { step, isSampleDBExpanded, isDocumentsTabOpened, sampleCollection, setStep } = useTeachingBubble();
const onDimissTeachingBubble = (): void => {
setStep(0);
traceCancel(Action.CancelUITour, { step });
};
if (userContext.apiType !== "SQL") {
return <></>;
}
switch (step) {
case 1:
return isSampleDBExpanded ? (
<TeachingBubble
headline="View sample data"
target={"#sampleItems"}
hasCloseButton
primaryButtonProps={{
text: "Open Items",
onClick: () => {
sampleCollection.openTab();
setStep(2);
},
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent="Step 1 of 7"
>
Start viewing and working with your data by opening Items under Data
</TeachingBubble>
) : (
<></>
);
case 2:
return isDocumentsTabOpened ? (
<TeachingBubble
headline="View item"
target={".queryButton"}
hasCloseButton
primaryButtonProps={{
text: "Next",
onClick: () => setStep(3),
}}
secondaryButtonProps={{
text: "Previous",
onClick: () => setStep(1),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent="Step 2 of 7"
>
View item here using the items window. Additionally you can also filter items to be reviewed with the filter
function
</TeachingBubble>
) : (
<></>
);
case 3:
return (
<TeachingBubble
headline="Add new item"
target={"#uploadItemBtn"}
hasCloseButton
primaryButtonProps={{
text: "Next",
onClick: () => setStep(4),
}}
secondaryButtonProps={{
text: "Previous",
onClick: () => setStep(2),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent="Step 3 of 7"
>
Add new item by copy / pasting JSON; or uploading a JSON
</TeachingBubble>
);
case 4:
return (
<TeachingBubble
headline="Run a query"
target={"#newQueryBtn"}
hasCloseButton
primaryButtonProps={{
text: "Next",
onClick: () => setStep(5),
}}
secondaryButtonProps={{
text: "Previous",
onClick: () => setStep(3),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent="Step 4 of 7"
>
Query your data using either the filter function or new query.
</TeachingBubble>
);
case 5:
return (
<TeachingBubble
headline="Scale throughput"
target={"#sampleScaleSettings"}
hasCloseButton
primaryButtonProps={{
text: "Next",
onClick: () => setStep(6),
}}
secondaryButtonProps={{
text: "Previous",
onClick: () => setStep(4),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent="Step 5 of 7"
>
Change throughput provisioned to your container according to your needs
</TeachingBubble>
);
case 6:
return (
<TeachingBubble
headline="Create notebook"
target={"#newNotebookBtn"}
hasCloseButton
primaryButtonProps={{
text: "Next",
onClick: () => setStep(7),
}}
secondaryButtonProps={{
text: "Previous",
onClick: () => setStep(5),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent="Step 6 of 7"
>
Visualize your data, store queries in an interactive document
</TeachingBubble>
);
case 7:
return (
<TeachingBubble
headline="Congratulations!"
target={"#newNotebookBtn"}
hasCloseButton
primaryButtonProps={{
text: "Launch connect",
onClick: () => {
traceSuccess(Action.CompleteUITour);
useTabs.getState().openAndActivateReactTab(ReactTabKind.Connect);
},
}}
secondaryButtonProps={{
text: "Previous",
onClick: () => setStep(6),
}}
onDismiss={() => onDimissTeachingBubble()}
footerContent="Step 7 of 7"
>
<Stack>
<Text style={{ color: "white" }}>
You have finished the tour in data explorer. For next steps, you may want to launch connect and start
connecting with your current app.
</Text>
<Link style={{ color: "white", fontWeight: 600 }} target="_blank" href="https://aka.ms/cosmosdbsurvey">
Share your feedback
</Link>
</Stack>
</TeachingBubble>
);
default:
return <></>;
}
};

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