mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-08 20:17:03 +00:00
Compare commits
16 Commits
users/sind
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38823ac86f | ||
|
|
b71ea50972 | ||
|
|
e27cff0553 | ||
|
|
4ac8cd8fe4 | ||
|
|
53288dec6f | ||
|
|
258a6286e7 | ||
|
|
c8ebca6da4 | ||
|
|
6167f94bc3 | ||
|
|
be89c634f3 | ||
|
|
42e230b88b | ||
|
|
6196ba4722 | ||
|
|
2c31ec2a8d | ||
|
|
bc7e8a71ca | ||
|
|
d67c1a0464 | ||
|
|
5b7d1a74af | ||
|
|
8c0e6da377 |
33
.github/workflows/ci.yml
vendored
33
.github/workflows/ci.yml
vendored
@@ -164,8 +164,8 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
|
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
|
||||||
shardTotal: [16]
|
shardTotal: [20]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Use Node.js 18.x
|
- name: Use Node.js 18.x
|
||||||
@@ -192,24 +192,29 @@ jobs:
|
|||||||
NOSQL_READONLY_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-readonly.documents.azure.com/.default" -o tsv --query accessToken)
|
NOSQL_READONLY_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-readonly.documents.azure.com/.default" -o tsv --query accessToken)
|
||||||
echo "::add-mask::$NOSQL_READONLY_TESTACCOUNT_TOKEN"
|
echo "::add-mask::$NOSQL_READONLY_TESTACCOUNT_TOKEN"
|
||||||
echo NOSQL_READONLY_TESTACCOUNT_TOKEN=$NOSQL_READONLY_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
echo NOSQL_READONLY_TESTACCOUNT_TOKEN=$NOSQL_READONLY_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
||||||
|
NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-containercopyonly.documents.azure.com/.default" -o tsv --query accessToken)
|
||||||
|
echo "::add-mask::$NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN"
|
||||||
|
echo NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN=$NOSQL_CONTAINERCOPY_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
||||||
TABLE_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-tables.documents.azure.com/.default" -o tsv --query accessToken)
|
TABLE_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-tables.documents.azure.com/.default" -o tsv --query accessToken)
|
||||||
echo "::add-mask::$TABLE_TESTACCOUNT_TOKEN"
|
echo "::add-mask::$TABLE_TESTACCOUNT_TOKEN"
|
||||||
echo TABLE_TESTACCOUNT_TOKEN=$TABLE_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
echo TABLE_TESTACCOUNT_TOKEN=$TABLE_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
||||||
GREMLIN_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-gremlin.documents.azure.com/.default" -o tsv --query accessToken)
|
GREMLIN_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-gremlin.documents.azure.com/.default" -o tsv --query accessToken)
|
||||||
echo "::add-mask::$GREMLIN_TESTACCOUNT_TOKEN"
|
echo "::add-mask::$GREMLIN_TESTACCOUNT_TOKEN"
|
||||||
echo GREMLIN_TESTACCOUNT_TOKEN=$GREMLIN_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
echo GREMLIN_TESTACCOUNT_TOKEN=$GREMLIN_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
||||||
CASSANDRA_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-cassandra.documents.azure.com/.default" -o tsv --query accessToken)
|
# CASSANDRA_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-cassandra.documents.azure.com/.default" -o tsv --query accessToken)
|
||||||
echo "::add-mask::$CASSANDRA_TESTACCOUNT_TOKEN"
|
# echo "::add-mask::$CASSANDRA_TESTACCOUNT_TOKEN"
|
||||||
echo CASSANDRA_TESTACCOUNT_TOKEN=$CASSANDRA_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
# echo CASSANDRA_TESTACCOUNT_TOKEN=$CASSANDRA_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
||||||
MONGO_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo.documents.azure.com/.default" -o tsv --query accessToken)
|
# MONGO_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo.documents.azure.com/.default" -o tsv --query accessToken)
|
||||||
echo "::add-mask::$MONGO_TESTACCOUNT_TOKEN"
|
# echo "::add-mask::$MONGO_TESTACCOUNT_TOKEN"
|
||||||
echo MONGO_TESTACCOUNT_TOKEN=$MONGO_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
# echo MONGO_TESTACCOUNT_TOKEN=$MONGO_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
||||||
MONGO32_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo32.documents.azure.com/.default" -o tsv --query accessToken)
|
# MONGO32_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo32.documents.azure.com/.default" -o tsv --query accessToken)
|
||||||
echo "::add-mask::$MONGO32_TESTACCOUNT_TOKEN"
|
# echo "::add-mask::$MONGO32_TESTACCOUNT_TOKEN"
|
||||||
echo MONGO32_TESTACCOUNT_TOKEN=$MONGO32_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
# echo MONGO32_TESTACCOUNT_TOKEN=$MONGO32_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
||||||
MONGO_READONLY_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo-readonly.documents.azure.com/.default" -o tsv --query accessToken)
|
# MONGO_READONLY_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo-readonly.documents.azure.com/.default" -o tsv --query accessToken)
|
||||||
echo "::add-mask::$MONGO_READONLY_TESTACCOUNT_TOKEN"
|
# echo "::add-mask::$MONGO_READONLY_TESTACCOUNT_TOKEN"
|
||||||
echo MONGO_READONLY_TESTACCOUNT_TOKEN=$MONGO_READONLY_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
# echo MONGO_READONLY_TESTACCOUNT_TOKEN=$MONGO_READONLY_TESTACCOUNT_TOKEN >> $GITHUB_ENV
|
||||||
|
- name: List test files for shard ${{ matrix['shardIndex'] }} of ${{ matrix['shardTotal']}}
|
||||||
|
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --list
|
||||||
- name: Run test shard ${{ matrix['shardIndex'] }} of ${{ matrix['shardTotal']}}
|
- name: Run test shard ${{ matrix['shardIndex'] }} of ${{ matrix['shardTotal']}}
|
||||||
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers=3
|
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers=3
|
||||||
- name: Upload blob report to GitHub Actions Artifacts
|
- name: Upload blob report to GitHub Actions Artifacts
|
||||||
|
|||||||
4
.github/workflows/cleanup.yml
vendored
4
.github/workflows/cleanup.yml
vendored
@@ -6,8 +6,8 @@ on:
|
|||||||
# Allows you to run this workflow manually from the Actions tab
|
# Allows you to run this workflow manually from the Actions tab
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
# Once every hour
|
# Once every two hours
|
||||||
- cron: "0 15 * * *"
|
- cron: "0 */2 * * *"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
|
|||||||
@@ -19,6 +19,6 @@
|
|||||||
</frameworkAssemblies>
|
</frameworkAssemblies>
|
||||||
</metadata>
|
</metadata>
|
||||||
<files>
|
<files>
|
||||||
<file src="**\*" target="content"/>
|
<file src="**\*" exclude="obj\**\*" target="content"/>
|
||||||
</files>
|
</files>
|
||||||
</package>
|
</package>
|
||||||
3
images/DocumentIcon.svg
Normal file
3
images/DocumentIcon.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
|
||||||
|
<path d="M4 4c0-1.1.9-2 2-2h3.59c.4 0 .78.16 1.06.44l3.91 3.91c.28.28.44.67.44 1.06V14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4Zm2-1a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1V8h-3.5A1.5 1.5 0 0 1 9 6.5V3H6Zm4 .2v3.3c0 .28.22.5.5.5h3.3L10 3.2ZM17 9a1 1 0 0 0-1-1v6a3 3 0 0 1-3 3H6a1 1 0 0 0 1 1h6.06A3.94 3.94 0 0 0 17 14.06V9Z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 439 B |
3
images/MoonIcon.svg
Normal file
3
images/MoonIcon.svg
Normal 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="M7.85032 3.0153C10.4276 3.21621 12.4563 5.37119 12.4563 8C12.4563 10.7614 10.2177 13 7.45629 13C5.70158 13 4.15722 12.0961 3.26465 10.7271C4.66791 10.3479 6.58077 9.42526 7.42438 7.17555C7.97709 5.70162 8.00857 4.23763 7.85032 3.0153ZM13.4563 8C13.4563 4.68629 10.77 2 7.45629 2C7.38577 2 7.31552 2.00122 7.24555 2.00364C7.09984 2.00867 6.96358 2.07706 6.87247 2.19089C6.78136 2.30471 6.74447 2.45263 6.77147 2.59591C7.00024 3.81021 7.05064 5.32413 6.48805 6.82444C5.68804 8.95787 3.68609 9.66359 2.41062 9.89533C2.25698 9.92325 2.1252 10.0213 2.05438 10.1605C1.98356 10.2997 1.98182 10.4639 2.04969 10.6046C3.01873 12.6128 5.07502 14 7.45629 14C10.77 14 13.4563 11.3137 13.4563 8Z" fill="#0078d4" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 814 B |
3
images/SunIcon.svg
Normal file
3
images/SunIcon.svg
Normal 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="M8 1.33C8.276 1.33 8.5 1.554 8.5 1.83V2.83C8.5 3.106 8.276 3.33 8 3.33C7.724 3.33 7.5 3.106 7.5 2.83V1.83C7.5 1.554 7.724 1.33 8 1.33ZM8 11.33C9.841 11.33 11.33 9.841 11.33 8C11.33 6.159 9.841 4.67 8 4.67C6.159 4.67 4.67 6.159 4.67 8C4.67 9.841 6.159 11.33 8 11.33ZM8 10.33C6.711 10.33 5.67 9.289 5.67 8C5.67 6.711 6.711 5.67 8 5.67C9.289 5.67 10.33 6.711 10.33 8C10.33 9.289 9.289 10.33 8 10.33ZM14.17 8.5C14.446 8.5 14.67 8.276 14.67 8C14.67 7.724 14.446 7.5 14.17 7.5H13.17C12.894 7.5 12.67 7.724 12.67 8C12.67 8.276 12.894 8.5 13.17 8.5H14.17ZM8 12.67C8.276 12.67 8.5 12.894 8.5 13.17V14.17C8.5 14.446 8.276 14.67 8 14.67C7.724 14.67 7.5 14.446 7.5 14.17V13.17C7.5 12.894 7.724 12.67 8 12.67ZM2.83 8.5C3.106 8.5 3.33 8.276 3.33 8C3.33 7.724 3.106 7.5 2.83 7.5H1.83C1.554 7.5 1.33 7.724 1.33 8C1.33 8.276 1.554 8.5 1.83 8.5H2.83ZM2.813 2.813C3.009 2.617 3.325 2.617 3.521 2.813L4.521 3.813C4.717 4.009 4.717 4.325 4.521 4.521C4.325 4.717 4.009 4.717 3.813 4.521L2.813 3.521C2.617 3.325 2.617 3.009 2.813 2.813ZM3.521 13.187C3.325 13.383 3.009 13.383 2.813 13.187C2.617 12.991 2.617 12.675 2.813 12.479L3.813 11.479C4.009 11.283 4.325 11.283 4.521 11.479C4.717 11.675 4.717 11.991 4.521 12.187L3.521 13.187ZM13.187 2.813C12.991 2.617 12.675 2.617 12.479 2.813L11.479 3.813C11.283 4.009 11.283 4.325 11.479 4.521C11.675 4.717 11.991 4.717 12.187 4.521L13.187 3.521C13.383 3.325 13.383 3.009 13.187 2.813ZM12.479 13.187C12.675 13.383 12.991 13.383 13.187 13.187C13.383 12.991 13.383 12.675 13.187 12.479L12.187 11.479C11.991 11.283 11.675 11.283 11.479 11.479C11.283 11.675 11.283 11.991 11.479 12.187L12.479 13.187Z" fill="#0078d4" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
3
images/moon-blue.svg
Normal file
3
images/moon-blue.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4 7.5C4 5.567 5.567 4 7.5 4C8.5 4 9.4 4.4 10 5.1C9.5 4.8 8.9 4.6 8.3 4.6C6.8 4.6 5.6 5.8 5.6 7.3C5.6 8.8 6.8 10 8.3 10C8.9 10 9.5 9.8 10 9.5C9.4 10.2 8.5 10.6 7.5 10.6C5.567 10.6 4 9.033 4 7.1V7.5Z" fill="#0078D4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 328 B |
11
images/sun-blue.svg
Normal file
11
images/sun-blue.svg
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 2C6 1.44772 6.44772 1 7 1C7.55228 1 8 1.44772 8 2C8 2.55228 7.55228 3 7 3C6.44772 3 6 2.55228 6 2Z" fill="#0078D4"/>
|
||||||
|
<path d="M6 13C6 12.4477 6.44772 12 7 12C7.55228 12 8 12.4477 8 13C8 13.5523 7.55228 14 7 14C6.44772 14 6 13.5523 6 13Z" fill="#0078D4"/>
|
||||||
|
<path d="M1 7C1 6.44772 1.44772 6 2 6C2.55228 6 3 6.44772 3 7C3 7.55228 2.55228 8 2 8C1.44772 8 1 7.55228 1 7Z" fill="#0078D4"/>
|
||||||
|
<path d="M12 7C12 6.44772 12.4477 6 13 6C13.5523 6 14 6.44772 14 7C14 7.55228 13.5523 8 13 8C12.4477 8 12 7.55228 12 7Z" fill="#0078D4"/>
|
||||||
|
<path d="M2.63604 3.63604C3.02656 3.24551 3.65973 3.24551 4.05025 3.63604C4.44078 4.02656 4.44078 4.65973 4.05025 5.05025C3.65973 5.44078 3.02656 5.44078 2.63604 5.05025C2.24551 4.65973 2.24551 4.02656 2.63604 3.63604Z" fill="#0078D4"/>
|
||||||
|
<path d="M10.9497 9.94975C11.3403 9.55922 11.9734 9.55922 12.364 9.94975C12.7545 10.3403 12.7545 10.9734 12.364 11.364C11.9734 11.7545 11.3403 11.7545 10.9497 11.364C10.5592 10.9734 10.5592 10.3403 10.9497 9.94975Z" fill="#0078D4"/>
|
||||||
|
<path d="M10.9497 5.05025C10.5592 4.65973 10.5592 4.02656 10.9497 3.63604C11.3403 3.24551 11.9734 3.24551 12.364 3.63604C12.7545 4.02656 12.7545 4.65973 12.364 5.05025C11.9734 5.44078 11.3403 5.44078 10.9497 5.05025Z" fill="#0078D4"/>
|
||||||
|
<path d="M2.63604 11.364C2.24551 10.9734 2.24551 10.3403 2.63604 9.94975C3.02656 9.55922 3.65973 9.55922 4.05025 9.94975C4.44078 10.3403 4.44078 10.9734 4.05025 11.364C3.65973 11.7545 3.02656 11.7545 2.63604 11.364Z" fill="#0078D4"/>
|
||||||
|
<circle cx="7.5" cy="7.5" r="2.5" fill="#0078D4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -128,7 +128,7 @@
|
|||||||
@provisionDatabaseThroughputInfo: 200px;
|
@provisionDatabaseThroughputInfo: 200px;
|
||||||
|
|
||||||
//tabs container
|
//tabs container
|
||||||
@ActiveTabHeight: 31px;
|
@ActiveTabHeight: 32px;
|
||||||
@ActiveTabWidth: 141px;
|
@ActiveTabWidth: 141px;
|
||||||
@TabsHeight: 30px;
|
@TabsHeight: 30px;
|
||||||
@TabsWidth: 140px;
|
@TabsWidth: 140px;
|
||||||
@@ -237,11 +237,11 @@
|
|||||||
*********************************************************************************************/
|
*********************************************************************************************/
|
||||||
|
|
||||||
.hover() {
|
.hover() {
|
||||||
background-color: @AccentLight;
|
background-color: var(--colorNeutralBackground1Hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.active() {
|
.active() {
|
||||||
background-color: @AccentExtra;
|
background-color: var(--colorNeutralBackground1Hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.focus() {
|
.focus() {
|
||||||
|
|||||||
@@ -406,7 +406,11 @@ body {
|
|||||||
width: 440px;
|
width: 440px;
|
||||||
min-height: 565px;
|
min-height: 565px;
|
||||||
}
|
}
|
||||||
|
.dataExplorerLoaderforcopyJobs{
|
||||||
|
width: 100%;
|
||||||
|
min-height: 565px;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
.dataExplorerTabLoaderContainer {
|
.dataExplorerTabLoaderContainer {
|
||||||
left: initial;
|
left: initial;
|
||||||
top: initial;
|
top: initial;
|
||||||
@@ -1740,7 +1744,7 @@ input::-webkit-calendar-picker-indicator {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
padding-left: 34px;
|
padding-left: 34px;
|
||||||
padding-right: 34px;
|
padding-right: 34px;
|
||||||
color: @BaseDark;
|
color: var(--colorNeutralForeground1);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
margin: (2 * @MediumSpace) 0px;
|
margin: (2 * @MediumSpace) 0px;
|
||||||
@@ -1749,7 +1753,6 @@ input::-webkit-calendar-picker-indicator {
|
|||||||
.contextual-pane .panelMainContent {
|
.contextual-pane .panelMainContent {
|
||||||
padding-left: 34px;
|
padding-left: 34px;
|
||||||
padding-right: 34px;
|
padding-right: 34px;
|
||||||
color: @BaseDark;
|
|
||||||
margin: (2 * @MediumSpace) 0px;
|
margin: (2 * @MediumSpace) 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1914,7 +1917,8 @@ input::-webkit-calendar-picker-indicator::after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs-margin {
|
.nav-tabs-margin {
|
||||||
background-color: #f2f2f2;
|
background-color: var(--colorNeutralBackground1);
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
|
|
||||||
.nav-tabs {
|
.nav-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1922,11 +1926,19 @@ input::-webkit-calendar-picker-indicator::after {
|
|||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-bottom: -0.5px;
|
margin-bottom: -0.5px;
|
||||||
|
background-color: var(--colorNeutralBackground1Selected);
|
||||||
|
|
||||||
li {
|
li {
|
||||||
// Override the bootstrap defaults here to align with our layout constants.
|
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--colorNeutralBackground1Hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: var(--colorNeutralBackground1Pressed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1940,8 +1952,9 @@ input::-webkit-calendar-picker-indicator::after {
|
|||||||
.nav.nav-tabs.qslevel > li > a:hover {
|
.nav.nav-tabs.qslevel > li > a:hover {
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background-color: transparent !important;
|
background-color: var(--colorNeutralBackground1Selected);
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.numbersize {
|
.numbersize {
|
||||||
@@ -2376,6 +2389,8 @@ a:link {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-width: 0; // This prevents it to grow past the parent's width if its content is too wide
|
min-width: 0; // This prevents it to grow past the parent's width if its content is too wide
|
||||||
|
background-color: var(--colorNeutralBackground1);
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs {
|
.tabs {
|
||||||
@@ -2631,14 +2646,16 @@ a:link {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tabPanesContainer {
|
.tabPanesContainer {
|
||||||
display: flex;
|
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background-color: var(--colorNeutralBackground1);
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs-container {
|
.tabs-container {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.paddingspan4 {
|
.paddingspan4 {
|
||||||
@@ -2655,24 +2672,18 @@ a:link {
|
|||||||
.nav-tabs > li.active > .tabNavContentContainer,
|
.nav-tabs > li.active > .tabNavContentContainer,
|
||||||
.nav-tabs > li.active > .tabNavContentContainer:focus,
|
.nav-tabs > li.active > .tabNavContentContainer:focus,
|
||||||
.nav-tabs > li.active > .tabNavContentContainer:hover {
|
.nav-tabs > li.active > .tabNavContentContainer:hover {
|
||||||
color: #555;
|
color: var(--colorNeutralForeground1);
|
||||||
cursor: default;
|
cursor: default;
|
||||||
background-color: @BaseLight;
|
background-color: var(--colorNeutralBackground1);
|
||||||
border-color: @BaseMedium;
|
border-color: var(--colorNeutralStroke1);
|
||||||
border-bottom-color: @BaseLight;
|
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
height: @ActiveTabHeight;
|
height: @ActiveTabHeight;
|
||||||
width: @ActiveTabWidth;
|
width: @ActiveTabWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs > li.active > .tabNavContentContainer > .tab_Content > .contentWrapper > .tabNavText {
|
.nav-tabs > li.active .contentWrapper .tabNavText {
|
||||||
font-weight: bolder;
|
border-bottom: 2px solid var(--colorCompoundBrandBackground);
|
||||||
border-bottom: 2px solid rgba(0, 120, 212, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-tabs > li.active:focus > .tabNavContentContainer {
|
|
||||||
.focus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabNavContentContainer {
|
.tabNavContentContainer {
|
||||||
@@ -2681,7 +2692,7 @@ a:link {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
border-radius: 2px 2px 0 0;
|
border-radius: 2px 2px 0 0;
|
||||||
padding: @DefaultSpace 0px @SmallSpace 0px;
|
padding: @DefaultSpace 0px @SmallSpace 0px;
|
||||||
color: @BaseHigh;
|
color: var(--colorNeutralForeground1);
|
||||||
width: @TabsWidth;
|
width: @TabsWidth;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -2689,19 +2700,21 @@ a:link {
|
|||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
background-color: @BaseMediumLow;
|
background-color: var(--colorNeutralBackground1Hover);
|
||||||
border-color: @BaseMediumLow;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background-color: @BaseMediumLow;
|
background-color: var(--colorNeutralBackground1Pressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab_Content {
|
.tab_Content {
|
||||||
.flex-display();
|
.flex-display();
|
||||||
width: @TabsWidth;
|
width: @TabsWidth;
|
||||||
border-right: @ButtonBorderWidth solid @BaseMedium;
|
border-right: @ButtonBorderWidth solid var(--colorNeutralStroke1);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
|
|
||||||
.contentWrapper {
|
.contentWrapper {
|
||||||
.flex-display();
|
.flex-display();
|
||||||
width: @ContentWrapper;
|
width: @ContentWrapper;
|
||||||
@@ -2723,9 +2736,8 @@ a:link {
|
|||||||
background-image: url(../images/error_no_outline.svg);
|
background-image: url(../images/error_no_outline.svg);
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-size: 3px;
|
|
||||||
display: block;
|
display: block;
|
||||||
margin: 1px 0px 0px 6px;
|
margin: 4px 0px 0px 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2750,39 +2762,60 @@ a:link {
|
|||||||
.loadingIcon {
|
.loadingIcon {
|
||||||
width: @LoadingErrorIconSize;
|
width: @LoadingErrorIconSize;
|
||||||
height: @LoadingErrorIconSize;
|
height: @LoadingErrorIconSize;
|
||||||
margin: 0px 0px @SmallSpace @SmallSpace;
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warningIconContainer {
|
||||||
|
width: @ErrorIconContainer;
|
||||||
|
height: @ErrorIconContainer;
|
||||||
|
margin-top: 1px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabNavText {
|
.tabNavText {
|
||||||
margin-left: @SmallSpace;
|
margin-left: @SmallSpace;
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
color: @BaseDark;
|
color: var(--colorNeutralForeground1);
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabIconSection {
|
.tabIconSection {
|
||||||
width: 29px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-top: 2px;
|
|
||||||
|
|
||||||
.cancelButton {
|
.cancelButton {
|
||||||
padding: 0px @SmallSpace 0px @SmallSpace;
|
padding: 0px @SmallSpace 0px @SmallSpace;
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.hover();
|
background-color: var(--colorNeutralBackground1Hover);
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
.focus();
|
background-color: var(--colorNeutralBackground1Pressed);
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
.active();
|
background-color: var(--colorNeutralBackground1Pressed);
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "×";
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3137,3 +3170,12 @@ a:link {
|
|||||||
.sidebarContainer {
|
.sidebarContainer {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.close-Icon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ html {
|
|||||||
body {
|
body {
|
||||||
font-family: @FabricFont;
|
font-family: @FabricFont;
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
|
--colorCompoundBrandBackground: @FabricAccentMedium;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@@ -41,7 +42,7 @@ a:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs-margin {
|
.nav-tabs-margin {
|
||||||
padding-top: 5px;
|
padding-top: 0px;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,17 +69,20 @@ a:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs > li > .tabNavContentContainer > .tab_Content:hover {
|
.nav-tabs > li > .tabNavContentContainer > .tab_Content:hover {
|
||||||
border-bottom: 2px solid #e0e0e0;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs > li.active > .tabNavContentContainer > .tab_Content,
|
.nav-tabs > li.active > .tabNavContentContainer > .tab_Content,
|
||||||
.nav-tabs > li.active > .tabNavContentContainer > .tab_Content:hover {
|
.nav-tabs > li.active > .tabNavContentContainer > .tab_Content:hover {
|
||||||
border-bottom: 2px solid @FabricAccentMedium;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs > li.active > .tabNavContentContainer > .tab_Content > .contentWrapper > .tabNavText {
|
.nav-tabs > li.active > .tabNavContentContainer > .tab_Content > .contentWrapper > .tabNavText {
|
||||||
border-bottom: 0px none transparent;
|
border-bottom: 0px none transparent;
|
||||||
}
|
}
|
||||||
|
.nav-tabs > li.active .contentWrapper .tabNavText {
|
||||||
|
border-bottom: 2px solid @FabricAccentMedium;
|
||||||
|
}
|
||||||
|
|
||||||
.tabNavContentContainer {
|
.tabNavContentContainer {
|
||||||
padding: @SmallSpace 0px @SmallSpace 0px;
|
padding: @SmallSpace 0px @SmallSpace 0px;
|
||||||
@@ -214,6 +218,7 @@ a:focus {
|
|||||||
|
|
||||||
.tabPanesContainer {
|
.tabPanesContainer {
|
||||||
overflow: auto !important;
|
overflow: auto !important;
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs-container {
|
.tabs-container {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
.scaleSettingScrollable {
|
.scaleSettingScrollable {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
height:100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.disableFocusDefaults[tabindex] {
|
.disableFocusDefaults[tabindex] {
|
||||||
@@ -79,13 +79,13 @@
|
|||||||
|
|
||||||
.storageCapacityTitle {
|
.storageCapacityTitle {
|
||||||
padding: @LargeSpace 0px;
|
padding: @LargeSpace 0px;
|
||||||
|
|
||||||
}
|
}
|
||||||
.throughputStorageValue {
|
.throughputStorageValue {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.estimatedCost, .largePartitionKeyEnabled {
|
.estimatedCost,
|
||||||
|
.largePartitionKeyEnabled {
|
||||||
padding: @SmallSpace 0px @LargeSpace;
|
padding: @SmallSpace 0px @LargeSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,18 +109,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.formTree {
|
.formTree {
|
||||||
border: 1px solid #969696;
|
border: 1px solid var(--colorNeutralStroke1);
|
||||||
color: #393939;
|
color: var(--colorNeutralForeground1);
|
||||||
|
background-color: var(--colorNeutralBackground1);
|
||||||
padding: 0px 12px 1px 8px;
|
padding: 0px 12px 1px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.formTree:hover {
|
.formTree:hover {
|
||||||
border: 1px solid #969696;
|
border: 1px solid var(--colorNeutralStroke1Hover);
|
||||||
background-color: #e6f8fe;
|
background-color: var(--colorNeutralBackground1Hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.formTree::placeholder {
|
||||||
|
color: var(--colorNeutralForeground2);
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.formTree:active {
|
.formTree:active {
|
||||||
border: 1px solid #1ebbee;
|
border: 1px solid var(--colorNeutralStroke1Pressed);
|
||||||
|
background-color: var(--colorNeutralBackground1Pressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
.scaleForm {
|
.scaleForm {
|
||||||
@@ -139,7 +146,6 @@
|
|||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.spUdfTriggerHeader {
|
.spUdfTriggerHeader {
|
||||||
padding: @DefaultSpace 0px @SmallSpace (2 * @MediumSpace);
|
padding: @DefaultSpace 0px @SmallSpace (2 * @MediumSpace);
|
||||||
}
|
}
|
||||||
@@ -151,34 +157,34 @@
|
|||||||
|
|
||||||
.unselectedRadio {
|
.unselectedRadio {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border-color: #EEE!important;
|
border-color: #eee !important;
|
||||||
color: black!important;
|
color: black !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.disabledRadio {
|
.disabledRadio {
|
||||||
background-color: #A19F9D;
|
background-color: #a19f9d;
|
||||||
border-color: #EEE!important;
|
border-color: #eee !important;
|
||||||
color: white!important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectedRadio {
|
.selectedRadio {
|
||||||
background-color: @AccentMediumHigh;
|
background-color: @AccentMediumHigh;
|
||||||
border-color: #EEE!important;
|
border-color: #eee !important;
|
||||||
color: white!important;
|
color: white !important;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectedRadio:hover {
|
.selectedRadio:hover {
|
||||||
background-color: @AccentMediumHigh;
|
background-color: @AccentMediumHigh;
|
||||||
border-color: #EEE!important;
|
border-color: #eee !important;
|
||||||
color: white!important;
|
color: white !important;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectedRadio:active {
|
.selectedRadio:active {
|
||||||
background-color: #0072c6;
|
background-color: #0072c6;
|
||||||
border-color: #EEE!important;
|
border-color: #eee !important;
|
||||||
color: white!important;
|
color: white !important;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 1px solid #0072c6;
|
border: 1px solid #0072c6;
|
||||||
}
|
}
|
||||||
@@ -204,8 +210,18 @@
|
|||||||
|
|
||||||
.trigger-field {
|
.trigger-field {
|
||||||
width: 40%;
|
width: 40%;
|
||||||
margin-top: 10px
|
margin-top: 10px;
|
||||||
|
background-color: var(--colorNeutralBackground1);
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.trigger-field input::placeholder {
|
||||||
|
color: var(--colorNeutralForeground3);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.trigger-form {
|
.trigger-form {
|
||||||
padding: 10px 30px 10px 30px;
|
background-color: var(--colorNeutralBackground1);
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
|
padding: 10px 30px;
|
||||||
}
|
}
|
||||||
@@ -255,7 +255,7 @@ body {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
padding-left: 34px;
|
padding-left: 34px;
|
||||||
padding-right: 34px;
|
padding-right: 34px;
|
||||||
color: @BaseDark;
|
color: var(--colorNeutralForeground1);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
margin: (2 * @MediumSpace) 0px;
|
margin: (2 * @MediumSpace) 0px;
|
||||||
|
|||||||
270
less/tree.less
270
less/tree.less
@@ -1,270 +0,0 @@
|
|||||||
@import "./Common/Constants";
|
|
||||||
|
|
||||||
.resourceTree {
|
|
||||||
height: 100%;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
.main {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.resourceTreeScroll {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.userSelectNone {
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.treeHovermargin {
|
|
||||||
margin-left: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlight {
|
|
||||||
padding: @SmallSpace 2px;
|
|
||||||
outline: 0;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.hover();
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
.active();
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.contextmenushowing {
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collectionstree {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: @DefaultSpace;
|
|
||||||
|
|
||||||
.databaseList {
|
|
||||||
list-style-type: none;
|
|
||||||
padding-left: 0px;
|
|
||||||
|
|
||||||
.collectionList {
|
|
||||||
padding-left: (2 * @MediumSpace);
|
|
||||||
}
|
|
||||||
|
|
||||||
.collectionChildList {
|
|
||||||
padding-left: @LargeSpace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.databaseDocuments {
|
|
||||||
padding-left: (5 * @MediumSpace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.pointerCursor {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menuEllipsis {
|
|
||||||
padding-right: 6px;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 18px;
|
|
||||||
position: relative;
|
|
||||||
top: -5px;
|
|
||||||
left: 0px;
|
|
||||||
float: right;
|
|
||||||
display: none;
|
|
||||||
padding-left: 6px !important;
|
|
||||||
line-height: @TreeLineHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
.databaseMenu {
|
|
||||||
.flex-display();
|
|
||||||
}
|
|
||||||
|
|
||||||
.databaseMenu:hover .menuEllipsis,
|
|
||||||
.databaseMenu:focus .menuEllipsis {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.databaseCollChildTextOverflow {
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collectionMenu {
|
|
||||||
.flex-display();
|
|
||||||
}
|
|
||||||
|
|
||||||
.collectionMenu:hover .menuEllipsis,
|
|
||||||
.collectionMenu:focus .menuEllipsis {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.documentsMenu:hover .menuEllipsis,
|
|
||||||
.documentsMenu:focus .menuEllipsis {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.treeChildMenu {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.storedProcedureMenu:hover .menuEllipsis,
|
|
||||||
.storedProcedureMenu:focus .menuEllipsis {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.childMenu {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
padding-left: (6 * @MediumSpace);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.storedChildMenu:hover .menuEllipsis,
|
|
||||||
.storedChildMenu:focus .menuEllipsis {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contextmenu6 {
|
|
||||||
top: -29px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.userDefinedMenu:hover .contextmenu6 {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.userDefinedchildMenu:hover .menuEllipsis,
|
|
||||||
.userDefinedchildMenu:focus .menuEllipsis {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.triggersMenu:hover .menuEllipsis,
|
|
||||||
.triggersMenu:focus .menuEllipsis {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.triggersChildMenu:hover .menuEllipsis,
|
|
||||||
.triggersChildMenu:focus .menuEllipsis {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.databaseId {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.storedUdfTriggerMenu {
|
|
||||||
padding-left: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collectionstree img {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
vertical-align: text-top;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.collectionsTreeCollapseExpand {
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
vertical-align: middle;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapsed::before {
|
|
||||||
content: "\23F5";
|
|
||||||
margin-left: 0px;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.expanded::before {
|
|
||||||
content: "\23F7";
|
|
||||||
margin-left: 0px;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collectionMenuChildren {
|
|
||||||
padding-left: 42px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-nav {
|
|
||||||
width: 100vh;
|
|
||||||
height: 40px;
|
|
||||||
background: white;
|
|
||||||
transform-origin: left top;
|
|
||||||
-webkit-transform-origin: left top;
|
|
||||||
-ms-transform-origin: left top;
|
|
||||||
transform: rotate(-90deg) translateX(-100%);
|
|
||||||
-webkit-transform: rotate(-90deg) translateX(-100%);
|
|
||||||
-ms-transform: rotate(-90deg) translateX(-100%);
|
|
||||||
border-bottom: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-nav-img {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
margin: -32px 0 0 0;
|
|
||||||
transform: rotate(-90deg) translateX(-100%);
|
|
||||||
-webkit-transform: rotate(-90deg) translateX(-100%);
|
|
||||||
-ms-transform: rotate(-90deg) translateX(-100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-nav-img.main-nav-sub-img {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
margin: 0px 0px 0 0;
|
|
||||||
transform: rotate(180deg) translateX(0%);
|
|
||||||
-webkit-transform: rotate(180deg) translateX(0%);
|
|
||||||
-ms-transform: rotate(180deg) translateX(0%);
|
|
||||||
position: absolute;
|
|
||||||
right: -8px;
|
|
||||||
top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.nav {
|
|
||||||
margin: 0 auto;
|
|
||||||
margin-top: 0px;
|
|
||||||
margin-left: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini ul.nav li {
|
|
||||||
float: right;
|
|
||||||
line-height: 25px;
|
|
||||||
height: auto;
|
|
||||||
margin-top: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spancolchildstyle {
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contextmenubutton {
|
|
||||||
float: right;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlight:hover > .contextmenubutton {
|
|
||||||
display: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlight:hover > .contextmenubutton::after {
|
|
||||||
content: "\2026";
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.showEllipsis {
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -117,6 +117,7 @@
|
|||||||
"underscore": "1.12.1",
|
"underscore": "1.12.1",
|
||||||
"utility-types": "3.10.0",
|
"utility-types": "3.10.0",
|
||||||
"uuid": "9.0.0",
|
"uuid": "9.0.0",
|
||||||
|
"web-vitals": "4.2.4",
|
||||||
"zustand": "3.5.0"
|
"zustand": "3.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -35930,6 +35931,11 @@
|
|||||||
"defaults": "^1.0.3"
|
"defaults": "^1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/web-vitals": {
|
||||||
|
"version": "4.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz",
|
||||||
|
"integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="
|
||||||
|
},
|
||||||
"node_modules/webidl-conversions": {
|
"node_modules/webidl-conversions": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"license": "BSD-2-Clause"
|
"license": "BSD-2-Clause"
|
||||||
|
|||||||
@@ -111,6 +111,7 @@
|
|||||||
"tinykeys": "2.1.0",
|
"tinykeys": "2.1.0",
|
||||||
"underscore": "1.12.1",
|
"underscore": "1.12.1",
|
||||||
"utility-types": "3.10.0",
|
"utility-types": "3.10.0",
|
||||||
|
"web-vitals": "4.2.4",
|
||||||
"uuid": "9.0.0",
|
"uuid": "9.0.0",
|
||||||
"zustand": "3.5.0"
|
"zustand": "3.5.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ export default defineConfig({
|
|||||||
reporter: process.env.CI ? "blob" : "html",
|
reporter: process.env.CI ? "blob" : "html",
|
||||||
timeout: 10 * 60 * 1000,
|
timeout: 10 * 60 * 1000,
|
||||||
use: {
|
use: {
|
||||||
trace: "off",
|
trace: "retain-on-failure",
|
||||||
video: "off",
|
video: "retain-on-failure",
|
||||||
screenshot: "on",
|
screenshot: "on",
|
||||||
testIdAttribute: "data-test",
|
testIdAttribute: "data-test",
|
||||||
contextOptions: {
|
contextOptions: {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Overlay, Spinner, SpinnerSize } from "@fluentui/react";
|
import { Overlay, Spinner, SpinnerSize } from "@fluentui/react";
|
||||||
|
import { useThemeStore } from "hooks/useTheme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
interface LoadingOverlayProps {
|
interface LoadingOverlayProps {
|
||||||
@@ -7,15 +8,17 @@ interface LoadingOverlayProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const LoadingOverlay: React.FC<LoadingOverlayProps> = ({ isLoading, label }) => {
|
const LoadingOverlay: React.FC<LoadingOverlayProps> = ({ isLoading, label }) => {
|
||||||
|
const isDarkMode = useThemeStore((state) => state.isDarkMode);
|
||||||
if (!isLoading) {
|
if (!isLoading) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Overlay
|
<Overlay
|
||||||
|
data-test="loading-overlay"
|
||||||
styles={{
|
styles={{
|
||||||
root: {
|
root: {
|
||||||
backgroundColor: "rgba(255,255,255,0.9)",
|
backgroundColor: isDarkMode ? "rgba(32, 31, 30, 0.9)" : "rgba(255,255,255,0.9)",
|
||||||
zIndex: 9999,
|
zIndex: 9999,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@@ -23,7 +26,11 @@ const LoadingOverlay: React.FC<LoadingOverlayProps> = ({ isLoading, label }) =>
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Spinner size={SpinnerSize.large} label={label} styles={{ label: { fontWeight: 600 } }} />
|
<Spinner
|
||||||
|
size={SpinnerSize.large}
|
||||||
|
label={label}
|
||||||
|
styles={{ label: { fontWeight: 600, color: isDarkMode ? "#ffffff" : "#323130" } }}
|
||||||
|
/>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,3 +11,14 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Override dark mode inherit for pagination icons */
|
||||||
|
body.isDarkMode .pager-container .ms-Button .ms-Button-icon,
|
||||||
|
body.isDarkMode .pager-container .ms-Button i {
|
||||||
|
color: var(--colorBrandForeground1);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.isDarkMode .pager-container .ms-Button:disabled .ms-Button-icon,
|
||||||
|
body.isDarkMode .pager-container .ms-Button:disabled i {
|
||||||
|
color: var(--colorNeutralForegroundDisabled);
|
||||||
|
}
|
||||||
@@ -59,7 +59,7 @@ const Pager: React.FC<PagerProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className={className || "pager-container"}>
|
<div className={className || "pager-container"}>
|
||||||
{showItemCount && (
|
{showItemCount && (
|
||||||
<Text>
|
<Text className="themeText">
|
||||||
Showing {startIndex + 1} - {endIndex} of {totalCount} items
|
Showing {startIndex + 1} - {endIndex} of {totalCount} items
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
@@ -82,7 +82,7 @@ const Pager: React.FC<PagerProps> = ({
|
|||||||
disabled={disabled || currentPage === 1}
|
disabled={disabled || currentPage === 1}
|
||||||
styles={iconButtonStyles}
|
styles={iconButtonStyles}
|
||||||
/>
|
/>
|
||||||
<Text>
|
<Text className="themeText">
|
||||||
Page {currentPage} of {totalPages}
|
Page {currentPage} of {totalPages}
|
||||||
</Text>
|
</Text>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|||||||
@@ -50,10 +50,33 @@ export const Upload: FunctionComponent<UploadProps> = ({
|
|||||||
const title = label + " to upload";
|
const title = label + " to upload";
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<span className="renewUploadItemsHeader">{label}</span>
|
<span className="renewUploadItemsHeader" style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
{tooltip && <InfoTooltip>{tooltip}</InfoTooltip>}
|
{tooltip && <InfoTooltip>{tooltip}</InfoTooltip>}
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<TextField styles={{ fieldGroup: { width: 300 } }} readOnly value={selectedFilesTitle.toString()} />
|
<TextField
|
||||||
|
styles={{
|
||||||
|
fieldGroup: {
|
||||||
|
width: 300,
|
||||||
|
backgroundColor: "var(--colorNeutralBackground3)",
|
||||||
|
borderColor: "var(--colorNeutralStroke1)",
|
||||||
|
},
|
||||||
|
field: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground3)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
subComponentStyles: {
|
||||||
|
label: {
|
||||||
|
root: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
readOnly
|
||||||
|
value={selectedFilesTitle.toString()}
|
||||||
|
/>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
id="importFileInput"
|
id="importFileInput"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
exports[`LoadingOverlay should handle long labels properly 1`] = `
|
exports[`LoadingOverlay should handle long labels properly 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ms-Overlay root-109"
|
class="ms-Overlay root-109"
|
||||||
|
data-test="loading-overlay"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Spinner root-111"
|
class="ms-Spinner root-111"
|
||||||
@@ -22,6 +23,7 @@ exports[`LoadingOverlay should handle long labels properly 1`] = `
|
|||||||
exports[`LoadingOverlay should render loading overlay when isLoading is true 1`] = `
|
exports[`LoadingOverlay should render loading overlay when isLoading is true 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ms-Overlay root-109"
|
class="ms-Overlay root-109"
|
||||||
|
data-test="loading-overlay"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Spinner root-111"
|
class="ms-Spinner root-111"
|
||||||
@@ -41,6 +43,7 @@ exports[`LoadingOverlay should render loading overlay when isLoading is true 1`]
|
|||||||
exports[`LoadingOverlay should render loading overlay with custom label 1`] = `
|
exports[`LoadingOverlay should render loading overlay with custom label 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ms-Overlay root-109"
|
class="ms-Overlay root-109"
|
||||||
|
data-test="loading-overlay"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Spinner root-111"
|
class="ms-Spinner root-111"
|
||||||
@@ -60,6 +63,7 @@ exports[`LoadingOverlay should render loading overlay with custom label 1`] = `
|
|||||||
exports[`LoadingOverlay should render loading overlay with empty label 1`] = `
|
exports[`LoadingOverlay should render loading overlay with empty label 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ms-Overlay root-109"
|
class="ms-Overlay root-109"
|
||||||
|
data-test="loading-overlay"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Spinner root-111"
|
class="ms-Spinner root-111"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { configContext } from "ConfigContext";
|
||||||
import { ApiType, userContext } from "UserContext";
|
import { ApiType, userContext } from "UserContext";
|
||||||
import * as NotificationConsoleUtils from "Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "Utils/NotificationConsoleUtils";
|
||||||
import {
|
import {
|
||||||
@@ -14,9 +15,12 @@ import {
|
|||||||
DataTransferJobFeedResults,
|
DataTransferJobFeedResults,
|
||||||
DataTransferJobGetResults,
|
DataTransferJobGetResults,
|
||||||
} from "Utils/arm/generatedClients/dataTransferService/types";
|
} from "Utils/arm/generatedClients/dataTransferService/types";
|
||||||
|
import { armRequest } from "Utils/arm/request";
|
||||||
import { addToPolling, removeFromPolling, updateDataTransferJob, useDataTransferJobs } from "hooks/useDataTransferJobs";
|
import { addToPolling, removeFromPolling, updateDataTransferJob, useDataTransferJobs } from "hooks/useDataTransferJobs";
|
||||||
import promiseRetry, { AbortError, FailedAttemptError } from "p-retry";
|
import promiseRetry, { AbortError, FailedAttemptError } from "p-retry";
|
||||||
|
|
||||||
|
export const DATA_TRANSFER_JOB_API_VERSION = "2025-05-01-preview";
|
||||||
|
|
||||||
export interface DataTransferParams {
|
export interface DataTransferParams {
|
||||||
jobName: string;
|
jobName: string;
|
||||||
apiType: ApiType;
|
apiType: ApiType;
|
||||||
@@ -33,26 +37,34 @@ export const getDataTransferJobs = async (
|
|||||||
subscriptionId: string,
|
subscriptionId: string,
|
||||||
resourceGroup: string,
|
resourceGroup: string,
|
||||||
accountName: string,
|
accountName: string,
|
||||||
|
signal?: AbortSignal,
|
||||||
): Promise<DataTransferJobGetResults[]> => {
|
): Promise<DataTransferJobGetResults[]> => {
|
||||||
let dataTransferJobs: DataTransferJobGetResults[] = [];
|
let dataTransferJobs: DataTransferJobGetResults[] = [];
|
||||||
let dataTransferFeeds: DataTransferJobFeedResults = await listByDatabaseAccount(
|
let dataTransferFeeds: DataTransferJobFeedResults = await listByDatabaseAccount(
|
||||||
subscriptionId,
|
subscriptionId,
|
||||||
resourceGroup,
|
resourceGroup,
|
||||||
accountName,
|
accountName,
|
||||||
|
signal,
|
||||||
);
|
);
|
||||||
dataTransferJobs = [...dataTransferJobs, ...(dataTransferFeeds?.value || [])];
|
dataTransferJobs = [...dataTransferJobs, ...(dataTransferFeeds?.value || [])];
|
||||||
while (dataTransferFeeds?.nextLink) {
|
while (dataTransferFeeds?.nextLink) {
|
||||||
const nextResponse = await window.fetch(dataTransferFeeds.nextLink, {
|
/**
|
||||||
headers: {
|
* The `nextLink` URL returned by the Cosmos DB SQL API pointed to an incorrect endpoint, causing timeouts.
|
||||||
Authorization: userContext.authorizationToken,
|
* (i.e: https://cdbmgmtprodby.documents.azure.com:450/subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.DocumentDB/databaseAccounts/{account}/sql/dataTransferJobs?$top=100&$skiptoken=...)
|
||||||
},
|
* We manipulate the URL by parsing it to extract the path and query parameters,
|
||||||
|
* then construct the correct URL for the Azure Resource Manager (ARM) API.
|
||||||
|
* This ensures that the request is made to the correct base URL (`configContext.ARM_ENDPOINT`),
|
||||||
|
* which is required for ARM operations.
|
||||||
|
*/
|
||||||
|
const parsedUrl = new URL(dataTransferFeeds.nextLink);
|
||||||
|
const nextUrlPath = parsedUrl.pathname + parsedUrl.search;
|
||||||
|
dataTransferFeeds = await armRequest({
|
||||||
|
host: configContext.ARM_ENDPOINT,
|
||||||
|
path: nextUrlPath,
|
||||||
|
method: "GET",
|
||||||
|
apiVersion: DATA_TRANSFER_JOB_API_VERSION,
|
||||||
});
|
});
|
||||||
if (nextResponse.ok) {
|
dataTransferJobs.push(...(dataTransferFeeds?.value || []));
|
||||||
dataTransferFeeds = await nextResponse.json();
|
|
||||||
dataTransferJobs = [...dataTransferJobs, ...(dataTransferFeeds?.value || [])];
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return dataTransferJobs;
|
return dataTransferJobs;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,4 +50,5 @@ export enum MessageTypes {
|
|||||||
OpenCESCVAFeedbackBlade,
|
OpenCESCVAFeedbackBlade,
|
||||||
ActivateTab,
|
ActivateTab,
|
||||||
OpenContainerCopyFeedbackBlade,
|
OpenContainerCopyFeedbackBlade,
|
||||||
|
UpdateTheme,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
TriggerDefinition,
|
TriggerDefinition,
|
||||||
UserDefinedFunctionDefinition,
|
UserDefinedFunctionDefinition,
|
||||||
} from "@azure/cosmos";
|
} from "@azure/cosmos";
|
||||||
import Explorer from "../Explorer/Explorer";
|
import type Explorer from "../Explorer/Explorer";
|
||||||
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/ConsoleData";
|
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/ConsoleData";
|
||||||
import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient";
|
import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient";
|
||||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
import ConflictId from "../Explorer/Tree/ConflictId";
|
||||||
@@ -447,6 +447,9 @@ export interface DataExplorerInputsFrame {
|
|||||||
aadToken?: string;
|
aadToken?: string;
|
||||||
containerCopyEnabled?: boolean;
|
containerCopyEnabled?: boolean;
|
||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
|
theme?: {
|
||||||
|
mode: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SelfServeFrameInputs {
|
export interface SelfServeFrameInputs {
|
||||||
@@ -480,3 +483,6 @@ export interface DropdownOption<T> {
|
|||||||
value: T;
|
value: T;
|
||||||
disable?: boolean;
|
disable?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove the duplicate Explorer interface and export the type
|
||||||
|
export type { Explorer };
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import "@testing-library/jest-dom";
|
import "@testing-library/jest-dom";
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
|
import { getDataTransferJobs } from "../../../Common/dataAccess/dataTransfers";
|
||||||
import * as Logger from "../../../Common/Logger";
|
import * as Logger from "../../../Common/Logger";
|
||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import * as dataTransferService from "../../../Utils/arm/generatedClients/dataTransferService/dataTransferJobs";
|
import * as dataTransferService from "../../../Utils/arm/generatedClients/dataTransferService/dataTransferJobs";
|
||||||
@@ -30,6 +31,7 @@ jest.mock("../../../Common/Logger");
|
|||||||
jest.mock("../../../Utils/arm/generatedClients/dataTransferService/dataTransferJobs");
|
jest.mock("../../../Utils/arm/generatedClients/dataTransferService/dataTransferJobs");
|
||||||
jest.mock("../MonitorCopyJobs/MonitorCopyJobRefState");
|
jest.mock("../MonitorCopyJobs/MonitorCopyJobRefState");
|
||||||
jest.mock("../CopyJobUtils");
|
jest.mock("../CopyJobUtils");
|
||||||
|
jest.mock("../../../Common/dataAccess/dataTransfers");
|
||||||
|
|
||||||
describe("CopyJobActions", () => {
|
describe("CopyJobActions", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -154,8 +156,7 @@ describe("CopyJobActions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should fetch and format copy jobs successfully", async () => {
|
it("should fetch and format copy jobs successfully", async () => {
|
||||||
const mockResponse = {
|
const mockResponse = [
|
||||||
value: [
|
|
||||||
{
|
{
|
||||||
properties: {
|
properties: {
|
||||||
jobName: "job-1",
|
jobName: "job-1",
|
||||||
@@ -177,10 +178,9 @@ describe("CopyJobActions", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
];
|
||||||
};
|
|
||||||
|
|
||||||
(dataTransferService.listByDatabaseAccount as jest.Mock).mockResolvedValue(mockResponse);
|
(getDataTransferJobs as jest.Mock).mockResolvedValue(mockResponse);
|
||||||
(CopyJobUtils.formatUTCDateTime as jest.Mock).mockReturnValue({
|
(CopyJobUtils.formatUTCDateTime as jest.Mock).mockReturnValue({
|
||||||
formattedDateTime: "1/1/2025, 10:00:00 AM",
|
formattedDateTime: "1/1/2025, 10:00:00 AM",
|
||||||
timestamp: 1704106800000,
|
timestamp: 1704106800000,
|
||||||
@@ -201,8 +201,7 @@ describe("CopyJobActions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should filter jobs by CosmosDBSql component", async () => {
|
it("should filter jobs by CosmosDBSql component", async () => {
|
||||||
const mockResponse = {
|
const mockResponse = [
|
||||||
value: [
|
|
||||||
{
|
{
|
||||||
properties: {
|
properties: {
|
||||||
jobName: "sql-job",
|
jobName: "sql-job",
|
||||||
@@ -229,10 +228,9 @@ describe("CopyJobActions", () => {
|
|||||||
destination: { component: "CosmosDBSql", databaseName: "db2", containerName: "c2" },
|
destination: { component: "CosmosDBSql", databaseName: "db2", containerName: "c2" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
];
|
||||||
};
|
|
||||||
|
|
||||||
(dataTransferService.listByDatabaseAccount as jest.Mock).mockResolvedValue(mockResponse);
|
(getDataTransferJobs as jest.Mock).mockResolvedValue(mockResponse);
|
||||||
(CopyJobUtils.formatUTCDateTime as jest.Mock).mockReturnValue({
|
(CopyJobUtils.formatUTCDateTime as jest.Mock).mockReturnValue({
|
||||||
formattedDateTime: "1/1/2025, 10:00:00 AM",
|
formattedDateTime: "1/1/2025, 10:00:00 AM",
|
||||||
timestamp: 1704106800000,
|
timestamp: 1704106800000,
|
||||||
@@ -247,8 +245,7 @@ describe("CopyJobActions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should sort jobs by last updated time (newest first)", async () => {
|
it("should sort jobs by last updated time (newest first)", async () => {
|
||||||
const mockResponse = {
|
const mockResponse = [
|
||||||
value: [
|
|
||||||
{
|
{
|
||||||
properties: {
|
properties: {
|
||||||
jobName: "older-job",
|
jobName: "older-job",
|
||||||
@@ -275,10 +272,9 @@ describe("CopyJobActions", () => {
|
|||||||
destination: { component: "CosmosDBSql", databaseName: "db4", containerName: "c4" },
|
destination: { component: "CosmosDBSql", databaseName: "db4", containerName: "c4" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
];
|
||||||
};
|
|
||||||
|
|
||||||
(dataTransferService.listByDatabaseAccount as jest.Mock).mockResolvedValue(mockResponse);
|
(getDataTransferJobs as jest.Mock).mockResolvedValue(mockResponse);
|
||||||
(CopyJobUtils.formatUTCDateTime as jest.Mock).mockReturnValue({
|
(CopyJobUtils.formatUTCDateTime as jest.Mock).mockReturnValue({
|
||||||
formattedDateTime: "1/1/2025, 10:00:00 AM",
|
formattedDateTime: "1/1/2025, 10:00:00 AM",
|
||||||
timestamp: 1704106800000,
|
timestamp: 1704106800000,
|
||||||
@@ -293,8 +289,7 @@ describe("CopyJobActions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should calculate completion percentage correctly", async () => {
|
it("should calculate completion percentage correctly", async () => {
|
||||||
const mockResponse = {
|
const mockResponse = [
|
||||||
value: [
|
|
||||||
{
|
{
|
||||||
properties: {
|
properties: {
|
||||||
jobName: "job-1",
|
jobName: "job-1",
|
||||||
@@ -308,10 +303,9 @@ describe("CopyJobActions", () => {
|
|||||||
destination: { component: "CosmosDBSql", databaseName: "db2", containerName: "c2" },
|
destination: { component: "CosmosDBSql", databaseName: "db2", containerName: "c2" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
];
|
||||||
};
|
|
||||||
|
|
||||||
(dataTransferService.listByDatabaseAccount as jest.Mock).mockResolvedValue(mockResponse);
|
(getDataTransferJobs as jest.Mock).mockResolvedValue(mockResponse);
|
||||||
(CopyJobUtils.formatUTCDateTime as jest.Mock).mockReturnValue({
|
(CopyJobUtils.formatUTCDateTime as jest.Mock).mockReturnValue({
|
||||||
formattedDateTime: "1/1/2025, 10:00:00 AM",
|
formattedDateTime: "1/1/2025, 10:00:00 AM",
|
||||||
timestamp: 1704106800000,
|
timestamp: 1704106800000,
|
||||||
@@ -325,8 +319,7 @@ describe("CopyJobActions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should handle zero total count gracefully", async () => {
|
it("should handle zero total count gracefully", async () => {
|
||||||
const mockResponse = {
|
const mockResponse = [
|
||||||
value: [
|
|
||||||
{
|
{
|
||||||
properties: {
|
properties: {
|
||||||
jobName: "job-1",
|
jobName: "job-1",
|
||||||
@@ -340,10 +333,9 @@ describe("CopyJobActions", () => {
|
|||||||
destination: { component: "CosmosDBSql", databaseName: "db2", containerName: "c2" },
|
destination: { component: "CosmosDBSql", databaseName: "db2", containerName: "c2" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
];
|
||||||
};
|
|
||||||
|
|
||||||
(dataTransferService.listByDatabaseAccount as jest.Mock).mockResolvedValue(mockResponse);
|
(getDataTransferJobs as jest.Mock).mockResolvedValue(mockResponse);
|
||||||
(CopyJobUtils.formatUTCDateTime as jest.Mock).mockReturnValue({
|
(CopyJobUtils.formatUTCDateTime as jest.Mock).mockReturnValue({
|
||||||
formattedDateTime: "1/1/2025, 10:00:00 AM",
|
formattedDateTime: "1/1/2025, 10:00:00 AM",
|
||||||
timestamp: 1704106800000,
|
timestamp: 1704106800000,
|
||||||
@@ -361,8 +353,7 @@ describe("CopyJobActions", () => {
|
|||||||
message: "Error message line 1\r\n\r\nError message line 2",
|
message: "Error message line 1\r\n\r\nError message line 2",
|
||||||
code: "ErrorCode123",
|
code: "ErrorCode123",
|
||||||
};
|
};
|
||||||
const mockResponse = {
|
const mockResponse = [
|
||||||
value: [
|
|
||||||
{
|
{
|
||||||
properties: {
|
properties: {
|
||||||
jobName: "failed-job",
|
jobName: "failed-job",
|
||||||
@@ -377,10 +368,9 @@ describe("CopyJobActions", () => {
|
|||||||
error: mockError,
|
error: mockError,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
];
|
||||||
};
|
|
||||||
|
|
||||||
(dataTransferService.listByDatabaseAccount as jest.Mock).mockResolvedValue(mockResponse);
|
(getDataTransferJobs as jest.Mock).mockResolvedValue(mockResponse);
|
||||||
(CopyJobUtils.formatUTCDateTime as jest.Mock).mockReturnValue({
|
(CopyJobUtils.formatUTCDateTime as jest.Mock).mockReturnValue({
|
||||||
formattedDateTime: "1/1/2025, 10:00:00 AM",
|
formattedDateTime: "1/1/2025, 10:00:00 AM",
|
||||||
timestamp: 1704106800000,
|
timestamp: 1704106800000,
|
||||||
@@ -408,7 +398,7 @@ describe("CopyJobActions", () => {
|
|||||||
};
|
};
|
||||||
(global as any).AbortController = jest.fn(() => mockAbortController);
|
(global as any).AbortController = jest.fn(() => mockAbortController);
|
||||||
|
|
||||||
(dataTransferService.listByDatabaseAccount as jest.Mock).mockResolvedValue({ value: [] });
|
(getDataTransferJobs as jest.Mock).mockResolvedValue([]);
|
||||||
|
|
||||||
getCopyJobs();
|
getCopyJobs();
|
||||||
expect(mockAbortController.abort).not.toHaveBeenCalled();
|
expect(mockAbortController.abort).not.toHaveBeenCalled();
|
||||||
@@ -418,9 +408,7 @@ describe("CopyJobActions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should throw error for invalid response format", async () => {
|
it("should throw error for invalid response format", async () => {
|
||||||
(dataTransferService.listByDatabaseAccount as jest.Mock).mockResolvedValue({
|
(getDataTransferJobs as jest.Mock).mockResolvedValue("not-an-array");
|
||||||
value: "not-an-array",
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(getCopyJobs()).rejects.toThrow("Invalid migration job status response: Expected an array of jobs.");
|
await expect(getCopyJobs()).rejects.toThrow("Invalid migration job status response: Expected an array of jobs.");
|
||||||
});
|
});
|
||||||
@@ -430,16 +418,16 @@ describe("CopyJobActions", () => {
|
|||||||
message: "Aborted",
|
message: "Aborted",
|
||||||
content: JSON.stringify({ message: "signal is aborted without reason" }),
|
content: JSON.stringify({ message: "signal is aborted without reason" }),
|
||||||
};
|
};
|
||||||
(dataTransferService.listByDatabaseAccount as jest.Mock).mockRejectedValue(abortError);
|
(getDataTransferJobs as jest.Mock).mockRejectedValue(abortError);
|
||||||
|
|
||||||
await expect(getCopyJobs()).rejects.toMatchObject({
|
await expect(getCopyJobs()).rejects.toMatchObject({
|
||||||
message: expect.stringContaining("Please wait for the current fetch request to complete"),
|
message: expect.stringContaining("Previous copy job request was cancelled."),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should handle generic errors", async () => {
|
it("should handle generic errors", async () => {
|
||||||
const genericError = new Error("Network error");
|
const genericError = new Error("Network error");
|
||||||
(dataTransferService.listByDatabaseAccount as jest.Mock).mockRejectedValue(genericError);
|
(getDataTransferJobs as jest.Mock).mockRejectedValue(genericError);
|
||||||
|
|
||||||
await expect(getCopyJobs()).rejects.toThrow("Network error");
|
await expect(getCopyJobs()).rejects.toThrow("Network error");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
|
import { getDataTransferJobs } from "../../../Common/dataAccess/dataTransfers";
|
||||||
import { logError } from "../../../Common/Logger";
|
import { logError } from "../../../Common/Logger";
|
||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
import {
|
import {
|
||||||
cancel,
|
cancel,
|
||||||
complete,
|
complete,
|
||||||
create,
|
create,
|
||||||
listByDatabaseAccount,
|
|
||||||
pause,
|
pause,
|
||||||
resume,
|
resume,
|
||||||
} from "../../../Utils/arm/generatedClients/dataTransferService/dataTransferJobs";
|
} from "../../../Utils/arm/generatedClients/dataTransferService/dataTransferJobs";
|
||||||
@@ -63,14 +63,8 @@ export const getCopyJobs = async (): Promise<CopyJobType[]> => {
|
|||||||
const { subscriptionId, resourceGroup, accountName } = getAccountDetailsFromResourceId(
|
const { subscriptionId, resourceGroup, accountName } = getAccountDetailsFromResourceId(
|
||||||
userContext.databaseAccount?.id || "",
|
userContext.databaseAccount?.id || "",
|
||||||
);
|
);
|
||||||
const response = await listByDatabaseAccount(
|
const jobs = await getDataTransferJobs(subscriptionId, resourceGroup, accountName, copyJobsAbortController.signal);
|
||||||
subscriptionId,
|
|
||||||
resourceGroup,
|
|
||||||
accountName,
|
|
||||||
copyJobsAbortController.signal,
|
|
||||||
);
|
|
||||||
|
|
||||||
const jobs = response.value || [];
|
|
||||||
if (!Array.isArray(jobs)) {
|
if (!Array.isArray(jobs)) {
|
||||||
throw new Error("Invalid migration job status response: Expected an array of jobs.");
|
throw new Error("Invalid migration job status response: Expected an array of jobs.");
|
||||||
}
|
}
|
||||||
@@ -124,8 +118,7 @@ export const getCopyJobs = async (): Promise<CopyJobType[]> => {
|
|||||||
const errorContent = JSON.stringify(error.content || error.message || error);
|
const errorContent = JSON.stringify(error.content || error.message || error);
|
||||||
if (errorContent.includes("signal is aborted without reason")) {
|
if (errorContent.includes("signal is aborted without reason")) {
|
||||||
throw {
|
throw {
|
||||||
message:
|
message: "Previous copy job request was cancelled.",
|
||||||
"Please wait for the current fetch request to complete. The previous copy job fetch request was aborted.",
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ describe("CopyJobCommandBar", () => {
|
|||||||
|
|
||||||
render(<CopyJobCommandBar explorer={mockExplorer} />);
|
render(<CopyJobCommandBar explorer={mockExplorer} />);
|
||||||
|
|
||||||
expect(mockGetCommandBarButtons).toHaveBeenCalledWith(mockExplorer);
|
expect(mockGetCommandBarButtons).toHaveBeenCalledWith(mockExplorer, false);
|
||||||
expect(mockGetCommandBarButtons).toHaveBeenCalledTimes(1);
|
expect(mockGetCommandBarButtons).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ describe("CopyJobCommandBar", () => {
|
|||||||
|
|
||||||
render(<CopyJobCommandBar explorer={mockExplorer} />);
|
render(<CopyJobCommandBar explorer={mockExplorer} />);
|
||||||
|
|
||||||
expect(mockGetCommandBarButtons).toHaveBeenCalledWith(mockExplorer);
|
expect(mockGetCommandBarButtons).toHaveBeenCalledWith(mockExplorer, false);
|
||||||
expect(mockConvertButton.mock.calls[0][0]).toEqual(mockCommandButtonProps);
|
expect(mockConvertButton.mock.calls[0][0]).toEqual(mockCommandButtonProps);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -175,11 +175,11 @@ describe("CopyJobCommandBar", () => {
|
|||||||
mockConvertButton.mockReturnValue([]);
|
mockConvertButton.mockReturnValue([]);
|
||||||
|
|
||||||
const { rerender } = render(<CopyJobCommandBar explorer={mockExplorer1} />);
|
const { rerender } = render(<CopyJobCommandBar explorer={mockExplorer1} />);
|
||||||
expect(mockGetCommandBarButtons).toHaveBeenCalledWith(mockExplorer1);
|
expect(mockGetCommandBarButtons).toHaveBeenCalledWith(mockExplorer1, false);
|
||||||
|
|
||||||
rerender(<CopyJobCommandBar explorer={mockExplorer2} />);
|
rerender(<CopyJobCommandBar explorer={mockExplorer2} />);
|
||||||
|
|
||||||
expect(mockGetCommandBarButtons).toHaveBeenCalledWith(mockExplorer2);
|
expect(mockGetCommandBarButtons).toHaveBeenCalledWith(mockExplorer2, false);
|
||||||
expect(mockGetCommandBarButtons).toHaveBeenCalledTimes(2);
|
expect(mockGetCommandBarButtons).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,24 +1,28 @@
|
|||||||
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
|
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { StyleConstants } from "../../../Common/StyleConstants";
|
import { useThemeStore } from "../../../hooks/useTheme";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import * as CommandBarUtil from "../../Menus/CommandBar/CommandBarUtil";
|
import * as CommandBarUtil from "../../Menus/CommandBar/CommandBarUtil";
|
||||||
|
import { getThemeTokens } from "../../Theme/ThemeUtil";
|
||||||
import { ContainerCopyProps } from "../Types/CopyJobTypes";
|
import { ContainerCopyProps } from "../Types/CopyJobTypes";
|
||||||
import { getCommandBarButtons } from "./Utils";
|
import { getCommandBarButtons } from "./Utils";
|
||||||
|
|
||||||
const backgroundColor = StyleConstants.BaseLight;
|
const CopyJobCommandBar: React.FC<ContainerCopyProps> = ({ explorer }) => {
|
||||||
const rootStyle = {
|
const isDarkMode = useThemeStore((state) => state.isDarkMode);
|
||||||
|
const themeTokens = getThemeTokens(isDarkMode);
|
||||||
|
const backgroundColor = themeTokens.colorNeutralBackground1;
|
||||||
|
|
||||||
|
const rootStyle = {
|
||||||
root: {
|
root: {
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const CopyJobCommandBar: React.FC<ContainerCopyProps> = ({ explorer }) => {
|
const commandBarItems: CommandButtonComponentProps[] = getCommandBarButtons(explorer, isDarkMode);
|
||||||
const commandBarItems: CommandButtonComponentProps[] = getCommandBarButtons(explorer);
|
|
||||||
const controlButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton(commandBarItems, backgroundColor);
|
const controlButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton(commandBarItems, backgroundColor);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="commandBarContainer">
|
<div className="commandBarContainer" style={{ backgroundColor }}>
|
||||||
<FluentCommandBar
|
<FluentCommandBar
|
||||||
ariaLabel="Use left and right arrow keys to navigate between commands"
|
ariaLabel="Use left and right arrow keys to navigate between commands"
|
||||||
styles={rootStyle}
|
styles={rootStyle}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ describe("CommandBar Utils", () => {
|
|||||||
|
|
||||||
describe("getCommandBarButtons", () => {
|
describe("getCommandBarButtons", () => {
|
||||||
it("should return an array of command button props", () => {
|
it("should return an array of command button props", () => {
|
||||||
const buttons = getCommandBarButtons(mockExplorer);
|
const buttons = getCommandBarButtons(mockExplorer, false);
|
||||||
|
|
||||||
expect(buttons).toBeDefined();
|
expect(buttons).toBeDefined();
|
||||||
expect(Array.isArray(buttons)).toBe(true);
|
expect(Array.isArray(buttons)).toBe(true);
|
||||||
@@ -58,7 +58,7 @@ describe("CommandBar Utils", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should include create copy job button", () => {
|
it("should include create copy job button", () => {
|
||||||
const buttons = getCommandBarButtons(mockExplorer);
|
const buttons = getCommandBarButtons(mockExplorer, false);
|
||||||
const createButton = buttons[0];
|
const createButton = buttons[0];
|
||||||
|
|
||||||
expect(createButton).toBeDefined();
|
expect(createButton).toBeDefined();
|
||||||
@@ -70,7 +70,7 @@ describe("CommandBar Utils", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should include refresh button", () => {
|
it("should include refresh button", () => {
|
||||||
const buttons = getCommandBarButtons(mockExplorer);
|
const buttons = getCommandBarButtons(mockExplorer, false);
|
||||||
const refreshButton = buttons[1];
|
const refreshButton = buttons[1];
|
||||||
|
|
||||||
expect(refreshButton).toBeDefined();
|
expect(refreshButton).toBeDefined();
|
||||||
@@ -80,11 +80,11 @@ describe("CommandBar Utils", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should include feedback button when platform is Portal", () => {
|
it("should include feedback button when platform is Portal", () => {
|
||||||
const buttons = getCommandBarButtons(mockExplorer);
|
const buttons = getCommandBarButtons(mockExplorer, false);
|
||||||
|
|
||||||
expect(buttons.length).toBe(3);
|
expect(buttons.length).toBe(4);
|
||||||
|
|
||||||
const feedbackButton = buttons[2];
|
const feedbackButton = buttons[3];
|
||||||
expect(feedbackButton).toBeDefined();
|
expect(feedbackButton).toBeDefined();
|
||||||
expect(feedbackButton.ariaLabel).toBe("Provide feedback on copy jobs");
|
expect(feedbackButton.ariaLabel).toBe("Provide feedback on copy jobs");
|
||||||
expect(feedbackButton.tooltipText).toBe("Feedback");
|
expect(feedbackButton.tooltipText).toBe("Feedback");
|
||||||
@@ -105,13 +105,13 @@ describe("CommandBar Utils", () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const { getCommandBarButtons: getCommandBarButtonsEmulator } = await import("./Utils");
|
const { getCommandBarButtons: getCommandBarButtonsEmulator } = await import("./Utils");
|
||||||
const buttons = getCommandBarButtonsEmulator(mockExplorer);
|
const buttons = getCommandBarButtonsEmulator(mockExplorer, false);
|
||||||
|
|
||||||
expect(buttons.length).toBe(2);
|
expect(buttons.length).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should call openCreateCopyJobPanel when create button is clicked", () => {
|
it("should call openCreateCopyJobPanel when create button is clicked", () => {
|
||||||
const buttons = getCommandBarButtons(mockExplorer);
|
const buttons = getCommandBarButtons(mockExplorer, false);
|
||||||
const createButton = buttons[0];
|
const createButton = buttons[0];
|
||||||
|
|
||||||
createButton.onCommandClick({} as React.SyntheticEvent);
|
createButton.onCommandClick({} as React.SyntheticEvent);
|
||||||
@@ -121,7 +121,7 @@ describe("CommandBar Utils", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should call refreshJobList when refresh button is clicked", () => {
|
it("should call refreshJobList when refresh button is clicked", () => {
|
||||||
const buttons = getCommandBarButtons(mockExplorer);
|
const buttons = getCommandBarButtons(mockExplorer, false);
|
||||||
const refreshButton = buttons[1];
|
const refreshButton = buttons[1];
|
||||||
|
|
||||||
refreshButton.onCommandClick({} as React.SyntheticEvent);
|
refreshButton.onCommandClick({} as React.SyntheticEvent);
|
||||||
@@ -130,8 +130,8 @@ describe("CommandBar Utils", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should call openContainerCopyFeedbackBlade when feedback button is clicked", () => {
|
it("should call openContainerCopyFeedbackBlade when feedback button is clicked", () => {
|
||||||
const buttons = getCommandBarButtons(mockExplorer);
|
const buttons = getCommandBarButtons(mockExplorer, false);
|
||||||
const feedbackButton = buttons[2];
|
const feedbackButton = buttons[3];
|
||||||
|
|
||||||
feedbackButton.onCommandClick({} as React.SyntheticEvent);
|
feedbackButton.onCommandClick({} as React.SyntheticEvent);
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ describe("CommandBar Utils", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should return buttons with correct icon sources", () => {
|
it("should return buttons with correct icon sources", () => {
|
||||||
const buttons = getCommandBarButtons(mockExplorer);
|
const buttons = getCommandBarButtons(mockExplorer, false);
|
||||||
|
|
||||||
expect(buttons[0].iconSrc).toBeDefined();
|
expect(buttons[0].iconSrc).toBeDefined();
|
||||||
expect(buttons[0].iconAlt).toBe("Create Copy Job");
|
expect(buttons[0].iconAlt).toBe("Create Copy Job");
|
||||||
@@ -148,7 +148,10 @@ describe("CommandBar Utils", () => {
|
|||||||
expect(buttons[1].iconAlt).toBe("Refresh");
|
expect(buttons[1].iconAlt).toBe("Refresh");
|
||||||
|
|
||||||
expect(buttons[2].iconSrc).toBeDefined();
|
expect(buttons[2].iconSrc).toBeDefined();
|
||||||
expect(buttons[2].iconAlt).toBe("Feedback");
|
expect(buttons[2].iconAlt).toBe("Dark Theme");
|
||||||
|
|
||||||
|
expect(buttons[3].iconSrc).toBeDefined();
|
||||||
|
expect(buttons[3].iconAlt).toBe("Feedback");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should handle null MonitorCopyJobsRefState ref gracefully", () => {
|
it("should handle null MonitorCopyJobsRefState ref gracefully", () => {
|
||||||
@@ -157,14 +160,14 @@ describe("CommandBar Utils", () => {
|
|||||||
return selector(state);
|
return selector(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
const buttons = getCommandBarButtons(mockExplorer);
|
const buttons = getCommandBarButtons(mockExplorer, false);
|
||||||
const refreshButton = buttons[1];
|
const refreshButton = buttons[1];
|
||||||
|
|
||||||
expect(() => refreshButton.onCommandClick({} as React.SyntheticEvent)).not.toThrow();
|
expect(() => refreshButton.onCommandClick({} as React.SyntheticEvent)).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should set hasPopup to false for all buttons", () => {
|
it("should set hasPopup to false for all buttons", () => {
|
||||||
const buttons = getCommandBarButtons(mockExplorer);
|
const buttons = getCommandBarButtons(mockExplorer, false);
|
||||||
|
|
||||||
buttons.forEach((button) => {
|
buttons.forEach((button) => {
|
||||||
expect(button.hasPopup).toBe(false);
|
expect(button.hasPopup).toBe(false);
|
||||||
@@ -172,7 +175,7 @@ describe("CommandBar Utils", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should set commandButtonLabel to undefined for all buttons", () => {
|
it("should set commandButtonLabel to undefined for all buttons", () => {
|
||||||
const buttons = getCommandBarButtons(mockExplorer);
|
const buttons = getCommandBarButtons(mockExplorer, false);
|
||||||
|
|
||||||
buttons.forEach((button) => {
|
buttons.forEach((button) => {
|
||||||
expect(button.commandButtonLabel).toBeUndefined();
|
expect(button.commandButtonLabel).toBeUndefined();
|
||||||
@@ -180,7 +183,7 @@ describe("CommandBar Utils", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should respect disabled state when provided", () => {
|
it("should respect disabled state when provided", () => {
|
||||||
const buttons = getCommandBarButtons(mockExplorer);
|
const buttons = getCommandBarButtons(mockExplorer, false);
|
||||||
|
|
||||||
buttons.forEach((button) => {
|
buttons.forEach((button) => {
|
||||||
expect(button.disabled).toBe(false);
|
expect(button.disabled).toBe(false);
|
||||||
@@ -188,7 +191,7 @@ describe("CommandBar Utils", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should return CommandButtonComponentProps with all required properties", () => {
|
it("should return CommandButtonComponentProps with all required properties", () => {
|
||||||
const buttons = getCommandBarButtons(mockExplorer);
|
const buttons = getCommandBarButtons(mockExplorer, false);
|
||||||
|
|
||||||
buttons.forEach((button: CommandButtonComponentProps) => {
|
buttons.forEach((button: CommandButtonComponentProps) => {
|
||||||
expect(button).toHaveProperty("iconSrc");
|
expect(button).toHaveProperty("iconSrc");
|
||||||
@@ -202,18 +205,19 @@ describe("CommandBar Utils", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should maintain button order: create, refresh, feedback", () => {
|
it("should maintain button order: create, refresh, themeToggle, feedback", () => {
|
||||||
const buttons = getCommandBarButtons(mockExplorer);
|
const buttons = getCommandBarButtons(mockExplorer, false);
|
||||||
|
|
||||||
expect(buttons[0].tooltipText).toBe("Create Copy Job");
|
expect(buttons[0].tooltipText).toBe("Create Copy Job");
|
||||||
expect(buttons[1].tooltipText).toBe("Refresh");
|
expect(buttons[1].tooltipText).toBe("Refresh");
|
||||||
expect(buttons[2].tooltipText).toBe("Feedback");
|
expect(buttons[2].tooltipText).toBe("Dark Theme");
|
||||||
|
expect(buttons[3].tooltipText).toBe("Feedback");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Button click handlers", () => {
|
describe("Button click handlers", () => {
|
||||||
it("should execute click handlers without errors", () => {
|
it("should execute click handlers without errors", () => {
|
||||||
const buttons = getCommandBarButtons(mockExplorer);
|
const buttons = getCommandBarButtons(mockExplorer, false);
|
||||||
|
|
||||||
buttons.forEach((button) => {
|
buttons.forEach((button) => {
|
||||||
expect(() => button.onCommandClick({} as React.SyntheticEvent)).not.toThrow();
|
expect(() => button.onCommandClick({} as React.SyntheticEvent)).not.toThrow();
|
||||||
@@ -221,7 +225,7 @@ describe("CommandBar Utils", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should call correct action for each button", () => {
|
it("should call correct action for each button", () => {
|
||||||
const buttons = getCommandBarButtons(mockExplorer);
|
const buttons = getCommandBarButtons(mockExplorer, false);
|
||||||
|
|
||||||
buttons[0].onCommandClick({} as React.SyntheticEvent);
|
buttons[0].onCommandClick({} as React.SyntheticEvent);
|
||||||
expect(Actions.openCreateCopyJobPanel).toHaveBeenCalledWith(mockExplorer);
|
expect(Actions.openCreateCopyJobPanel).toHaveBeenCalledWith(mockExplorer);
|
||||||
@@ -229,14 +233,14 @@ describe("CommandBar Utils", () => {
|
|||||||
buttons[1].onCommandClick({} as React.SyntheticEvent);
|
buttons[1].onCommandClick({} as React.SyntheticEvent);
|
||||||
expect(mockRefreshJobList).toHaveBeenCalled();
|
expect(mockRefreshJobList).toHaveBeenCalled();
|
||||||
|
|
||||||
buttons[2].onCommandClick({} as React.SyntheticEvent);
|
buttons[3].onCommandClick({} as React.SyntheticEvent);
|
||||||
expect(mockOpenContainerCopyFeedbackBlade).toHaveBeenCalled();
|
expect(mockOpenContainerCopyFeedbackBlade).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Accessibility", () => {
|
describe("Accessibility", () => {
|
||||||
it("should have aria labels for all buttons", () => {
|
it("should have aria labels for all buttons", () => {
|
||||||
const buttons = getCommandBarButtons(mockExplorer);
|
const buttons = getCommandBarButtons(mockExplorer, false);
|
||||||
|
|
||||||
buttons.forEach((button) => {
|
buttons.forEach((button) => {
|
||||||
expect(button.ariaLabel).toBeDefined();
|
expect(button.ariaLabel).toBeDefined();
|
||||||
@@ -246,7 +250,7 @@ describe("CommandBar Utils", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should have tooltip text for all buttons", () => {
|
it("should have tooltip text for all buttons", () => {
|
||||||
const buttons = getCommandBarButtons(mockExplorer);
|
const buttons = getCommandBarButtons(mockExplorer, false);
|
||||||
|
|
||||||
buttons.forEach((button) => {
|
buttons.forEach((button) => {
|
||||||
expect(button.tooltipText).toBeDefined();
|
expect(button.tooltipText).toBeDefined();
|
||||||
@@ -256,7 +260,7 @@ describe("CommandBar Utils", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should have icon alt text for all buttons", () => {
|
it("should have icon alt text for all buttons", () => {
|
||||||
const buttons = getCommandBarButtons(mockExplorer);
|
const buttons = getCommandBarButtons(mockExplorer, false);
|
||||||
|
|
||||||
buttons.forEach((button) => {
|
buttons.forEach((button) => {
|
||||||
expect(button.iconAlt).toBeDefined();
|
expect(button.iconAlt).toBeDefined();
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import AddIcon from "../../../../images/Add.svg";
|
import AddIcon from "../../../../images/Add.svg";
|
||||||
import FeedbackIcon from "../../../../images/Feedback-Command.svg";
|
import FeedbackIcon from "../../../../images/Feedback-Command.svg";
|
||||||
|
import MoonIcon from "../../../../images/MoonIcon.svg";
|
||||||
import RefreshIcon from "../../../../images/refresh-cosmos.svg";
|
import RefreshIcon from "../../../../images/refresh-cosmos.svg";
|
||||||
|
import SunIcon from "../../../../images/SunIcon.svg";
|
||||||
import { configContext, Platform } from "../../../ConfigContext";
|
import { configContext, Platform } from "../../../ConfigContext";
|
||||||
|
import { useThemeStore } from "../../../hooks/useTheme";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import * as Actions from "../Actions/CopyJobActions";
|
import * as Actions from "../Actions/CopyJobActions";
|
||||||
@@ -9,7 +12,7 @@ import ContainerCopyMessages from "../ContainerCopyMessages";
|
|||||||
import { MonitorCopyJobsRefState } from "../MonitorCopyJobs/MonitorCopyJobRefState";
|
import { MonitorCopyJobsRefState } from "../MonitorCopyJobs/MonitorCopyJobRefState";
|
||||||
import { CopyJobCommandBarBtnType } from "../Types/CopyJobTypes";
|
import { CopyJobCommandBarBtnType } from "../Types/CopyJobTypes";
|
||||||
|
|
||||||
function getCopyJobBtns(explorer: Explorer): CopyJobCommandBarBtnType[] {
|
function getCopyJobBtns(explorer: Explorer, isDarkMode: boolean): CopyJobCommandBarBtnType[] {
|
||||||
const monitorCopyJobsRef = MonitorCopyJobsRefState((state) => state.ref);
|
const monitorCopyJobsRef = MonitorCopyJobsRefState((state) => state.ref);
|
||||||
const buttons: CopyJobCommandBarBtnType[] = [
|
const buttons: CopyJobCommandBarBtnType[] = [
|
||||||
{
|
{
|
||||||
@@ -26,7 +29,15 @@ function getCopyJobBtns(explorer: Explorer): CopyJobCommandBarBtnType[] {
|
|||||||
ariaLabel: ContainerCopyMessages.refreshButtonAriaLabel,
|
ariaLabel: ContainerCopyMessages.refreshButtonAriaLabel,
|
||||||
onClick: () => monitorCopyJobsRef?.refreshJobList(),
|
onClick: () => monitorCopyJobsRef?.refreshJobList(),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "themeToggle",
|
||||||
|
iconSrc: isDarkMode ? SunIcon : MoonIcon,
|
||||||
|
label: isDarkMode ? "Light Theme" : "Dark Theme",
|
||||||
|
ariaLabel: isDarkMode ? "Switch to Light Theme" : "Switch to Dark Theme",
|
||||||
|
onClick: () => useThemeStore.getState().toggleTheme(),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (configContext.platform === Platform.Portal) {
|
if (configContext.platform === Platform.Portal) {
|
||||||
buttons.push({
|
buttons.push({
|
||||||
key: "feedback",
|
key: "feedback",
|
||||||
@@ -54,6 +65,6 @@ function btnMapper(config: CopyJobCommandBarBtnType): CommandButtonComponentProp
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCommandBarButtons(explorer: Explorer): CommandButtonComponentProps[] {
|
export function getCommandBarButtons(explorer: Explorer, isDarkMode: boolean): CommandButtonComponentProps[] {
|
||||||
return getCopyJobBtns(explorer).map(btnMapper);
|
return getCopyJobBtns(explorer, isDarkMode).map(btnMapper);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,10 +162,10 @@ export default {
|
|||||||
viewDetails: "View Details",
|
viewDetails: "View Details",
|
||||||
},
|
},
|
||||||
Status: {
|
Status: {
|
||||||
Pending: "Pending",
|
Pending: "Queued",
|
||||||
InProgress: "In Progress",
|
InProgress: "Running",
|
||||||
Running: "In Progress",
|
Running: "Running",
|
||||||
Partitioning: "In Progress",
|
Partitioning: "Running",
|
||||||
Paused: "Paused",
|
Paused: "Paused",
|
||||||
Completed: "Completed",
|
Completed: "Completed",
|
||||||
Failed: "Failed",
|
Failed: "Failed",
|
||||||
|
|||||||
@@ -59,15 +59,8 @@ describe("CopyJobContext", () => {
|
|||||||
jobName: "",
|
jobName: "",
|
||||||
migrationType: CopyJobMigrationType.Offline,
|
migrationType: CopyJobMigrationType.Offline,
|
||||||
source: {
|
source: {
|
||||||
subscription: {
|
subscription: null,
|
||||||
subscriptionId: "test-subscription-id",
|
account: null,
|
||||||
},
|
|
||||||
account: {
|
|
||||||
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDB/databaseAccounts/test-account",
|
|
||||||
name: "test-account",
|
|
||||||
location: "East US",
|
|
||||||
kind: "GlobalDocumentDB",
|
|
||||||
},
|
|
||||||
databaseId: "",
|
databaseId: "",
|
||||||
containerId: "",
|
containerId: "",
|
||||||
},
|
},
|
||||||
@@ -605,8 +598,8 @@ describe("CopyJobContext", () => {
|
|||||||
</CopyJobContextProvider>,
|
</CopyJobContextProvider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(contextValue.copyJobState.source.subscription.subscriptionId).toBe("test-subscription-id");
|
expect(contextValue.copyJobState.source?.subscription?.subscriptionId).toBeUndefined();
|
||||||
expect(contextValue.copyJobState.source.account.name).toBe("test-account");
|
expect(contextValue.copyJobState.source?.account?.name).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should initialize target with userContext values", () => {
|
it("should initialize target with userContext values", () => {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { Subscription } from "Contracts/DataModels";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { CopyJobMigrationType } from "../Enums/CopyJobEnums";
|
import { CopyJobMigrationType } from "../Enums/CopyJobEnums";
|
||||||
@@ -24,10 +23,8 @@ const getInitialCopyJobState = (): CopyJobContextState => {
|
|||||||
jobName: "",
|
jobName: "",
|
||||||
migrationType: CopyJobMigrationType.Offline,
|
migrationType: CopyJobMigrationType.Offline,
|
||||||
source: {
|
source: {
|
||||||
subscription: {
|
subscription: null,
|
||||||
subscriptionId: userContext.subscriptionId || "",
|
account: null,
|
||||||
} as Subscription,
|
|
||||||
account: userContext.databaseAccount || null,
|
|
||||||
databaseId: "",
|
databaseId: "",
|
||||||
containerId: "",
|
containerId: "",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ export function isEqual(prevJobs: CopyJobType[], newJobs: CopyJobType[]): boolea
|
|||||||
}
|
}
|
||||||
|
|
||||||
const truncateLength = 5;
|
const truncateLength = 5;
|
||||||
const truncateName = (name: string, length: number = truncateLength): string => {
|
export const truncateName = (name: string, length: number = truncateLength): string => {
|
||||||
return name.length <= length ? name : name.slice(0, length);
|
return name.length <= length ? name : name.slice(0, length);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,12 @@ import useToggle from "./hooks/useToggle";
|
|||||||
const managedIdentityTooltip = (
|
const managedIdentityTooltip = (
|
||||||
<Text>
|
<Text>
|
||||||
{ContainerCopyMessages.addManagedIdentity.tooltip.content}
|
{ContainerCopyMessages.addManagedIdentity.tooltip.content}
|
||||||
<Link href={ContainerCopyMessages.addManagedIdentity.tooltip.href} target="_blank" rel="noopener noreferrer">
|
<Link
|
||||||
|
style={{ color: "var(--colorBrandForeground1)" }}
|
||||||
|
href={ContainerCopyMessages.addManagedIdentity.tooltip.href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
{ContainerCopyMessages.addManagedIdentity.tooltip.hrefText}
|
{ContainerCopyMessages.addManagedIdentity.tooltip.hrefText}
|
||||||
</Link>
|
</Link>
|
||||||
</Text>
|
</Text>
|
||||||
@@ -26,7 +31,7 @@ const AddManagedIdentity: React.FC<AddManagedIdentityProps> = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack className="addManagedIdentityContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}>
|
<Stack className="addManagedIdentityContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}>
|
||||||
<Text>
|
<Text className="themeText">
|
||||||
{ContainerCopyMessages.addManagedIdentity.description} 
|
{ContainerCopyMessages.addManagedIdentity.description} 
|
||||||
<Link href={ContainerCopyMessages.addManagedIdentity.descriptionHref} target="_blank" rel="noopener noreferrer">
|
<Link href={ContainerCopyMessages.addManagedIdentity.descriptionHref} target="_blank" rel="noopener noreferrer">
|
||||||
{ContainerCopyMessages.addManagedIdentity.descriptionHrefText}
|
{ContainerCopyMessages.addManagedIdentity.descriptionHrefText}
|
||||||
@@ -35,6 +40,7 @@ const AddManagedIdentity: React.FC<AddManagedIdentityProps> = () => {
|
|||||||
<InfoTooltip content={managedIdentityTooltip} />
|
<InfoTooltip content={managedIdentityTooltip} />
|
||||||
</Text>
|
</Text>
|
||||||
<Toggle
|
<Toggle
|
||||||
|
data-test="btn-toggle"
|
||||||
checked={systemAssigned}
|
checked={systemAssigned}
|
||||||
onText={ContainerCopyMessages.toggleBtn.onText}
|
onText={ContainerCopyMessages.toggleBtn.onText}
|
||||||
offText={ContainerCopyMessages.toggleBtn.offText}
|
offText={ContainerCopyMessages.toggleBtn.offText}
|
||||||
|
|||||||
@@ -13,7 +13,12 @@ import useToggle from "./hooks/useToggle";
|
|||||||
const TooltipContent = (
|
const TooltipContent = (
|
||||||
<Text>
|
<Text>
|
||||||
{ContainerCopyMessages.readPermissionAssigned.tooltip.content}
|
{ContainerCopyMessages.readPermissionAssigned.tooltip.content}
|
||||||
<Link href={ContainerCopyMessages.readPermissionAssigned.tooltip.href} target="_blank" rel="noopener noreferrer">
|
<Link
|
||||||
|
style={{ color: "var(--colorBrandForeground1)" }}
|
||||||
|
href={ContainerCopyMessages.readPermissionAssigned.tooltip.href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
{ContainerCopyMessages.readPermissionAssigned.tooltip.hrefText}
|
{ContainerCopyMessages.readPermissionAssigned.tooltip.hrefText}
|
||||||
</Link>
|
</Link>
|
||||||
</Text>
|
</Text>
|
||||||
@@ -65,6 +70,7 @@ const AddReadPermissionToDefaultIdentity: React.FC<AddReadPermissionToDefaultIde
|
|||||||
<InfoTooltip content={TooltipContent} />
|
<InfoTooltip content={TooltipContent} />
|
||||||
</Text>
|
</Text>
|
||||||
<Toggle
|
<Toggle
|
||||||
|
data-test="btn-toggle"
|
||||||
checked={readPermissionAssigned}
|
checked={readPermissionAssigned}
|
||||||
onText={ContainerCopyMessages.toggleBtn.onText}
|
onText={ContainerCopyMessages.toggleBtn.onText}
|
||||||
offText={ContainerCopyMessages.toggleBtn.offText}
|
offText={ContainerCopyMessages.toggleBtn.offText}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { useCopyJobPrerequisitesCache } from "../../Utils/useCopyJobPrerequisite
|
|||||||
import usePermissionSections, { PermissionGroupConfig, PermissionSectionConfig } from "./hooks/usePermissionsSection";
|
import usePermissionSections, { PermissionGroupConfig, PermissionSectionConfig } from "./hooks/usePermissionsSection";
|
||||||
|
|
||||||
const PermissionSection: React.FC<PermissionSectionConfig> = ({ id, title, Component, completed, disabled }) => (
|
const PermissionSection: React.FC<PermissionSectionConfig> = ({ id, title, Component, completed, disabled }) => (
|
||||||
<AccordionItem key={id} value={id} disabled={disabled}>
|
<AccordionItem key={id} value={id} disabled={disabled} data-test="accordion-item">
|
||||||
<AccordionHeader className="accordionHeader">
|
<AccordionHeader className="accordionHeader">
|
||||||
<Text className="accordionHeaderText" variant="medium">
|
<Text className="accordionHeaderText" variant="medium">
|
||||||
{title}
|
{title}
|
||||||
@@ -25,13 +25,13 @@ const PermissionSection: React.FC<PermissionSectionConfig> = ({ id, title, Compo
|
|||||||
height={completed ? 20 : 24}
|
height={completed ? 20 : 24}
|
||||||
/>
|
/>
|
||||||
</AccordionHeader>
|
</AccordionHeader>
|
||||||
<AccordionPanel aria-disabled={disabled} className="accordionPanel">
|
<AccordionPanel aria-disabled={disabled} className="accordionPanel" data-test="accordion-panel">
|
||||||
<Component />
|
<Component />
|
||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
);
|
);
|
||||||
|
|
||||||
const PermissionGroup: React.FC<PermissionGroupConfig> = ({ title, description, sections }) => {
|
const PermissionGroup: React.FC<PermissionGroupConfig> = ({ id, title, description, sections }) => {
|
||||||
const [openItems, setOpenItems] = React.useState<string[]>([]);
|
const [openItems, setOpenItems] = React.useState<string[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -44,11 +44,12 @@ const PermissionGroup: React.FC<PermissionGroupConfig> = ({ title, description,
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
|
data-test={`permission-group-container-${id}`}
|
||||||
tokens={{ childrenGap: 15 }}
|
tokens={{ childrenGap: 15 }}
|
||||||
styles={{
|
styles={{
|
||||||
root: {
|
root: {
|
||||||
background: "#fafafa",
|
background: "var(--colorNeutralBackground2)",
|
||||||
border: "1px solid #e1e1e1",
|
border: "1px solid var(--colorNeutralStroke1)",
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
padding: 16,
|
padding: 16,
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
||||||
@@ -56,11 +57,11 @@ const PermissionGroup: React.FC<PermissionGroupConfig> = ({ title, description,
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack tokens={{ childrenGap: 5 }}>
|
<Stack tokens={{ childrenGap: 5 }}>
|
||||||
<Text variant="medium" style={{ fontWeight: 600 }}>
|
<Text variant="medium" style={{ fontWeight: 600, color: "var(--colorNeutralForeground1)" }}>
|
||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
{description && (
|
{description && (
|
||||||
<Text variant="small" styles={{ root: { color: "#605E5C" } }}>
|
<Text variant="small" styles={{ root: { color: "var(--colorNeutralForeground2)" } }}>
|
||||||
{description}
|
{description}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
@@ -99,8 +100,12 @@ const AssignPermissions = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack className="assignPermissionsContainer" tokens={{ childrenGap: 20 }}>
|
<Stack
|
||||||
<Text variant="medium">
|
data-test="Panel:AssignPermissionsContainer"
|
||||||
|
className="assignPermissionsContainer"
|
||||||
|
tokens={{ childrenGap: 20 }}
|
||||||
|
>
|
||||||
|
<Text variant="medium" style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||||
{isSameAccount && copyJobState.migrationType === CopyJobMigrationType.Online
|
{isSameAccount && copyJobState.migrationType === CopyJobMigrationType.Online
|
||||||
? ContainerCopyMessages.assignPermissions.intraAccountOnlineDescription(
|
? ContainerCopyMessages.assignPermissions.intraAccountOnlineDescription(
|
||||||
copyJobState?.source?.account?.name || "",
|
copyJobState?.source?.account?.name || "",
|
||||||
|
|||||||
@@ -12,7 +12,12 @@ import useToggle from "./hooks/useToggle";
|
|||||||
const managedIdentityTooltip = (
|
const managedIdentityTooltip = (
|
||||||
<Text>
|
<Text>
|
||||||
{ContainerCopyMessages.defaultManagedIdentity.tooltip.content}
|
{ContainerCopyMessages.defaultManagedIdentity.tooltip.content}
|
||||||
<Link href={ContainerCopyMessages.defaultManagedIdentity.tooltip.href} target="_blank" rel="noopener noreferrer">
|
<Link
|
||||||
|
style={{ color: "var(--colorBrandForeground1)" }}
|
||||||
|
href={ContainerCopyMessages.defaultManagedIdentity.tooltip.href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
{ContainerCopyMessages.defaultManagedIdentity.tooltip.hrefText}
|
{ContainerCopyMessages.defaultManagedIdentity.tooltip.hrefText}
|
||||||
</Link>
|
</Link>
|
||||||
</Text>
|
</Text>
|
||||||
@@ -31,6 +36,7 @@ const DefaultManagedIdentity: React.FC<AddManagedIdentityProps> = () => {
|
|||||||
<InfoTooltip content={managedIdentityTooltip} />
|
<InfoTooltip content={managedIdentityTooltip} />
|
||||||
</div>
|
</div>
|
||||||
<Toggle
|
<Toggle
|
||||||
|
data-test="btn-toggle"
|
||||||
checked={defaultSystemAssigned}
|
checked={defaultSystemAssigned}
|
||||||
onText={ContainerCopyMessages.toggleBtn.onText}
|
onText={ContainerCopyMessages.toggleBtn.onText}
|
||||||
offText={ContainerCopyMessages.toggleBtn.offText}
|
offText={ContainerCopyMessages.toggleBtn.offText}
|
||||||
|
|||||||
@@ -13,7 +13,12 @@ import InfoTooltip from "../Components/InfoTooltip";
|
|||||||
const tooltipContent = (
|
const tooltipContent = (
|
||||||
<Text>
|
<Text>
|
||||||
{ContainerCopyMessages.pointInTimeRestore.tooltip.content}
|
{ContainerCopyMessages.pointInTimeRestore.tooltip.content}
|
||||||
<Link href={ContainerCopyMessages.pointInTimeRestore.tooltip.href} target="_blank" rel="noopener noreferrer">
|
<Link
|
||||||
|
style={{ color: "var(--colorBrandForeground1)" }}
|
||||||
|
href={ContainerCopyMessages.pointInTimeRestore.tooltip.href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
{ContainerCopyMessages.pointInTimeRestore.tooltip.hrefText}
|
{ContainerCopyMessages.pointInTimeRestore.tooltip.hrefText}
|
||||||
</Link>
|
</Link>
|
||||||
</Text>
|
</Text>
|
||||||
@@ -127,6 +132,7 @@ const PointInTimeRestore: React.FC = () => {
|
|||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
{showRefreshButton ? (
|
{showRefreshButton ? (
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
|
data-test="pointInTimeRestore:RefreshBtn"
|
||||||
className="fullWidth"
|
className="fullWidth"
|
||||||
text={ContainerCopyMessages.refreshButtonLabel}
|
text={ContainerCopyMessages.refreshButtonLabel}
|
||||||
iconProps={{ iconName: "Refresh" }}
|
iconProps={{ iconName: "Refresh" }}
|
||||||
@@ -134,6 +140,7 @@ const PointInTimeRestore: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
|
data-test="pointInTimeRestore:PrimaryBtn"
|
||||||
className="fullWidth"
|
className="fullWidth"
|
||||||
text={loading ? "" : ContainerCopyMessages.pointInTimeRestore.buttonText}
|
text={loading ? "" : ContainerCopyMessages.pointInTimeRestore.buttonText}
|
||||||
{...(loading ? { iconProps: { iconName: "SyncStatusSolid" } } : {})}
|
{...(loading ? { iconProps: { iconName: "SyncStatusSolid" } } : {})}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ exports[`AddManagedIdentity Snapshot Tests renders initial state correctly 1`] =
|
|||||||
class="ms-Stack addManagedIdentityContainer css-109"
|
class="ms-Stack addManagedIdentityContainer css-109"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="themeText css-110"
|
||||||
>
|
>
|
||||||
A system-assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. Once enabled, you can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you don’t have to store any credentials in code.
|
A system-assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. Once enabled, you can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you don’t have to store any credentials in code.
|
||||||
|
|
||||||
@@ -67,6 +67,7 @@ exports[`AddManagedIdentity Snapshot Tests renders initial state correctly 1`] =
|
|||||||
class="ms-Toggle-background pill-117"
|
class="ms-Toggle-background pill-117"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="btn-toggle"
|
||||||
id="Toggle1"
|
id="Toggle1"
|
||||||
role="switch"
|
role="switch"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -92,7 +93,7 @@ exports[`AddManagedIdentity Snapshot Tests renders loading state 1`] = `
|
|||||||
class="ms-Stack addManagedIdentityContainer css-109"
|
class="ms-Stack addManagedIdentityContainer css-109"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="themeText css-110"
|
||||||
>
|
>
|
||||||
A system-assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. Once enabled, you can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you don’t have to store any credentials in code.
|
A system-assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. Once enabled, you can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you don’t have to store any credentials in code.
|
||||||
|
|
||||||
@@ -154,6 +155,7 @@ exports[`AddManagedIdentity Snapshot Tests renders loading state 1`] = `
|
|||||||
class="ms-Toggle-background pill-121"
|
class="ms-Toggle-background pill-121"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="btn-toggle"
|
||||||
id="Toggle11"
|
id="Toggle11"
|
||||||
role="switch"
|
role="switch"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -173,10 +175,12 @@ exports[`AddManagedIdentity Snapshot Tests renders loading state 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack popover-container foreground loading css-123"
|
class="ms-Stack popover-container foreground loading css-123"
|
||||||
|
data-test="popover-container"
|
||||||
style="max-width: 450px;"
|
style="max-width: 450px;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Overlay root-135"
|
class="ms-Overlay root-135"
|
||||||
|
data-test="loading-overlay"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Spinner root-137"
|
class="ms-Spinner root-137"
|
||||||
@@ -192,13 +196,13 @@ exports[`AddManagedIdentity Snapshot Tests renders loading state 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
class="css-124"
|
class="themeText css-124"
|
||||||
style="font-weight: 600;"
|
style="font-weight: 600;"
|
||||||
>
|
>
|
||||||
Enable system assigned managed identity
|
Enable system assigned managed identity
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="themeText css-110"
|
||||||
>
|
>
|
||||||
Enable system-assigned managed identity on the test-target-account. To confirm, click the "Yes" button.
|
Enable system-assigned managed identity on the test-target-account. To confirm, click the "Yes" button.
|
||||||
</span>
|
</span>
|
||||||
@@ -261,7 +265,7 @@ exports[`AddManagedIdentity Snapshot Tests renders with toggle on and popover vi
|
|||||||
class="ms-Stack addManagedIdentityContainer css-109"
|
class="ms-Stack addManagedIdentityContainer css-109"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="themeText css-110"
|
||||||
>
|
>
|
||||||
A system-assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. Once enabled, you can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you don’t have to store any credentials in code.
|
A system-assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. Once enabled, you can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you don’t have to store any credentials in code.
|
||||||
|
|
||||||
@@ -323,6 +327,7 @@ exports[`AddManagedIdentity Snapshot Tests renders with toggle on and popover vi
|
|||||||
class="ms-Toggle-background pill-121"
|
class="ms-Toggle-background pill-121"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="btn-toggle"
|
||||||
id="Toggle3"
|
id="Toggle3"
|
||||||
role="switch"
|
role="switch"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -342,16 +347,17 @@ exports[`AddManagedIdentity Snapshot Tests renders with toggle on and popover vi
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack popover-container foreground css-123"
|
class="ms-Stack popover-container foreground css-123"
|
||||||
|
data-test="popover-container"
|
||||||
style="max-width: 450px;"
|
style="max-width: 450px;"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-124"
|
class="themeText css-124"
|
||||||
style="font-weight: 600;"
|
style="font-weight: 600;"
|
||||||
>
|
>
|
||||||
Enable system assigned managed identity
|
Enable system assigned managed identity
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="themeText css-110"
|
||||||
>
|
>
|
||||||
Enable system-assigned managed identity on the test-target-account. To confirm, click the "Yes" button.
|
Enable system-assigned managed identity on the test-target-account. To confirm, click the "Yes" button.
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle m
|
|||||||
class="ms-Toggle-background pill-115"
|
class="ms-Toggle-background pill-115"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="btn-toggle"
|
||||||
id="Toggle17"
|
id="Toggle17"
|
||||||
role="switch"
|
role="switch"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -103,6 +104,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle m
|
|||||||
class="ms-Toggle-background pill-115"
|
class="ms-Toggle-background pill-115"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="btn-toggle"
|
||||||
id="Toggle16"
|
id="Toggle16"
|
||||||
role="switch"
|
role="switch"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -165,6 +167,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
|
|||||||
class="ms-Toggle-background pill-115"
|
class="ms-Toggle-background pill-115"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="btn-toggle"
|
||||||
id="Toggle3"
|
id="Toggle3"
|
||||||
role="switch"
|
role="switch"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -227,6 +230,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
|
|||||||
class="ms-Toggle-background pill-119"
|
class="ms-Toggle-background pill-119"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="btn-toggle"
|
||||||
id="Toggle1"
|
id="Toggle1"
|
||||||
role="switch"
|
role="switch"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -314,6 +318,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
|
|||||||
class="ms-Toggle-background pill-115"
|
class="ms-Toggle-background pill-115"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="btn-toggle"
|
||||||
id="Toggle0"
|
id="Toggle0"
|
||||||
role="switch"
|
role="switch"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -376,6 +381,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
|
|||||||
class="ms-Toggle-background pill-115"
|
class="ms-Toggle-background pill-115"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="btn-toggle"
|
||||||
id="Toggle2"
|
id="Toggle2"
|
||||||
role="switch"
|
role="switch"
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ exports[`AssignPermissions Component Accordion Behavior should render accordion
|
|||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack assignPermissionsContainer css-109"
|
class="ms-Stack assignPermissionsContainer css-109"
|
||||||
|
data-test="Panel:AssignPermissionsContainer"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="css-110"
|
||||||
@@ -15,6 +16,7 @@ exports[`AssignPermissions Component Accordion Behavior should render accordion
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack css-112"
|
class="ms-Stack css-112"
|
||||||
|
data-test="permission-group-container-testGroup"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack css-113"
|
class="ms-Stack css-113"
|
||||||
@@ -36,6 +38,7 @@ exports[`AssignPermissions Component Accordion Behavior should render accordion
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionItem"
|
class="fui-AccordionItem"
|
||||||
|
data-test="accordion-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||||
@@ -85,6 +88,7 @@ exports[`AssignPermissions Component Accordion Behavior should render accordion
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionItem"
|
class="fui-AccordionItem"
|
||||||
|
data-test="accordion-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||||
@@ -134,6 +138,7 @@ exports[`AssignPermissions Component Accordion Behavior should render accordion
|
|||||||
<div
|
<div
|
||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
|
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
|
||||||
|
data-test="accordion-panel"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
Incomplete Component
|
Incomplete Component
|
||||||
@@ -142,6 +147,7 @@ exports[`AssignPermissions Component Accordion Behavior should render accordion
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionItem"
|
class="fui-AccordionItem"
|
||||||
|
data-test="accordion-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionHeader accordionHeader ___lyghz50_53x5ri0 f1s2aq7o f1c21dwh f1s184ao ft85np5 fwrgwhw"
|
class="fui-AccordionHeader accordionHeader ___lyghz50_53x5ri0 f1s2aq7o f1c21dwh f1s184ao ft85np5 fwrgwhw"
|
||||||
@@ -201,6 +207,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
|||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack assignPermissionsContainer css-109"
|
class="ms-Stack assignPermissionsContainer css-109"
|
||||||
|
data-test="Panel:AssignPermissionsContainer"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="css-110"
|
||||||
@@ -212,6 +219,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack css-112"
|
class="ms-Stack css-112"
|
||||||
|
data-test="permission-group-container-testGroup"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack css-113"
|
class="ms-Stack css-113"
|
||||||
@@ -233,6 +241,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionItem"
|
class="fui-AccordionItem"
|
||||||
|
data-test="accordion-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||||
@@ -282,6 +291,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionItem"
|
class="fui-AccordionItem"
|
||||||
|
data-test="accordion-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||||
@@ -331,6 +341,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
|||||||
<div
|
<div
|
||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
|
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
|
||||||
|
data-test="accordion-panel"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
Incomplete Component
|
Incomplete Component
|
||||||
@@ -339,6 +350,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionItem"
|
class="fui-AccordionItem"
|
||||||
|
data-test="accordion-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionHeader accordionHeader ___lyghz50_53x5ri0 f1s2aq7o f1c21dwh f1s184ao ft85np5 fwrgwhw"
|
class="fui-AccordionHeader accordionHeader ___lyghz50_53x5ri0 f1s2aq7o f1c21dwh f1s184ao ft85np5 fwrgwhw"
|
||||||
@@ -398,6 +410,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
|||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack assignPermissionsContainer css-109"
|
class="ms-Stack assignPermissionsContainer css-109"
|
||||||
|
data-test="Panel:AssignPermissionsContainer"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="css-110"
|
||||||
@@ -409,6 +422,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack css-112"
|
class="ms-Stack css-112"
|
||||||
|
data-test="permission-group-container-testGroup"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack css-113"
|
class="ms-Stack css-113"
|
||||||
@@ -430,6 +444,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionItem"
|
class="fui-AccordionItem"
|
||||||
|
data-test="accordion-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||||
@@ -479,6 +494,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionItem"
|
class="fui-AccordionItem"
|
||||||
|
data-test="accordion-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||||
@@ -528,6 +544,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
|||||||
<div
|
<div
|
||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
|
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
|
||||||
|
data-test="accordion-panel"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
Incomplete Component
|
Incomplete Component
|
||||||
@@ -536,6 +553,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionItem"
|
class="fui-AccordionItem"
|
||||||
|
data-test="accordion-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionHeader accordionHeader ___lyghz50_53x5ri0 f1s2aq7o f1c21dwh f1s184ao ft85np5 fwrgwhw"
|
class="fui-AccordionHeader accordionHeader ___lyghz50_53x5ri0 f1s2aq7o f1c21dwh f1s184ao ft85np5 fwrgwhw"
|
||||||
@@ -595,6 +613,7 @@ exports[`AssignPermissions Component Edge Cases should handle missing account na
|
|||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack assignPermissionsContainer css-109"
|
class="ms-Stack assignPermissionsContainer css-109"
|
||||||
|
data-test="Panel:AssignPermissionsContainer"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="css-110"
|
||||||
@@ -606,6 +625,7 @@ exports[`AssignPermissions Component Edge Cases should handle missing account na
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack css-112"
|
class="ms-Stack css-112"
|
||||||
|
data-test="permission-group-container-testGroup"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack css-113"
|
class="ms-Stack css-113"
|
||||||
@@ -627,6 +647,7 @@ exports[`AssignPermissions Component Edge Cases should handle missing account na
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionItem"
|
class="fui-AccordionItem"
|
||||||
|
data-test="accordion-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||||
@@ -676,6 +697,7 @@ exports[`AssignPermissions Component Edge Cases should handle missing account na
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionItem"
|
class="fui-AccordionItem"
|
||||||
|
data-test="accordion-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||||
@@ -725,6 +747,7 @@ exports[`AssignPermissions Component Edge Cases should handle missing account na
|
|||||||
<div
|
<div
|
||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
|
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
|
||||||
|
data-test="accordion-panel"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
Incomplete Component
|
Incomplete Component
|
||||||
@@ -733,6 +756,7 @@ exports[`AssignPermissions Component Edge Cases should handle missing account na
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionItem"
|
class="fui-AccordionItem"
|
||||||
|
data-test="accordion-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionHeader accordionHeader ___lyghz50_53x5ri0 f1s2aq7o f1c21dwh f1s184ao ft85np5 fwrgwhw"
|
class="fui-AccordionHeader accordionHeader ___lyghz50_53x5ri0 f1s2aq7o f1c21dwh f1s184ao ft85np5 fwrgwhw"
|
||||||
@@ -792,6 +816,7 @@ exports[`AssignPermissions Component Permission Groups should render multiple pe
|
|||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack assignPermissionsContainer css-109"
|
class="ms-Stack assignPermissionsContainer css-109"
|
||||||
|
data-test="Panel:AssignPermissionsContainer"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="css-110"
|
||||||
@@ -803,6 +828,7 @@ exports[`AssignPermissions Component Permission Groups should render multiple pe
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack css-112"
|
class="ms-Stack css-112"
|
||||||
|
data-test="permission-group-container-crossAccountConfigs"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack css-113"
|
class="ms-Stack css-113"
|
||||||
@@ -824,6 +850,7 @@ exports[`AssignPermissions Component Permission Groups should render multiple pe
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionItem"
|
class="fui-AccordionItem"
|
||||||
|
data-test="accordion-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||||
@@ -875,6 +902,7 @@ exports[`AssignPermissions Component Permission Groups should render multiple pe
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack css-112"
|
class="ms-Stack css-112"
|
||||||
|
data-test="permission-group-container-onlineConfigs"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack css-113"
|
class="ms-Stack css-113"
|
||||||
@@ -896,6 +924,7 @@ exports[`AssignPermissions Component Permission Groups should render multiple pe
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionItem"
|
class="fui-AccordionItem"
|
||||||
|
data-test="accordion-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||||
@@ -945,6 +974,7 @@ exports[`AssignPermissions Component Permission Groups should render multiple pe
|
|||||||
<div
|
<div
|
||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
|
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
|
||||||
|
data-test="accordion-panel"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
data-testid="online-copy-enabled"
|
data-testid="online-copy-enabled"
|
||||||
@@ -964,6 +994,7 @@ exports[`AssignPermissions Component Permission Groups should render online migr
|
|||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack assignPermissionsContainer css-109"
|
class="ms-Stack assignPermissionsContainer css-109"
|
||||||
|
data-test="Panel:AssignPermissionsContainer"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="css-110"
|
||||||
@@ -975,6 +1006,7 @@ exports[`AssignPermissions Component Permission Groups should render online migr
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack css-112"
|
class="ms-Stack css-112"
|
||||||
|
data-test="permission-group-container-onlineConfigs"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack css-113"
|
class="ms-Stack css-113"
|
||||||
@@ -996,6 +1028,7 @@ exports[`AssignPermissions Component Permission Groups should render online migr
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionItem"
|
class="fui-AccordionItem"
|
||||||
|
data-test="accordion-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||||
@@ -1045,6 +1078,7 @@ exports[`AssignPermissions Component Permission Groups should render online migr
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionItem"
|
class="fui-AccordionItem"
|
||||||
|
data-test="accordion-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||||
@@ -1094,6 +1128,7 @@ exports[`AssignPermissions Component Permission Groups should render online migr
|
|||||||
<div
|
<div
|
||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
|
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
|
||||||
|
data-test="accordion-panel"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
data-testid="online-copy-enabled"
|
data-testid="online-copy-enabled"
|
||||||
@@ -1113,6 +1148,7 @@ exports[`AssignPermissions Component Permission Groups should render permission
|
|||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack assignPermissionsContainer css-109"
|
class="ms-Stack assignPermissionsContainer css-109"
|
||||||
|
data-test="Panel:AssignPermissionsContainer"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="css-110"
|
||||||
@@ -1124,6 +1160,7 @@ exports[`AssignPermissions Component Permission Groups should render permission
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack css-112"
|
class="ms-Stack css-112"
|
||||||
|
data-test="permission-group-container-crossAccountConfigs"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack css-113"
|
class="ms-Stack css-113"
|
||||||
@@ -1145,6 +1182,7 @@ exports[`AssignPermissions Component Permission Groups should render permission
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionItem"
|
class="fui-AccordionItem"
|
||||||
|
data-test="accordion-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||||
@@ -1194,6 +1232,7 @@ exports[`AssignPermissions Component Permission Groups should render permission
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionItem"
|
class="fui-AccordionItem"
|
||||||
|
data-test="accordion-item"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
|
||||||
@@ -1243,6 +1282,7 @@ exports[`AssignPermissions Component Permission Groups should render permission
|
|||||||
<div
|
<div
|
||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
|
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
|
||||||
|
data-test="accordion-panel"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
data-testid="add-read-permission"
|
data-testid="add-read-permission"
|
||||||
@@ -1262,6 +1302,7 @@ exports[`AssignPermissions Component Rendering should render without crashing wi
|
|||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack assignPermissionsContainer css-109"
|
class="ms-Stack assignPermissionsContainer css-109"
|
||||||
|
data-test="Panel:AssignPermissionsContainer"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="css-110"
|
||||||
@@ -1283,6 +1324,7 @@ exports[`AssignPermissions Component Rendering should render without crashing wi
|
|||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack assignPermissionsContainer css-109"
|
class="ms-Stack assignPermissionsContainer css-109"
|
||||||
|
data-test="Panel:AssignPermissionsContainer"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="css-110"
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ exports[`DefaultManagedIdentity Edge Cases should handle missing account name gr
|
|||||||
class="ms-Toggle-background pill-115"
|
class="ms-Toggle-background pill-115"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="btn-toggle"
|
||||||
id="Toggle14"
|
id="Toggle14"
|
||||||
role="switch"
|
role="switch"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -103,6 +104,7 @@ exports[`DefaultManagedIdentity Edge Cases should handle null account 1`] = `
|
|||||||
class="ms-Toggle-background pill-115"
|
class="ms-Toggle-background pill-115"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="btn-toggle"
|
||||||
id="Toggle15"
|
id="Toggle15"
|
||||||
role="switch"
|
role="switch"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -165,6 +167,7 @@ exports[`DefaultManagedIdentity Loading States should render loading state snaps
|
|||||||
class="ms-Toggle-background pill-119"
|
class="ms-Toggle-background pill-119"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="btn-toggle"
|
||||||
id="Toggle10"
|
id="Toggle10"
|
||||||
role="switch"
|
role="switch"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -256,6 +259,7 @@ exports[`DefaultManagedIdentity Rendering should render correctly with default s
|
|||||||
class="ms-Toggle-background pill-115"
|
class="ms-Toggle-background pill-115"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="btn-toggle"
|
||||||
id="Toggle0"
|
id="Toggle0"
|
||||||
role="switch"
|
role="switch"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -318,6 +322,7 @@ exports[`DefaultManagedIdentity Toggle Interactions should render toggle with ch
|
|||||||
class="ms-Toggle-background pill-119"
|
class="ms-Toggle-background pill-119"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="btn-toggle"
|
||||||
id="Toggle7"
|
id="Toggle7"
|
||||||
role="switch"
|
role="switch"
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ exports[`PointInTimeRestore Initial Render should render correctly with default
|
|||||||
<button
|
<button
|
||||||
class="ms-Button ms-Button--primary fullWidth root-115"
|
class="ms-Button ms-Button--primary fullWidth root-115"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
|
data-test="pointInTimeRestore:PrimaryBtn"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -131,6 +132,7 @@ exports[`PointInTimeRestore Initial Render should render with empty account name
|
|||||||
<button
|
<button
|
||||||
class="ms-Button ms-Button--primary fullWidth root-115"
|
class="ms-Button ms-Button--primary fullWidth root-115"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
|
data-test="pointInTimeRestore:PrimaryBtn"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -161,6 +163,7 @@ exports[`PointInTimeRestore Snapshots should match snapshot in loading state 1`]
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Overlay root-123"
|
class="ms-Overlay root-123"
|
||||||
|
data-test="loading-overlay"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Spinner root-125"
|
class="ms-Spinner root-125"
|
||||||
@@ -223,6 +226,7 @@ exports[`PointInTimeRestore Snapshots should match snapshot in loading state 1`]
|
|||||||
aria-disabled="true"
|
aria-disabled="true"
|
||||||
class="ms-Button ms-Button--primary is-disabled fullWidth root-128"
|
class="ms-Button ms-Button--primary is-disabled fullWidth root-128"
|
||||||
data-is-focusable="false"
|
data-is-focusable="false"
|
||||||
|
data-test="pointInTimeRestore:PrimaryBtn"
|
||||||
disabled=""
|
disabled=""
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@@ -301,6 +305,7 @@ exports[`PointInTimeRestore Snapshots should match snapshot with refresh button
|
|||||||
<button
|
<button
|
||||||
class="ms-Button ms-Button--primary fullWidth root-115"
|
class="ms-Button ms-Button--primary fullWidth root-115"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
|
data-test="pointInTimeRestore:RefreshBtn"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -19,9 +19,21 @@ const NavigationControls: React.FC<NavigationControlsProps> = ({
|
|||||||
isPreviousDisabled,
|
isPreviousDisabled,
|
||||||
}) => (
|
}) => (
|
||||||
<Stack horizontal tokens={{ childrenGap: 20 }}>
|
<Stack horizontal tokens={{ childrenGap: 20 }}>
|
||||||
<PrimaryButton text={primaryBtnText} onClick={onPrimary} allowDisabledFocus disabled={isPrimaryDisabled} />
|
<PrimaryButton
|
||||||
<DefaultButton text="Previous" onClick={onPrevious} allowDisabledFocus disabled={isPreviousDisabled} />
|
data-test="copy-job-primary"
|
||||||
<DefaultButton text="Cancel" onClick={onCancel} />
|
text={primaryBtnText}
|
||||||
|
onClick={onPrimary}
|
||||||
|
allowDisabledFocus
|
||||||
|
disabled={isPrimaryDisabled}
|
||||||
|
/>
|
||||||
|
<DefaultButton
|
||||||
|
data-test="copy-job-previous"
|
||||||
|
text="Previous"
|
||||||
|
onClick={onPrevious}
|
||||||
|
allowDisabledFocus
|
||||||
|
disabled={isPreviousDisabled}
|
||||||
|
/>
|
||||||
|
<DefaultButton data-test="copy-job-cancel" text="Cancel" onClick={onCancel} />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -17,15 +17,16 @@ const PopoverContainer: React.FC<PopoverContainerProps> = React.memo(
|
|||||||
({ isLoading = false, title, children, onPrimary, onCancel }) => {
|
({ isLoading = false, title, children, onPrimary, onCancel }) => {
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
|
data-test="popover-container"
|
||||||
className={`popover-container foreground ${isLoading ? "loading" : ""}`}
|
className={`popover-container foreground ${isLoading ? "loading" : ""}`}
|
||||||
tokens={{ childrenGap: 20 }}
|
tokens={{ childrenGap: 20 }}
|
||||||
style={{ maxWidth: 450 }}
|
style={{ maxWidth: 450 }}
|
||||||
>
|
>
|
||||||
<LoadingOverlay isLoading={isLoading} label={ContainerCopyMessages.popoverOverlaySpinnerLabel} />
|
<LoadingOverlay isLoading={isLoading} label={ContainerCopyMessages.popoverOverlaySpinnerLabel} />
|
||||||
<Text variant="mediumPlus" style={{ fontWeight: 600 }}>
|
<Text variant="mediumPlus" className="themeText" style={{ fontWeight: 600 }}>
|
||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
<Text>{children}</Text>
|
<Text className="themeText">{children}</Text>
|
||||||
<Stack horizontal tokens={{ childrenGap: 20 }}>
|
<Stack horizontal tokens={{ childrenGap: 20 }}>
|
||||||
<PrimaryButton text={"Yes"} onClick={onPrimary} disabled={isLoading} />
|
<PrimaryButton text={"Yes"} onClick={onPrimary} disabled={isLoading} />
|
||||||
<DefaultButton text="No" onClick={onCancel} disabled={isLoading} />
|
<DefaultButton text="No" onClick={onCancel} disabled={isLoading} />
|
||||||
|
|||||||
@@ -4,14 +4,15 @@ exports[`PopoverMessage Component Edge Cases should handle empty string title 1`
|
|||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack popover-container foreground css-109"
|
class="ms-Stack popover-container foreground css-109"
|
||||||
|
data-test="popover-container"
|
||||||
style="max-width: 450px;"
|
style="max-width: 450px;"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="themeText css-110"
|
||||||
style="font-weight: 600;"
|
style="font-weight: 600;"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
class="css-111"
|
class="themeText css-111"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
Test content
|
Test content
|
||||||
@@ -71,10 +72,11 @@ exports[`PopoverMessage Component Edge Cases should handle null children 1`] = `
|
|||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack popover-container foreground css-109"
|
class="ms-Stack popover-container foreground css-109"
|
||||||
|
data-test="popover-container"
|
||||||
style="max-width: 450px;"
|
style="max-width: 450px;"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="themeText css-110"
|
||||||
style="font-weight: 600;"
|
style="font-weight: 600;"
|
||||||
>
|
>
|
||||||
Test Title
|
Test Title
|
||||||
@@ -133,10 +135,11 @@ exports[`PopoverMessage Component Edge Cases should handle undefined children 1`
|
|||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack popover-container foreground css-109"
|
class="ms-Stack popover-container foreground css-109"
|
||||||
|
data-test="popover-container"
|
||||||
style="max-width: 450px;"
|
style="max-width: 450px;"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="themeText css-110"
|
||||||
style="font-weight: 600;"
|
style="font-weight: 600;"
|
||||||
>
|
>
|
||||||
Test Title
|
Test Title
|
||||||
@@ -195,16 +198,17 @@ exports[`PopoverMessage Component Edge Cases should handle very long title 1`] =
|
|||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack popover-container foreground css-109"
|
class="ms-Stack popover-container foreground css-109"
|
||||||
|
data-test="popover-container"
|
||||||
style="max-width: 450px;"
|
style="max-width: 450px;"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="themeText css-110"
|
||||||
style="font-weight: 600;"
|
style="font-weight: 600;"
|
||||||
>
|
>
|
||||||
This is a very long title that might cause layout issues or text wrapping in the popover component
|
This is a very long title that might cause layout issues or text wrapping in the popover component
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-111"
|
class="themeText css-111"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
Test content
|
Test content
|
||||||
@@ -266,16 +270,17 @@ exports[`PopoverMessage Component Rendering should render correctly when visible
|
|||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack popover-container foreground css-109"
|
class="ms-Stack popover-container foreground css-109"
|
||||||
|
data-test="popover-container"
|
||||||
style="max-width: 450px;"
|
style="max-width: 450px;"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="themeText css-110"
|
||||||
style="font-weight: 600;"
|
style="font-weight: 600;"
|
||||||
>
|
>
|
||||||
Test Title
|
Test Title
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-111"
|
class="themeText css-111"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
Test content
|
Test content
|
||||||
@@ -335,16 +340,17 @@ exports[`PopoverMessage Component Rendering should render correctly with differe
|
|||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack popover-container foreground css-109"
|
class="ms-Stack popover-container foreground css-109"
|
||||||
|
data-test="popover-container"
|
||||||
style="max-width: 450px;"
|
style="max-width: 450px;"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="themeText css-110"
|
||||||
style="font-weight: 600;"
|
style="font-weight: 600;"
|
||||||
>
|
>
|
||||||
Test Title
|
Test Title
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-111"
|
class="themeText css-111"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
@@ -409,16 +415,17 @@ exports[`PopoverMessage Component Rendering should render correctly with differe
|
|||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack popover-container foreground css-109"
|
class="ms-Stack popover-container foreground css-109"
|
||||||
|
data-test="popover-container"
|
||||||
style="max-width: 450px;"
|
style="max-width: 450px;"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="themeText css-110"
|
||||||
style="font-weight: 600;"
|
style="font-weight: 600;"
|
||||||
>
|
>
|
||||||
Custom Title
|
Custom Title
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-111"
|
class="themeText css-111"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
Test content
|
Test content
|
||||||
@@ -478,6 +485,7 @@ exports[`PopoverMessage Component Rendering should render correctly with loading
|
|||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack popover-container foreground loading css-109"
|
class="ms-Stack popover-container foreground loading css-109"
|
||||||
|
data-test="popover-container"
|
||||||
style="max-width: 450px;"
|
style="max-width: 450px;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -485,13 +493,13 @@ exports[`PopoverMessage Component Rendering should render correctly with loading
|
|||||||
data-testid="loading-overlay"
|
data-testid="loading-overlay"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
class="css-110"
|
class="themeText css-110"
|
||||||
style="font-weight: 600;"
|
style="font-weight: 600;"
|
||||||
>
|
>
|
||||||
Test Title
|
Test Title
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-111"
|
class="themeText css-111"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
Test content
|
Test content
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const AddCollectionPanelWrapper: React.FunctionComponent<AddCollectionPanelWrapp
|
|||||||
return (
|
return (
|
||||||
<Stack className="addCollectionPanelWrapper">
|
<Stack className="addCollectionPanelWrapper">
|
||||||
<Stack.Item className="addCollectionPanelHeader">
|
<Stack.Item className="addCollectionPanelHeader">
|
||||||
<Text>{ContainerCopyMessages.createNewContainerSubHeading}</Text>
|
<Text className="themeText">{ContainerCopyMessages.createNewContainerSubHeading}</Text>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<Stack.Item className="addCollectionPanelBody">
|
<Stack.Item className="addCollectionPanelBody">
|
||||||
<AddCollectionPanel explorer={explorer} isCopyJobFlow={true} onSubmitSuccess={handleAddCollectionSuccess} />
|
<AddCollectionPanel explorer={explorer} isCopyJobFlow={true} onSubmitSuccess={handleAddCollectionSuccess} />
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ exports[`AddCollectionPanelWrapper Component Rendering should match snapshot 1`]
|
|||||||
class="ms-StackItem addCollectionPanelHeader css-110"
|
class="ms-StackItem addCollectionPanelHeader css-110"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-111"
|
class="themeText css-111"
|
||||||
>
|
>
|
||||||
Select the properties for your container.
|
Select the properties for your container.
|
||||||
</span>
|
</span>
|
||||||
@@ -50,7 +50,7 @@ exports[`AddCollectionPanelWrapper Component Rendering should match snapshot wit
|
|||||||
class="ms-StackItem addCollectionPanelHeader css-110"
|
class="ms-StackItem addCollectionPanelHeader css-110"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-111"
|
class="themeText css-111"
|
||||||
>
|
>
|
||||||
Select the properties for your container.
|
Select the properties for your container.
|
||||||
</span>
|
</span>
|
||||||
@@ -91,7 +91,7 @@ exports[`AddCollectionPanelWrapper Component Rendering should match snapshot wit
|
|||||||
class="ms-StackItem addCollectionPanelHeader css-110"
|
class="ms-StackItem addCollectionPanelHeader css-110"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-111"
|
class="themeText css-111"
|
||||||
>
|
>
|
||||||
Select the properties for your container.
|
Select the properties for your container.
|
||||||
</span>
|
</span>
|
||||||
@@ -132,7 +132,7 @@ exports[`AddCollectionPanelWrapper Component Rendering should match snapshot wit
|
|||||||
class="ms-StackItem addCollectionPanelHeader css-110"
|
class="ms-StackItem addCollectionPanelHeader css-110"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="css-111"
|
class="themeText css-111"
|
||||||
>
|
>
|
||||||
Select the properties for your container.
|
Select the properties for your container.
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const CreateCopyJobScreens: React.FC = () => {
|
|||||||
<Stack.Item className="createCopyJobScreensContent">
|
<Stack.Item className="createCopyJobScreensContent">
|
||||||
{contextError && (
|
{contextError && (
|
||||||
<MessageBar
|
<MessageBar
|
||||||
|
data-test="Panel:ErrorContainer"
|
||||||
className="createCopyJobErrorMessageBar"
|
className="createCopyJobErrorMessageBar"
|
||||||
messageBarType={MessageBarType.blocked}
|
messageBarType={MessageBarType.blocked}
|
||||||
isMultiline={false}
|
isMultiline={false}
|
||||||
|
|||||||
@@ -31,17 +31,21 @@ const PreviewCopyJob: React.FC = () => {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Stack tokens={{ childrenGap: 20 }} className="previewCopyJobContainer">
|
<Stack tokens={{ childrenGap: 20 }} className="previewCopyJobContainer" data-test="Panel:PreviewCopyJob">
|
||||||
<FieldRow label={ContainerCopyMessages.jobNameLabel}>
|
<FieldRow label={ContainerCopyMessages.jobNameLabel}>
|
||||||
<TextField value={jobName} onChange={onJobNameChange} />
|
<TextField data-test="job-name-textfield" value={jobName} onChange={onJobNameChange} />
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Text className="bold">{ContainerCopyMessages.sourceSubscriptionLabel}</Text>
|
<Text className="bold themeText">{ContainerCopyMessages.sourceSubscriptionLabel}</Text>
|
||||||
<Text>{copyJobState.source?.subscription?.displayName}</Text>
|
<Text data-test="source-subscription-name" className="themeText">
|
||||||
|
{copyJobState.source?.subscription?.displayName}
|
||||||
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Text className="bold">{ContainerCopyMessages.sourceAccountLabel}</Text>
|
<Text className="bold themeText">{ContainerCopyMessages.sourceAccountLabel}</Text>
|
||||||
<Text>{copyJobState.source?.account?.name}</Text>
|
<Text data-test="source-account-name" className="themeText">
|
||||||
|
{copyJobState.source?.account?.name}
|
||||||
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack>
|
<Stack>
|
||||||
<DetailsList
|
<DetailsList
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
exports[`PreviewCopyJob should handle special characters in database and container names 1`] = `
|
exports[`PreviewCopyJob should handle special characters in database and container names 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ms-Stack previewCopyJobContainer css-109"
|
class="ms-Stack previewCopyJobContainer css-109"
|
||||||
|
data-test="Panel:PreviewCopyJob"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack flex-row css-110"
|
class="ms-Stack flex-row css-110"
|
||||||
@@ -32,6 +33,7 @@ exports[`PreviewCopyJob should handle special characters in database and contain
|
|||||||
<input
|
<input
|
||||||
aria-invalid="false"
|
aria-invalid="false"
|
||||||
class="ms-TextField-field field-115"
|
class="ms-TextField-field field-115"
|
||||||
|
data-test="job-name-textfield"
|
||||||
id="TextField84"
|
id="TextField84"
|
||||||
type="text"
|
type="text"
|
||||||
value="job-with@special#chars_123"
|
value="job-with@special#chars_123"
|
||||||
@@ -45,12 +47,13 @@ exports[`PreviewCopyJob should handle special characters in database and contain
|
|||||||
class="ms-Stack css-124"
|
class="ms-Stack css-124"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bold css-125"
|
class="bold themeText css-125"
|
||||||
>
|
>
|
||||||
Source subscription
|
Source subscription
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-125"
|
class="themeText css-125"
|
||||||
|
data-test="source-subscription-name"
|
||||||
>
|
>
|
||||||
Test Subscription
|
Test Subscription
|
||||||
</span>
|
</span>
|
||||||
@@ -59,12 +62,13 @@ exports[`PreviewCopyJob should handle special characters in database and contain
|
|||||||
class="ms-Stack css-124"
|
class="ms-Stack css-124"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bold css-125"
|
class="bold themeText css-125"
|
||||||
>
|
>
|
||||||
Source account
|
Source account
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-125"
|
class="themeText css-125"
|
||||||
|
data-test="source-account-name"
|
||||||
>
|
>
|
||||||
test-account
|
test-account
|
||||||
</span>
|
</span>
|
||||||
@@ -321,6 +325,7 @@ exports[`PreviewCopyJob should handle special characters in database and contain
|
|||||||
exports[`PreviewCopyJob should render component with cross-subscription setup 1`] = `
|
exports[`PreviewCopyJob should render component with cross-subscription setup 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ms-Stack previewCopyJobContainer css-109"
|
class="ms-Stack previewCopyJobContainer css-109"
|
||||||
|
data-test="Panel:PreviewCopyJob"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack flex-row css-110"
|
class="ms-Stack flex-row css-110"
|
||||||
@@ -350,6 +355,7 @@ exports[`PreviewCopyJob should render component with cross-subscription setup 1`
|
|||||||
<input
|
<input
|
||||||
aria-invalid="false"
|
aria-invalid="false"
|
||||||
class="ms-TextField-field field-115"
|
class="ms-TextField-field field-115"
|
||||||
|
data-test="job-name-textfield"
|
||||||
id="TextField96"
|
id="TextField96"
|
||||||
type="text"
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
@@ -363,12 +369,13 @@ exports[`PreviewCopyJob should render component with cross-subscription setup 1`
|
|||||||
class="ms-Stack css-124"
|
class="ms-Stack css-124"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bold css-125"
|
class="bold themeText css-125"
|
||||||
>
|
>
|
||||||
Source subscription
|
Source subscription
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-125"
|
class="themeText css-125"
|
||||||
|
data-test="source-subscription-name"
|
||||||
>
|
>
|
||||||
Test Subscription
|
Test Subscription
|
||||||
</span>
|
</span>
|
||||||
@@ -377,12 +384,13 @@ exports[`PreviewCopyJob should render component with cross-subscription setup 1`
|
|||||||
class="ms-Stack css-124"
|
class="ms-Stack css-124"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bold css-125"
|
class="bold themeText css-125"
|
||||||
>
|
>
|
||||||
Source account
|
Source account
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-125"
|
class="themeText css-125"
|
||||||
|
data-test="source-account-name"
|
||||||
>
|
>
|
||||||
test-account
|
test-account
|
||||||
</span>
|
</span>
|
||||||
@@ -639,6 +647,7 @@ exports[`PreviewCopyJob should render component with cross-subscription setup 1`
|
|||||||
exports[`PreviewCopyJob should render with default state and empty job name 1`] = `
|
exports[`PreviewCopyJob should render with default state and empty job name 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ms-Stack previewCopyJobContainer css-109"
|
class="ms-Stack previewCopyJobContainer css-109"
|
||||||
|
data-test="Panel:PreviewCopyJob"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack flex-row css-110"
|
class="ms-Stack flex-row css-110"
|
||||||
@@ -668,6 +677,7 @@ exports[`PreviewCopyJob should render with default state and empty job name 1`]
|
|||||||
<input
|
<input
|
||||||
aria-invalid="false"
|
aria-invalid="false"
|
||||||
class="ms-TextField-field field-115"
|
class="ms-TextField-field field-115"
|
||||||
|
data-test="job-name-textfield"
|
||||||
id="TextField0"
|
id="TextField0"
|
||||||
type="text"
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
@@ -681,12 +691,13 @@ exports[`PreviewCopyJob should render with default state and empty job name 1`]
|
|||||||
class="ms-Stack css-124"
|
class="ms-Stack css-124"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bold css-125"
|
class="bold themeText css-125"
|
||||||
>
|
>
|
||||||
Source subscription
|
Source subscription
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-125"
|
class="themeText css-125"
|
||||||
|
data-test="source-subscription-name"
|
||||||
>
|
>
|
||||||
Test Subscription
|
Test Subscription
|
||||||
</span>
|
</span>
|
||||||
@@ -695,12 +706,13 @@ exports[`PreviewCopyJob should render with default state and empty job name 1`]
|
|||||||
class="ms-Stack css-124"
|
class="ms-Stack css-124"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bold css-125"
|
class="bold themeText css-125"
|
||||||
>
|
>
|
||||||
Source account
|
Source account
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-125"
|
class="themeText css-125"
|
||||||
|
data-test="source-account-name"
|
||||||
>
|
>
|
||||||
test-account
|
test-account
|
||||||
</span>
|
</span>
|
||||||
@@ -957,6 +969,7 @@ exports[`PreviewCopyJob should render with default state and empty job name 1`]
|
|||||||
exports[`PreviewCopyJob should render with long subscription and account names 1`] = `
|
exports[`PreviewCopyJob should render with long subscription and account names 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ms-Stack previewCopyJobContainer css-109"
|
class="ms-Stack previewCopyJobContainer css-109"
|
||||||
|
data-test="Panel:PreviewCopyJob"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack flex-row css-110"
|
class="ms-Stack flex-row css-110"
|
||||||
@@ -986,6 +999,7 @@ exports[`PreviewCopyJob should render with long subscription and account names 1
|
|||||||
<input
|
<input
|
||||||
aria-invalid="false"
|
aria-invalid="false"
|
||||||
class="ms-TextField-field field-115"
|
class="ms-TextField-field field-115"
|
||||||
|
data-test="job-name-textfield"
|
||||||
id="TextField60"
|
id="TextField60"
|
||||||
type="text"
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
@@ -999,12 +1013,13 @@ exports[`PreviewCopyJob should render with long subscription and account names 1
|
|||||||
class="ms-Stack css-124"
|
class="ms-Stack css-124"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bold css-125"
|
class="bold themeText css-125"
|
||||||
>
|
>
|
||||||
Source subscription
|
Source subscription
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-125"
|
class="themeText css-125"
|
||||||
|
data-test="source-subscription-name"
|
||||||
>
|
>
|
||||||
This is a very long subscription name that might cause display issues if not handled properly
|
This is a very long subscription name that might cause display issues if not handled properly
|
||||||
</span>
|
</span>
|
||||||
@@ -1013,12 +1028,13 @@ exports[`PreviewCopyJob should render with long subscription and account names 1
|
|||||||
class="ms-Stack css-124"
|
class="ms-Stack css-124"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bold css-125"
|
class="bold themeText css-125"
|
||||||
>
|
>
|
||||||
Source account
|
Source account
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-125"
|
class="themeText css-125"
|
||||||
|
data-test="source-account-name"
|
||||||
>
|
>
|
||||||
this-is-a-very-long-database-account-name-that-might-cause-display-issues
|
this-is-a-very-long-database-account-name-that-might-cause-display-issues
|
||||||
</span>
|
</span>
|
||||||
@@ -1275,6 +1291,7 @@ exports[`PreviewCopyJob should render with long subscription and account names 1
|
|||||||
exports[`PreviewCopyJob should render with missing source account information 1`] = `
|
exports[`PreviewCopyJob should render with missing source account information 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ms-Stack previewCopyJobContainer css-109"
|
class="ms-Stack previewCopyJobContainer css-109"
|
||||||
|
data-test="Panel:PreviewCopyJob"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack flex-row css-110"
|
class="ms-Stack flex-row css-110"
|
||||||
@@ -1304,6 +1321,7 @@ exports[`PreviewCopyJob should render with missing source account information 1`
|
|||||||
<input
|
<input
|
||||||
aria-invalid="false"
|
aria-invalid="false"
|
||||||
class="ms-TextField-field field-115"
|
class="ms-TextField-field field-115"
|
||||||
|
data-test="job-name-textfield"
|
||||||
id="TextField36"
|
id="TextField36"
|
||||||
type="text"
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
@@ -1317,12 +1335,13 @@ exports[`PreviewCopyJob should render with missing source account information 1`
|
|||||||
class="ms-Stack css-124"
|
class="ms-Stack css-124"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bold css-125"
|
class="bold themeText css-125"
|
||||||
>
|
>
|
||||||
Source subscription
|
Source subscription
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-125"
|
class="themeText css-125"
|
||||||
|
data-test="source-subscription-name"
|
||||||
>
|
>
|
||||||
Test Subscription
|
Test Subscription
|
||||||
</span>
|
</span>
|
||||||
@@ -1331,7 +1350,7 @@ exports[`PreviewCopyJob should render with missing source account information 1`
|
|||||||
class="ms-Stack css-124"
|
class="ms-Stack css-124"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bold css-125"
|
class="bold themeText css-125"
|
||||||
>
|
>
|
||||||
Source account
|
Source account
|
||||||
</span>
|
</span>
|
||||||
@@ -1588,6 +1607,7 @@ exports[`PreviewCopyJob should render with missing source account information 1`
|
|||||||
exports[`PreviewCopyJob should render with missing source subscription information 1`] = `
|
exports[`PreviewCopyJob should render with missing source subscription information 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ms-Stack previewCopyJobContainer css-109"
|
class="ms-Stack previewCopyJobContainer css-109"
|
||||||
|
data-test="Panel:PreviewCopyJob"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack flex-row css-110"
|
class="ms-Stack flex-row css-110"
|
||||||
@@ -1617,6 +1637,7 @@ exports[`PreviewCopyJob should render with missing source subscription informati
|
|||||||
<input
|
<input
|
||||||
aria-invalid="false"
|
aria-invalid="false"
|
||||||
class="ms-TextField-field field-115"
|
class="ms-TextField-field field-115"
|
||||||
|
data-test="job-name-textfield"
|
||||||
id="TextField24"
|
id="TextField24"
|
||||||
type="text"
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
@@ -1630,7 +1651,7 @@ exports[`PreviewCopyJob should render with missing source subscription informati
|
|||||||
class="ms-Stack css-124"
|
class="ms-Stack css-124"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bold css-125"
|
class="bold themeText css-125"
|
||||||
>
|
>
|
||||||
Source subscription
|
Source subscription
|
||||||
</span>
|
</span>
|
||||||
@@ -1639,12 +1660,13 @@ exports[`PreviewCopyJob should render with missing source subscription informati
|
|||||||
class="ms-Stack css-124"
|
class="ms-Stack css-124"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bold css-125"
|
class="bold themeText css-125"
|
||||||
>
|
>
|
||||||
Source account
|
Source account
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-125"
|
class="themeText css-125"
|
||||||
|
data-test="source-account-name"
|
||||||
>
|
>
|
||||||
test-account
|
test-account
|
||||||
</span>
|
</span>
|
||||||
@@ -1901,6 +1923,7 @@ exports[`PreviewCopyJob should render with missing source subscription informati
|
|||||||
exports[`PreviewCopyJob should render with online migration type 1`] = `
|
exports[`PreviewCopyJob should render with online migration type 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ms-Stack previewCopyJobContainer css-109"
|
class="ms-Stack previewCopyJobContainer css-109"
|
||||||
|
data-test="Panel:PreviewCopyJob"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack flex-row css-110"
|
class="ms-Stack flex-row css-110"
|
||||||
@@ -1930,6 +1953,7 @@ exports[`PreviewCopyJob should render with online migration type 1`] = `
|
|||||||
<input
|
<input
|
||||||
aria-invalid="false"
|
aria-invalid="false"
|
||||||
class="ms-TextField-field field-115"
|
class="ms-TextField-field field-115"
|
||||||
|
data-test="job-name-textfield"
|
||||||
id="TextField72"
|
id="TextField72"
|
||||||
type="text"
|
type="text"
|
||||||
value="online-migration-job"
|
value="online-migration-job"
|
||||||
@@ -1943,12 +1967,13 @@ exports[`PreviewCopyJob should render with online migration type 1`] = `
|
|||||||
class="ms-Stack css-124"
|
class="ms-Stack css-124"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bold css-125"
|
class="bold themeText css-125"
|
||||||
>
|
>
|
||||||
Source subscription
|
Source subscription
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-125"
|
class="themeText css-125"
|
||||||
|
data-test="source-subscription-name"
|
||||||
>
|
>
|
||||||
Test Subscription
|
Test Subscription
|
||||||
</span>
|
</span>
|
||||||
@@ -1957,12 +1982,13 @@ exports[`PreviewCopyJob should render with online migration type 1`] = `
|
|||||||
class="ms-Stack css-124"
|
class="ms-Stack css-124"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bold css-125"
|
class="bold themeText css-125"
|
||||||
>
|
>
|
||||||
Source account
|
Source account
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-125"
|
class="themeText css-125"
|
||||||
|
data-test="source-account-name"
|
||||||
>
|
>
|
||||||
test-account
|
test-account
|
||||||
</span>
|
</span>
|
||||||
@@ -2219,6 +2245,7 @@ exports[`PreviewCopyJob should render with online migration type 1`] = `
|
|||||||
exports[`PreviewCopyJob should render with pre-filled job name 1`] = `
|
exports[`PreviewCopyJob should render with pre-filled job name 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ms-Stack previewCopyJobContainer css-109"
|
class="ms-Stack previewCopyJobContainer css-109"
|
||||||
|
data-test="Panel:PreviewCopyJob"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack flex-row css-110"
|
class="ms-Stack flex-row css-110"
|
||||||
@@ -2248,6 +2275,7 @@ exports[`PreviewCopyJob should render with pre-filled job name 1`] = `
|
|||||||
<input
|
<input
|
||||||
aria-invalid="false"
|
aria-invalid="false"
|
||||||
class="ms-TextField-field field-115"
|
class="ms-TextField-field field-115"
|
||||||
|
data-test="job-name-textfield"
|
||||||
id="TextField12"
|
id="TextField12"
|
||||||
type="text"
|
type="text"
|
||||||
value="custom-job-name-123"
|
value="custom-job-name-123"
|
||||||
@@ -2261,12 +2289,13 @@ exports[`PreviewCopyJob should render with pre-filled job name 1`] = `
|
|||||||
class="ms-Stack css-124"
|
class="ms-Stack css-124"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bold css-125"
|
class="bold themeText css-125"
|
||||||
>
|
>
|
||||||
Source subscription
|
Source subscription
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-125"
|
class="themeText css-125"
|
||||||
|
data-test="source-subscription-name"
|
||||||
>
|
>
|
||||||
Test Subscription
|
Test Subscription
|
||||||
</span>
|
</span>
|
||||||
@@ -2275,12 +2304,13 @@ exports[`PreviewCopyJob should render with pre-filled job name 1`] = `
|
|||||||
class="ms-Stack css-124"
|
class="ms-Stack css-124"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bold css-125"
|
class="bold themeText css-125"
|
||||||
>
|
>
|
||||||
Source account
|
Source account
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-125"
|
class="themeText css-125"
|
||||||
|
data-test="source-account-name"
|
||||||
>
|
>
|
||||||
test-account
|
test-account
|
||||||
</span>
|
</span>
|
||||||
@@ -2537,6 +2567,7 @@ exports[`PreviewCopyJob should render with pre-filled job name 1`] = `
|
|||||||
exports[`PreviewCopyJob should render with undefined database and container names 1`] = `
|
exports[`PreviewCopyJob should render with undefined database and container names 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ms-Stack previewCopyJobContainer css-109"
|
class="ms-Stack previewCopyJobContainer css-109"
|
||||||
|
data-test="Panel:PreviewCopyJob"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Stack flex-row css-110"
|
class="ms-Stack flex-row css-110"
|
||||||
@@ -2566,6 +2597,7 @@ exports[`PreviewCopyJob should render with undefined database and container name
|
|||||||
<input
|
<input
|
||||||
aria-invalid="false"
|
aria-invalid="false"
|
||||||
class="ms-TextField-field field-115"
|
class="ms-TextField-field field-115"
|
||||||
|
data-test="job-name-textfield"
|
||||||
id="TextField48"
|
id="TextField48"
|
||||||
type="text"
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
@@ -2579,12 +2611,13 @@ exports[`PreviewCopyJob should render with undefined database and container name
|
|||||||
class="ms-Stack css-124"
|
class="ms-Stack css-124"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bold css-125"
|
class="bold themeText css-125"
|
||||||
>
|
>
|
||||||
Source subscription
|
Source subscription
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-125"
|
class="themeText css-125"
|
||||||
|
data-test="source-subscription-name"
|
||||||
>
|
>
|
||||||
Test Subscription
|
Test Subscription
|
||||||
</span>
|
</span>
|
||||||
@@ -2593,12 +2626,13 @@ exports[`PreviewCopyJob should render with undefined database and container name
|
|||||||
class="ms-Stack css-124"
|
class="ms-Stack css-124"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="bold css-125"
|
class="bold themeText css-125"
|
||||||
>
|
>
|
||||||
Source account
|
Source account
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="css-125"
|
class="themeText css-125"
|
||||||
|
data-test="source-account-name"
|
||||||
>
|
>
|
||||||
test-account
|
test-account
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,219 +1,424 @@
|
|||||||
import "@testing-library/jest-dom";
|
import "@testing-library/jest-dom";
|
||||||
import { render } from "@testing-library/react";
|
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { DropdownOptionType } from "../../../../Types/CopyJobTypes";
|
import { configContext, Platform } from "../../../../../../ConfigContext";
|
||||||
import { AccountDropdown } from "./AccountDropdown";
|
import { DatabaseAccount } from "../../../../../../Contracts/DataModels";
|
||||||
|
import * as useDatabaseAccountsHook from "../../../../../../hooks/useDatabaseAccounts";
|
||||||
|
import { apiType, userContext } from "../../../../../../UserContext";
|
||||||
|
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
|
||||||
|
import { CopyJobContext } from "../../../../Context/CopyJobContext";
|
||||||
|
import { CopyJobMigrationType } from "../../../../Enums/CopyJobEnums";
|
||||||
|
import { CopyJobContextProviderType, CopyJobContextState } from "../../../../Types/CopyJobTypes";
|
||||||
|
import { AccountDropdown, normalizeAccountId } from "./AccountDropdown";
|
||||||
|
|
||||||
|
jest.mock("../../../../../../hooks/useDatabaseAccounts");
|
||||||
|
jest.mock("../../../../../../UserContext", () => ({
|
||||||
|
userContext: {
|
||||||
|
databaseAccount: null as DatabaseAccount | null,
|
||||||
|
},
|
||||||
|
apiType: jest.fn(),
|
||||||
|
}));
|
||||||
|
jest.mock("../../../../../../ConfigContext", () => ({
|
||||||
|
configContext: {
|
||||||
|
platform: "Portal",
|
||||||
|
},
|
||||||
|
Platform: {
|
||||||
|
Portal: "Portal",
|
||||||
|
Hosted: "Hosted",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mockUseDatabaseAccounts = useDatabaseAccountsHook.useDatabaseAccounts as jest.MockedFunction<
|
||||||
|
typeof useDatabaseAccountsHook.useDatabaseAccounts
|
||||||
|
>;
|
||||||
|
|
||||||
describe("AccountDropdown", () => {
|
describe("AccountDropdown", () => {
|
||||||
const mockOnChange = jest.fn();
|
const mockSetCopyJobState = jest.fn();
|
||||||
|
const mockCopyJobState = {
|
||||||
|
jobName: "",
|
||||||
|
migrationType: CopyJobMigrationType.Offline,
|
||||||
|
source: {
|
||||||
|
subscription: {
|
||||||
|
subscriptionId: "test-subscription-id",
|
||||||
|
displayName: "Test Subscription",
|
||||||
|
},
|
||||||
|
account: null,
|
||||||
|
databaseId: "",
|
||||||
|
containerId: "",
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
subscriptionId: "",
|
||||||
|
account: null,
|
||||||
|
databaseId: "",
|
||||||
|
containerId: "",
|
||||||
|
},
|
||||||
|
sourceReadAccessFromTarget: false,
|
||||||
|
} as CopyJobContextState;
|
||||||
|
|
||||||
const mockAccountOptions: DropdownOptionType[] = [
|
const mockCopyJobContextValue = {
|
||||||
{
|
copyJobState: mockCopyJobState,
|
||||||
key: "account-1",
|
setCopyJobState: mockSetCopyJobState,
|
||||||
text: "Development Account",
|
flow: null,
|
||||||
data: {
|
setFlow: jest.fn(),
|
||||||
id: "account-1",
|
contextError: null,
|
||||||
name: "Development Account",
|
setContextError: jest.fn(),
|
||||||
|
resetCopyJobState: jest.fn(),
|
||||||
|
} as CopyJobContextProviderType;
|
||||||
|
|
||||||
|
const mockDatabaseAccount1: DatabaseAccount = {
|
||||||
|
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDb/databaseAccounts/account1",
|
||||||
|
name: "test-account-1",
|
||||||
|
kind: "GlobalDocumentDB",
|
||||||
location: "East US",
|
location: "East US",
|
||||||
resourceGroup: "dev-rg",
|
type: "Microsoft.DocumentDB/databaseAccounts",
|
||||||
kind: "GlobalDocumentDB",
|
tags: {},
|
||||||
properties: {
|
properties: {
|
||||||
documentEndpoint: "https://dev-account.documents.azure.com:443/",
|
documentEndpoint: "https://account1.documents.azure.com:443/",
|
||||||
provisioningState: "Succeeded",
|
capabilities: [],
|
||||||
consistencyPolicy: {
|
enableMultipleWriteLocations: false,
|
||||||
defaultConsistencyLevel: "Session",
|
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
},
|
|
||||||
},
|
const mockDatabaseAccount2: DatabaseAccount = {
|
||||||
{
|
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDb/databaseAccounts/account2",
|
||||||
key: "account-2",
|
name: "test-account-2",
|
||||||
text: "Production Account",
|
|
||||||
data: {
|
|
||||||
id: "account-2",
|
|
||||||
name: "Production Account",
|
|
||||||
location: "West US 2",
|
|
||||||
resourceGroup: "prod-rg",
|
|
||||||
kind: "GlobalDocumentDB",
|
kind: "GlobalDocumentDB",
|
||||||
|
location: "West US",
|
||||||
|
type: "Microsoft.DocumentDB/databaseAccounts",
|
||||||
|
tags: {},
|
||||||
properties: {
|
properties: {
|
||||||
documentEndpoint: "https://prod-account.documents.azure.com:443/",
|
documentEndpoint: "https://account2.documents.azure.com:443/",
|
||||||
provisioningState: "Succeeded",
|
capabilities: [],
|
||||||
consistencyPolicy: {
|
enableMultipleWriteLocations: false,
|
||||||
defaultConsistencyLevel: "Strong",
|
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
},
|
|
||||||
},
|
const mockNonSqlAccount: DatabaseAccount = {
|
||||||
{
|
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDb/databaseAccounts/mongo-account",
|
||||||
key: "account-3",
|
name: "mongo-account",
|
||||||
text: "Testing Account",
|
kind: "MongoDB",
|
||||||
data: {
|
|
||||||
id: "account-3",
|
|
||||||
name: "Testing Account",
|
|
||||||
location: "Central US",
|
location: "Central US",
|
||||||
resourceGroup: "test-rg",
|
type: "Microsoft.DocumentDB/databaseAccounts",
|
||||||
kind: "GlobalDocumentDB",
|
tags: {},
|
||||||
properties: {
|
properties: {
|
||||||
documentEndpoint: "https://test-account.documents.azure.com:443/",
|
documentEndpoint: "https://mongo-account.documents.azure.com:443/",
|
||||||
provisioningState: "Succeeded",
|
capabilities: [],
|
||||||
consistencyPolicy: {
|
enableMultipleWriteLocations: false,
|
||||||
defaultConsistencyLevel: "Eventual",
|
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
},
|
|
||||||
},
|
const renderWithContext = (contextValue = mockCopyJobContextValue) => {
|
||||||
];
|
return render(
|
||||||
|
<CopyJobContext.Provider value={contextValue}>
|
||||||
|
<AccountDropdown />
|
||||||
|
</CopyJobContext.Provider>,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
(apiType as jest.MockedFunction<any>).mockImplementation((account: DatabaseAccount) => {
|
||||||
|
return account.kind === "MongoDB" ? "MongoDB" : "SQL";
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Snapshot Testing", () => {
|
describe("Rendering", () => {
|
||||||
it("matches snapshot with all account options", () => {
|
it("should render dropdown with correct label and placeholder", () => {
|
||||||
const { container } = render(
|
mockUseDatabaseAccounts.mockReturnValue([]);
|
||||||
<AccountDropdown options={mockAccountOptions} disabled={false} onChange={mockOnChange} />,
|
|
||||||
|
renderWithContext();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByText(`${ContainerCopyMessages.sourceAccountDropdownLabel}:`, { exact: true }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole("combobox")).toHaveAttribute(
|
||||||
|
"aria-label",
|
||||||
|
ContainerCopyMessages.sourceAccountDropdownLabel,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("matches snapshot with selected account", () => {
|
it("should render disabled dropdown when no subscription is selected", () => {
|
||||||
const { container } = render(
|
mockUseDatabaseAccounts.mockReturnValue([]);
|
||||||
<AccountDropdown
|
const contextWithoutSubscription = {
|
||||||
options={mockAccountOptions}
|
...mockCopyJobContextValue,
|
||||||
selectedKey="account-2"
|
copyJobState: {
|
||||||
disabled={false}
|
...mockCopyJobState,
|
||||||
onChange={mockOnChange}
|
source: {
|
||||||
/>,
|
...mockCopyJobState.source,
|
||||||
);
|
subscription: null,
|
||||||
|
},
|
||||||
|
} as CopyJobContextState,
|
||||||
|
};
|
||||||
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
renderWithContext(contextWithoutSubscription);
|
||||||
|
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toHaveAttribute("aria-disabled", "true");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("matches snapshot with disabled dropdown", () => {
|
it("should render disabled dropdown when no accounts are available", () => {
|
||||||
const { container } = render(
|
mockUseDatabaseAccounts.mockReturnValue([]);
|
||||||
<AccountDropdown
|
|
||||||
options={mockAccountOptions}
|
|
||||||
selectedKey="account-1"
|
|
||||||
disabled={true}
|
|
||||||
onChange={mockOnChange}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
renderWithContext();
|
||||||
|
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toHaveAttribute("aria-disabled", "true");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("matches snapshot with empty options", () => {
|
it("should render enabled dropdown when accounts are available", () => {
|
||||||
const { container } = render(<AccountDropdown options={[]} disabled={false} onChange={mockOnChange} />);
|
mockUseDatabaseAccounts.mockReturnValue([mockDatabaseAccount1, mockDatabaseAccount2]);
|
||||||
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
renderWithContext();
|
||||||
|
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toHaveAttribute("aria-disabled", "false");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("matches snapshot with single option", () => {
|
describe("Account filtering", () => {
|
||||||
const { container } = render(
|
it("should filter accounts to only show SQL API accounts", () => {
|
||||||
<AccountDropdown
|
const allAccounts = [mockDatabaseAccount1, mockDatabaseAccount2, mockNonSqlAccount];
|
||||||
options={[mockAccountOptions[0]]}
|
mockUseDatabaseAccounts.mockReturnValue(allAccounts);
|
||||||
selectedKey="account-1"
|
|
||||||
disabled={false}
|
|
||||||
onChange={mockOnChange}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
renderWithContext();
|
||||||
|
|
||||||
|
expect(mockUseDatabaseAccounts).toHaveBeenCalledWith("test-subscription-id");
|
||||||
|
|
||||||
|
expect(apiType as jest.MockedFunction<any>).toHaveBeenCalledWith(mockDatabaseAccount1);
|
||||||
|
expect(apiType as jest.MockedFunction<any>).toHaveBeenCalledWith(mockDatabaseAccount2);
|
||||||
|
expect(apiType as jest.MockedFunction<any>).toHaveBeenCalledWith(mockNonSqlAccount);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("matches snapshot with special characters in options", () => {
|
describe("Account selection", () => {
|
||||||
const specialOptions = [
|
it("should auto-select the first SQL account when no account is currently selected", async () => {
|
||||||
{
|
mockUseDatabaseAccounts.mockReturnValue([mockDatabaseAccount1, mockDatabaseAccount2]);
|
||||||
key: "special",
|
|
||||||
text: 'Account with & <special> "characters"',
|
|
||||||
data: {
|
|
||||||
id: "special",
|
|
||||||
name: 'Account with & <special> "characters"',
|
|
||||||
location: "East US",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const { container } = render(
|
renderWithContext();
|
||||||
<AccountDropdown options={specialOptions} disabled={false} onChange={mockOnChange} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
await waitFor(() => {
|
||||||
|
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("matches snapshot with long account name", () => {
|
const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0];
|
||||||
const longNameOption = [
|
const newState = stateUpdateFunction(mockCopyJobState);
|
||||||
{
|
expect(newState.source.account).toEqual({
|
||||||
key: "long",
|
...mockDatabaseAccount1,
|
||||||
text: "This is an extremely long account name that tests how the component handles text overflow and layout constraints in the dropdown",
|
id: normalizeAccountId(mockDatabaseAccount1.id),
|
||||||
data: {
|
});
|
||||||
id: "long",
|
|
||||||
name: "This is an extremely long account name that tests how the component handles text overflow and layout constraints in the dropdown",
|
|
||||||
location: "North Central US",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const { container } = render(
|
|
||||||
<AccountDropdown options={longNameOption} selectedKey="long" disabled={false} onChange={mockOnChange} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("matches snapshot with disabled state and no selection", () => {
|
it("should auto-select predefined account from userContext if available", async () => {
|
||||||
const { container } = render(
|
const userContextAccount = {
|
||||||
<AccountDropdown options={mockAccountOptions} disabled={true} onChange={mockOnChange} />,
|
...mockDatabaseAccount2,
|
||||||
);
|
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDB/databaseAccounts/account2",
|
||||||
|
};
|
||||||
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
(userContext as any).databaseAccount = userContextAccount;
|
||||||
|
|
||||||
|
mockUseDatabaseAccounts.mockReturnValue([mockDatabaseAccount1, mockDatabaseAccount2]);
|
||||||
|
|
||||||
|
renderWithContext();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("matches snapshot with multiple account types", () => {
|
const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0];
|
||||||
const mixedAccountOptions = [
|
const newState = stateUpdateFunction(mockCopyJobState);
|
||||||
{
|
expect(newState.source.account).toEqual({
|
||||||
key: "sql-account",
|
...mockDatabaseAccount2,
|
||||||
text: "SQL API Account",
|
id: normalizeAccountId(mockDatabaseAccount2.id),
|
||||||
data: {
|
});
|
||||||
id: "sql-account",
|
});
|
||||||
name: "SQL API Account",
|
|
||||||
kind: "GlobalDocumentDB",
|
|
||||||
location: "East US",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "mongo-account",
|
|
||||||
text: "MongoDB Account",
|
|
||||||
data: {
|
|
||||||
id: "mongo-account",
|
|
||||||
name: "MongoDB Account",
|
|
||||||
kind: "MongoDB",
|
|
||||||
location: "West US",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "cassandra-account",
|
|
||||||
text: "Cassandra Account",
|
|
||||||
data: {
|
|
||||||
id: "cassandra-account",
|
|
||||||
name: "Cassandra Account",
|
|
||||||
kind: "Cassandra",
|
|
||||||
location: "Central US",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const { container } = render(
|
it("should keep current account if it exists in the filtered list", async () => {
|
||||||
<AccountDropdown
|
const contextWithSelectedAccount = {
|
||||||
options={mixedAccountOptions}
|
...mockCopyJobContextValue,
|
||||||
selectedKey="mongo-account"
|
copyJobState: {
|
||||||
disabled={false}
|
...mockCopyJobState,
|
||||||
onChange={mockOnChange}
|
source: {
|
||||||
/>,
|
...mockCopyJobState.source,
|
||||||
);
|
account: mockDatabaseAccount1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
mockUseDatabaseAccounts.mockReturnValue([mockDatabaseAccount1, mockDatabaseAccount2]);
|
||||||
|
|
||||||
|
renderWithContext(contextWithSelectedAccount);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
||||||
|
});
|
||||||
|
|
||||||
|
const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0];
|
||||||
|
const newState = stateUpdateFunction(contextWithSelectedAccount.copyJobState);
|
||||||
|
expect(newState).toEqual({
|
||||||
|
...contextWithSelectedAccount.copyJobState,
|
||||||
|
source: {
|
||||||
|
...contextWithSelectedAccount.copyJobState.source,
|
||||||
|
account: {
|
||||||
|
...mockDatabaseAccount1,
|
||||||
|
id: normalizeAccountId(mockDatabaseAccount1.id),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle account change when user selects different account", async () => {
|
||||||
|
mockUseDatabaseAccounts.mockReturnValue([mockDatabaseAccount1, mockDatabaseAccount2]);
|
||||||
|
|
||||||
|
renderWithContext();
|
||||||
|
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
fireEvent.click(dropdown);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const option = screen.getByText("test-account-2");
|
||||||
|
fireEvent.click(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("ID normalization", () => {
|
||||||
|
it("should normalize account ID for Portal platform", () => {
|
||||||
|
const portalAccount = {
|
||||||
|
...mockDatabaseAccount1,
|
||||||
|
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDB/databaseAccounts/account1",
|
||||||
|
};
|
||||||
|
|
||||||
|
(configContext as any).platform = Platform.Portal;
|
||||||
|
mockUseDatabaseAccounts.mockReturnValue([portalAccount]);
|
||||||
|
|
||||||
|
const contextWithSelectedAccount = {
|
||||||
|
...mockCopyJobContextValue,
|
||||||
|
copyJobState: {
|
||||||
|
...mockCopyJobState,
|
||||||
|
source: {
|
||||||
|
...mockCopyJobState.source,
|
||||||
|
account: portalAccount,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithContext(contextWithSelectedAccount);
|
||||||
|
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should normalize account ID for Hosted platform", () => {
|
||||||
|
const hostedAccount = {
|
||||||
|
...mockDatabaseAccount1,
|
||||||
|
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDB/databaseAccounts/account1",
|
||||||
|
};
|
||||||
|
|
||||||
|
(configContext as any).platform = Platform.Hosted;
|
||||||
|
mockUseDatabaseAccounts.mockReturnValue([hostedAccount]);
|
||||||
|
|
||||||
|
const contextWithSelectedAccount = {
|
||||||
|
...mockCopyJobContextValue,
|
||||||
|
copyJobState: {
|
||||||
|
...mockCopyJobState,
|
||||||
|
source: {
|
||||||
|
...mockCopyJobState.source,
|
||||||
|
account: hostedAccount,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithContext(contextWithSelectedAccount);
|
||||||
|
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Edge cases", () => {
|
||||||
|
it("should handle empty account list gracefully", () => {
|
||||||
|
mockUseDatabaseAccounts.mockReturnValue([]);
|
||||||
|
|
||||||
|
renderWithContext();
|
||||||
|
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toHaveAttribute("aria-disabled", "true");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle null account list gracefully", () => {
|
||||||
|
mockUseDatabaseAccounts.mockReturnValue(null as any);
|
||||||
|
|
||||||
|
renderWithContext();
|
||||||
|
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toHaveAttribute("aria-disabled", "true");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle undefined subscription ID", () => {
|
||||||
|
const contextWithoutSubscription = {
|
||||||
|
...mockCopyJobContextValue,
|
||||||
|
copyJobState: {
|
||||||
|
...mockCopyJobState,
|
||||||
|
source: {
|
||||||
|
...mockCopyJobState.source,
|
||||||
|
subscription: null,
|
||||||
|
},
|
||||||
|
} as CopyJobContextState,
|
||||||
|
};
|
||||||
|
|
||||||
|
mockUseDatabaseAccounts.mockReturnValue([]);
|
||||||
|
|
||||||
|
renderWithContext(contextWithoutSubscription);
|
||||||
|
|
||||||
|
expect(mockUseDatabaseAccounts).toHaveBeenCalledWith(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not update state if account is already selected and the same", async () => {
|
||||||
|
const selectedAccount = mockDatabaseAccount1;
|
||||||
|
const contextWithSelectedAccount = {
|
||||||
|
...mockCopyJobContextValue,
|
||||||
|
copyJobState: {
|
||||||
|
...mockCopyJobState,
|
||||||
|
source: {
|
||||||
|
...mockCopyJobState.source,
|
||||||
|
account: selectedAccount,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
mockUseDatabaseAccounts.mockReturnValue([mockDatabaseAccount1, mockDatabaseAccount2]);
|
||||||
|
|
||||||
|
renderWithContext(contextWithSelectedAccount);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
||||||
|
});
|
||||||
|
|
||||||
|
const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0];
|
||||||
|
const newState = stateUpdateFunction(contextWithSelectedAccount.copyJobState);
|
||||||
|
expect(newState).toBe(contextWithSelectedAccount.copyJobState);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Accessibility", () => {
|
||||||
|
it("should have proper aria-label", () => {
|
||||||
|
mockUseDatabaseAccounts.mockReturnValue([mockDatabaseAccount1]);
|
||||||
|
|
||||||
|
renderWithContext();
|
||||||
|
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toHaveAttribute("aria-label", ContainerCopyMessages.sourceAccountDropdownLabel);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have required attribute", () => {
|
||||||
|
mockUseDatabaseAccounts.mockReturnValue([mockDatabaseAccount1]);
|
||||||
|
|
||||||
|
renderWithContext();
|
||||||
|
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toHaveAttribute("aria-required", "true");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,31 +1,96 @@
|
|||||||
/* eslint-disable react/prop-types */
|
/* eslint-disable react/prop-types */
|
||||||
/* eslint-disable react/display-name */
|
/* eslint-disable react/display-name */
|
||||||
import { Dropdown } from "@fluentui/react";
|
import { Dropdown } from "@fluentui/react";
|
||||||
import React from "react";
|
import { configContext, Platform } from "ConfigContext";
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { DatabaseAccount } from "../../../../../../Contracts/DataModels";
|
||||||
|
import { useDatabaseAccounts } from "../../../../../../hooks/useDatabaseAccounts";
|
||||||
|
import { apiType, userContext } from "../../../../../../UserContext";
|
||||||
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
|
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
|
||||||
import { DropdownOptionType } from "../../../../Types/CopyJobTypes";
|
import { useCopyJobContext } from "../../../../Context/CopyJobContext";
|
||||||
import FieldRow from "../../Components/FieldRow";
|
import FieldRow from "../../Components/FieldRow";
|
||||||
|
|
||||||
interface AccountDropdownProps {
|
interface AccountDropdownProps {}
|
||||||
options: DropdownOptionType[];
|
|
||||||
selectedKey?: string;
|
|
||||||
disabled: boolean;
|
|
||||||
onChange: (_ev?: React.FormEvent, option?: DropdownOptionType) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AccountDropdown: React.FC<AccountDropdownProps> = React.memo(
|
export const normalizeAccountId = (id: string = "") => {
|
||||||
({ options, selectedKey, disabled, onChange }) => (
|
if (configContext.platform === Platform.Portal) {
|
||||||
|
return id.replace("/Microsoft.DocumentDb/", "/Microsoft.DocumentDB/");
|
||||||
|
} else if (configContext.platform === Platform.Hosted) {
|
||||||
|
return id.replace("/Microsoft.DocumentDB/", "/Microsoft.DocumentDb/");
|
||||||
|
} else {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AccountDropdown: React.FC<AccountDropdownProps> = () => {
|
||||||
|
const { copyJobState, setCopyJobState } = useCopyJobContext();
|
||||||
|
|
||||||
|
const selectedSubscriptionId = copyJobState?.source?.subscription?.subscriptionId;
|
||||||
|
const allAccounts: DatabaseAccount[] = useDatabaseAccounts(selectedSubscriptionId);
|
||||||
|
const sqlApiOnlyAccounts = (allAccounts || [])
|
||||||
|
.filter((account) => apiType(account) === "SQL")
|
||||||
|
.map((account) => ({
|
||||||
|
...account,
|
||||||
|
id: normalizeAccountId(account.id),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const updateCopyJobState = (newAccount: DatabaseAccount) => {
|
||||||
|
setCopyJobState((prevState) => {
|
||||||
|
if (prevState.source?.account?.id !== newAccount.id) {
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
source: {
|
||||||
|
...prevState.source,
|
||||||
|
account: newAccount,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return prevState;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (sqlApiOnlyAccounts && sqlApiOnlyAccounts.length > 0 && selectedSubscriptionId) {
|
||||||
|
const currentAccountId = copyJobState?.source?.account?.id;
|
||||||
|
const predefinedAccountId = normalizeAccountId(userContext.databaseAccount?.id);
|
||||||
|
const selectedAccountId = currentAccountId || predefinedAccountId;
|
||||||
|
|
||||||
|
const targetAccount: DatabaseAccount | null =
|
||||||
|
sqlApiOnlyAccounts.find((account) => account.id === selectedAccountId) || null;
|
||||||
|
updateCopyJobState(targetAccount || sqlApiOnlyAccounts[0]);
|
||||||
|
}
|
||||||
|
}, [sqlApiOnlyAccounts?.length, selectedSubscriptionId]);
|
||||||
|
|
||||||
|
const accountOptions =
|
||||||
|
sqlApiOnlyAccounts?.map((account) => ({
|
||||||
|
key: account.id,
|
||||||
|
text: account.name,
|
||||||
|
data: account,
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
const handleAccountChange = (_ev?: React.FormEvent, option?: (typeof accountOptions)[0]) => {
|
||||||
|
const selectedAccount = option?.data as DatabaseAccount;
|
||||||
|
|
||||||
|
if (selectedAccount) {
|
||||||
|
updateCopyJobState(selectedAccount);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isAccountDropdownDisabled = !selectedSubscriptionId || accountOptions.length === 0;
|
||||||
|
const selectedAccountId = normalizeAccountId(copyJobState?.source?.account?.id ?? "");
|
||||||
|
|
||||||
|
return (
|
||||||
<FieldRow label={ContainerCopyMessages.sourceAccountDropdownLabel}>
|
<FieldRow label={ContainerCopyMessages.sourceAccountDropdownLabel}>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
placeholder={ContainerCopyMessages.sourceAccountDropdownPlaceholder}
|
placeholder={ContainerCopyMessages.sourceAccountDropdownPlaceholder}
|
||||||
ariaLabel={ContainerCopyMessages.sourceAccountDropdownLabel}
|
ariaLabel={ContainerCopyMessages.sourceAccountDropdownLabel}
|
||||||
options={options}
|
options={accountOptions}
|
||||||
disabled={disabled}
|
disabled={isAccountDropdownDisabled}
|
||||||
required
|
required
|
||||||
selectedKey={selectedKey}
|
selectedKey={selectedAccountId}
|
||||||
onChange={onChange}
|
onChange={handleAccountChange}
|
||||||
|
data-test="account-dropdown"
|
||||||
/>
|
/>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
),
|
);
|
||||||
(prev, next) => prev.options.length === next.options.length && prev.selectedKey === next.selectedKey,
|
};
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable react/prop-types */
|
/* eslint-disable react/prop-types */
|
||||||
/* eslint-disable react/display-name */
|
/* eslint-disable react/display-name */
|
||||||
import { Checkbox, Stack } from "@fluentui/react";
|
import { Checkbox, ICheckboxStyles, Stack } from "@fluentui/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
|
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
|
||||||
|
|
||||||
@@ -9,8 +9,25 @@ interface MigrationTypeCheckboxProps {
|
|||||||
onChange: (_ev?: React.FormEvent, checked?: boolean) => void;
|
onChange: (_ev?: React.FormEvent, checked?: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MigrationTypeCheckbox: React.FC<MigrationTypeCheckboxProps> = React.memo(({ checked, onChange }) => (
|
const checkboxStyles: ICheckboxStyles = {
|
||||||
<Stack horizontal horizontalAlign="space-between" className="migrationTypeRow">
|
text: { color: "var(--colorNeutralForeground1)" },
|
||||||
<Checkbox label={ContainerCopyMessages.migrationTypeCheckboxLabel} checked={checked} onChange={onChange} />
|
checkbox: { borderColor: "var(--colorNeutralStroke1)" },
|
||||||
|
root: {
|
||||||
|
selectors: {
|
||||||
|
":hover .ms-Checkbox-text": { color: "var(--colorNeutralForeground1)" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MigrationTypeCheckbox: React.FC<MigrationTypeCheckboxProps> = React.memo(({ checked, onChange }) => {
|
||||||
|
return (
|
||||||
|
<Stack horizontal horizontalAlign="space-between" className="migrationTypeRow" data-test="migration-type-checkbox">
|
||||||
|
<Checkbox
|
||||||
|
label={ContainerCopyMessages.migrationTypeCheckboxLabel}
|
||||||
|
checked={checked}
|
||||||
|
onChange={onChange}
|
||||||
|
styles={checkboxStyles}
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
));
|
);
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,118 +1,295 @@
|
|||||||
import "@testing-library/jest-dom";
|
import "@testing-library/jest-dom";
|
||||||
import { render } from "@testing-library/react";
|
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { DropdownOptionType } from "../../../../Types/CopyJobTypes";
|
import { Subscription } from "../../../../../../Contracts/DataModels";
|
||||||
|
import Explorer from "../../../../../Explorer";
|
||||||
|
import CopyJobContextProvider from "../../../../Context/CopyJobContext";
|
||||||
import { SubscriptionDropdown } from "./SubscriptionDropdown";
|
import { SubscriptionDropdown } from "./SubscriptionDropdown";
|
||||||
|
|
||||||
describe("SubscriptionDropdown", () => {
|
jest.mock("../../../../../../hooks/useSubscriptions");
|
||||||
const mockOnChange = jest.fn();
|
jest.mock("../../../../../../UserContext");
|
||||||
|
jest.mock("../../../../ContainerCopyMessages");
|
||||||
|
|
||||||
const mockSubscriptionOptions: DropdownOptionType[] = [
|
const mockUseSubscriptions = jest.requireMock("../../../../../../hooks/useSubscriptions").useSubscriptions;
|
||||||
|
const mockUserContext = jest.requireMock("../../../../../../UserContext").userContext;
|
||||||
|
const mockContainerCopyMessages = jest.requireMock("../../../../ContainerCopyMessages").default;
|
||||||
|
|
||||||
|
mockContainerCopyMessages.subscriptionDropdownLabel = "Subscription";
|
||||||
|
mockContainerCopyMessages.subscriptionDropdownPlaceholder = "Select a subscription";
|
||||||
|
|
||||||
|
describe("SubscriptionDropdown", () => {
|
||||||
|
let mockExplorer: Explorer;
|
||||||
|
const mockSubscriptions: Subscription[] = [
|
||||||
{
|
{
|
||||||
key: "sub-1",
|
|
||||||
text: "Development Subscription",
|
|
||||||
data: {
|
|
||||||
subscriptionId: "sub-1",
|
subscriptionId: "sub-1",
|
||||||
displayName: "Development Subscription",
|
displayName: "Subscription One",
|
||||||
authorizationSource: "RoleBased",
|
state: "Enabled",
|
||||||
subscriptionPolicies: {
|
tenantId: "tenant-1",
|
||||||
quotaId: "quota-1",
|
|
||||||
spendingLimit: "Off",
|
|
||||||
locationPlacementId: "loc-1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "sub-2",
|
|
||||||
text: "Production Subscription",
|
|
||||||
data: {
|
|
||||||
subscriptionId: "sub-2",
|
subscriptionId: "sub-2",
|
||||||
displayName: "Production Subscription",
|
displayName: "Subscription Two",
|
||||||
authorizationSource: "RoleBased",
|
state: "Enabled",
|
||||||
subscriptionPolicies: {
|
tenantId: "tenant-1",
|
||||||
quotaId: "quota-2",
|
|
||||||
spendingLimit: "On",
|
|
||||||
locationPlacementId: "loc-2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "sub-3",
|
|
||||||
text: "Testing Subscription",
|
|
||||||
data: {
|
|
||||||
subscriptionId: "sub-3",
|
subscriptionId: "sub-3",
|
||||||
displayName: "Testing Subscription",
|
displayName: "Another Subscription",
|
||||||
authorizationSource: "Legacy",
|
state: "Enabled",
|
||||||
subscriptionPolicies: {
|
tenantId: "tenant-1",
|
||||||
quotaId: "quota-3",
|
|
||||||
spendingLimit: "Off",
|
|
||||||
locationPlacementId: "loc-3",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const renderWithProvider = (children: React.ReactNode) => {
|
||||||
|
return render(<CopyJobContextProvider explorer={mockExplorer}>{children}</CopyJobContextProvider>);
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
mockExplorer = {} as Explorer;
|
||||||
|
|
||||||
|
mockUseSubscriptions.mockReturnValue(mockSubscriptions);
|
||||||
|
mockUserContext.subscriptionId = "sub-1";
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Snapshot Testing", () => {
|
describe("Rendering", () => {
|
||||||
it("matches snapshot with all subscription options", () => {
|
it("should render subscription dropdown with correct attributes", () => {
|
||||||
const { container } = render(<SubscriptionDropdown options={mockSubscriptionOptions} onChange={mockOnChange} />);
|
renderWithProvider(<SubscriptionDropdown />);
|
||||||
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toBeInTheDocument();
|
||||||
|
expect(dropdown).toHaveAttribute("aria-label", "Subscription");
|
||||||
|
expect(dropdown).toHaveAttribute("data-test", "subscription-dropdown");
|
||||||
|
expect(dropdown).toBeRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("matches snapshot with selected subscription", () => {
|
it("should render field label correctly", () => {
|
||||||
const { container } = render(
|
renderWithProvider(<SubscriptionDropdown />);
|
||||||
<SubscriptionDropdown options={mockSubscriptionOptions} selectedKey="sub-2" onChange={mockOnChange} />,
|
|
||||||
|
expect(screen.getByText("Subscription:")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show placeholder when no subscription is selected", async () => {
|
||||||
|
mockUserContext.subscriptionId = "";
|
||||||
|
mockUseSubscriptions.mockReturnValue([]);
|
||||||
|
|
||||||
|
renderWithProvider(<SubscriptionDropdown />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toHaveTextContent("Select a subscription");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Subscription Options", () => {
|
||||||
|
it("should populate dropdown with available subscriptions", async () => {
|
||||||
|
renderWithProvider(<SubscriptionDropdown />);
|
||||||
|
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
fireEvent.click(dropdown);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText("Subscription One", { selector: ".ms-Dropdown-optionText" })).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Subscription Two", { selector: ".ms-Dropdown-optionText" })).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Another Subscription", { selector: ".ms-Dropdown-optionText" })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle empty subscriptions list", () => {
|
||||||
|
mockUseSubscriptions.mockReturnValue([]);
|
||||||
|
|
||||||
|
renderWithProvider(<SubscriptionDropdown />);
|
||||||
|
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toBeInTheDocument();
|
||||||
|
expect(dropdown).toHaveTextContent("Select a subscription");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle undefined subscriptions", () => {
|
||||||
|
mockUseSubscriptions.mockReturnValue(undefined);
|
||||||
|
|
||||||
|
renderWithProvider(<SubscriptionDropdown />);
|
||||||
|
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toBeInTheDocument();
|
||||||
|
expect(dropdown).toHaveTextContent("Select a subscription");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Selection Logic", () => {
|
||||||
|
it("should auto-select subscription based on userContext.subscriptionId on mount", async () => {
|
||||||
|
mockUserContext.subscriptionId = "sub-2";
|
||||||
|
|
||||||
|
renderWithProvider(<SubscriptionDropdown />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toHaveTextContent("Subscription Two");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should maintain current selection when subscriptions list updates with same subscription", async () => {
|
||||||
|
renderWithProvider(<SubscriptionDropdown />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toHaveTextContent("Subscription One");
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
mockUseSubscriptions.mockReturnValue([...mockSubscriptions]);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toHaveTextContent("Subscription One");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should prioritize current copyJobState subscription over userContext subscription", async () => {
|
||||||
|
mockUserContext.subscriptionId = "sub-2";
|
||||||
|
|
||||||
|
const { rerender } = renderWithProvider(<SubscriptionDropdown />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toHaveTextContent("Subscription Two");
|
||||||
|
});
|
||||||
|
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
fireEvent.click(dropdown);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const option = screen.getByText("Another Subscription");
|
||||||
|
fireEvent.click(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<CopyJobContextProvider explorer={mockExplorer}>
|
||||||
|
<SubscriptionDropdown />
|
||||||
|
</CopyJobContextProvider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
await waitFor(() => {
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toHaveTextContent("Another Subscription");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("matches snapshot with empty options", () => {
|
it("should handle subscription selection change", async () => {
|
||||||
const { container } = render(<SubscriptionDropdown options={[]} onChange={mockOnChange} />);
|
renderWithProvider(<SubscriptionDropdown />);
|
||||||
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
fireEvent.click(dropdown);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const option = screen.getByText("Subscription Two");
|
||||||
|
fireEvent.click(option);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("matches snapshot with single option", () => {
|
await waitFor(() => {
|
||||||
const { container } = render(
|
expect(dropdown).toHaveTextContent("Subscription Two");
|
||||||
<SubscriptionDropdown options={[mockSubscriptionOptions[0]]} selectedKey="sub-1" onChange={mockOnChange} />,
|
});
|
||||||
);
|
|
||||||
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("matches snapshot with special characters in options", () => {
|
it("should not auto-select if target subscription not found in list", async () => {
|
||||||
const specialOptions = [
|
mockUserContext.subscriptionId = "non-existent-sub";
|
||||||
{
|
|
||||||
key: "special",
|
|
||||||
text: 'Subscription with & <special> "characters"',
|
|
||||||
data: { subscriptionId: "special" },
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const { container } = render(<SubscriptionDropdown options={specialOptions} onChange={mockOnChange} />);
|
renderWithProvider(<SubscriptionDropdown />);
|
||||||
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
await waitFor(() => {
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toHaveTextContent("Select a subscription");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("matches snapshot with long subscription name", () => {
|
describe("Context State Management", () => {
|
||||||
const longNameOption = [
|
it("should update copyJobState when subscription is selected", async () => {
|
||||||
{
|
renderWithProvider(<SubscriptionDropdown />);
|
||||||
key: "long",
|
|
||||||
text: "This is an extremely long subscription name that tests how the component handles text overflow and layout constraints",
|
|
||||||
data: { subscriptionId: "long" },
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const { container } = render(
|
const dropdown = screen.getByRole("combobox");
|
||||||
<SubscriptionDropdown options={longNameOption} selectedKey="long" onChange={mockOnChange} />,
|
fireEvent.click(dropdown);
|
||||||
);
|
|
||||||
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
await waitFor(() => {
|
||||||
|
const option = screen.getByText("Subscription Two");
|
||||||
|
fireEvent.click(option);
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(dropdown).toHaveTextContent("Subscription Two");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should reset account when subscription changes", async () => {
|
||||||
|
renderWithProvider(<SubscriptionDropdown />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toHaveTextContent("Subscription One");
|
||||||
|
});
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
fireEvent.click(dropdown);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const option = screen.getByText("Subscription Two");
|
||||||
|
fireEvent.click(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(dropdown).toHaveTextContent("Subscription Two");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not update state if same subscription is selected", async () => {
|
||||||
|
renderWithProvider(<SubscriptionDropdown />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toHaveTextContent("Subscription One");
|
||||||
|
});
|
||||||
|
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
fireEvent.click(dropdown);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const option = screen.getByText("Subscription One", { selector: ".ms-Dropdown-optionText" });
|
||||||
|
fireEvent.click(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(dropdown).toHaveTextContent("Subscription One");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Edge Cases", () => {
|
||||||
|
it("should handle subscription change event with option missing data", async () => {
|
||||||
|
renderWithProvider(<SubscriptionDropdown />);
|
||||||
|
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
fireEvent.click(dropdown);
|
||||||
|
expect(dropdown).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle subscriptions loading state", () => {
|
||||||
|
mockUseSubscriptions.mockReturnValue(undefined);
|
||||||
|
|
||||||
|
renderWithProvider(<SubscriptionDropdown />);
|
||||||
|
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toBeInTheDocument();
|
||||||
|
expect(dropdown).toHaveTextContent("Select a subscription");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should work when both userContext.subscriptionId and copyJobState subscription are null", () => {
|
||||||
|
mockUserContext.subscriptionId = "";
|
||||||
|
|
||||||
|
renderWithProvider(<SubscriptionDropdown />);
|
||||||
|
|
||||||
|
const dropdown = screen.getByRole("combobox");
|
||||||
|
expect(dropdown).toBeInTheDocument();
|
||||||
|
expect(dropdown).toHaveTextContent("Select a subscription");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,29 +1,79 @@
|
|||||||
/* eslint-disable react/prop-types */
|
/* eslint-disable react/prop-types */
|
||||||
/* eslint-disable react/display-name */
|
/* eslint-disable react/display-name */
|
||||||
import { Dropdown } from "@fluentui/react";
|
import { Dropdown } from "@fluentui/react";
|
||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
|
import { Subscription } from "../../../../../../Contracts/DataModels";
|
||||||
|
import { useSubscriptions } from "../../../../../../hooks/useSubscriptions";
|
||||||
|
import { userContext } from "../../../../../../UserContext";
|
||||||
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
|
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
|
||||||
import { DropdownOptionType } from "../../../../Types/CopyJobTypes";
|
import { useCopyJobContext } from "../../../../Context/CopyJobContext";
|
||||||
import FieldRow from "../../Components/FieldRow";
|
import FieldRow from "../../Components/FieldRow";
|
||||||
|
|
||||||
interface SubscriptionDropdownProps {
|
interface SubscriptionDropdownProps {}
|
||||||
options: DropdownOptionType[];
|
|
||||||
selectedKey?: string;
|
|
||||||
onChange: (_ev?: React.FormEvent, option?: DropdownOptionType) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SubscriptionDropdown: React.FC<SubscriptionDropdownProps> = React.memo(
|
export const SubscriptionDropdown: React.FC<SubscriptionDropdownProps> = React.memo(() => {
|
||||||
({ options, selectedKey, onChange }) => (
|
const { copyJobState, setCopyJobState } = useCopyJobContext();
|
||||||
|
const subscriptions: Subscription[] = useSubscriptions();
|
||||||
|
|
||||||
|
const updateCopyJobState = (newSubscription: Subscription) => {
|
||||||
|
setCopyJobState((prevState) => {
|
||||||
|
if (prevState.source?.subscription?.subscriptionId !== newSubscription.subscriptionId) {
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
source: {
|
||||||
|
...prevState.source,
|
||||||
|
subscription: newSubscription,
|
||||||
|
account: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return prevState;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (subscriptions && subscriptions.length > 0) {
|
||||||
|
const currentSubscriptionId = copyJobState?.source?.subscription?.subscriptionId;
|
||||||
|
const predefinedSubscriptionId = userContext.subscriptionId;
|
||||||
|
const selectedSubscriptionId = currentSubscriptionId || predefinedSubscriptionId;
|
||||||
|
|
||||||
|
const targetSubscription: Subscription | null =
|
||||||
|
subscriptions.find((sub) => sub.subscriptionId === selectedSubscriptionId) || null;
|
||||||
|
|
||||||
|
if (targetSubscription) {
|
||||||
|
updateCopyJobState(targetSubscription);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [subscriptions?.length]);
|
||||||
|
|
||||||
|
const subscriptionOptions =
|
||||||
|
subscriptions?.map((sub) => ({
|
||||||
|
key: sub.subscriptionId,
|
||||||
|
text: sub.displayName,
|
||||||
|
data: sub,
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
const handleSubscriptionChange = (_ev?: React.FormEvent, option?: (typeof subscriptionOptions)[0]) => {
|
||||||
|
const selectedSubscription = option?.data as Subscription;
|
||||||
|
|
||||||
|
if (selectedSubscription) {
|
||||||
|
updateCopyJobState(selectedSubscription);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectedSubscriptionId = copyJobState?.source?.subscription?.subscriptionId;
|
||||||
|
|
||||||
|
return (
|
||||||
<FieldRow label={ContainerCopyMessages.subscriptionDropdownLabel}>
|
<FieldRow label={ContainerCopyMessages.subscriptionDropdownLabel}>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
placeholder={ContainerCopyMessages.subscriptionDropdownPlaceholder}
|
placeholder={ContainerCopyMessages.subscriptionDropdownPlaceholder}
|
||||||
ariaLabel={ContainerCopyMessages.subscriptionDropdownLabel}
|
ariaLabel={ContainerCopyMessages.subscriptionDropdownLabel}
|
||||||
options={options}
|
data-test="subscription-dropdown"
|
||||||
|
options={subscriptionOptions}
|
||||||
required
|
required
|
||||||
selectedKey={selectedKey}
|
selectedKey={selectedSubscriptionId}
|
||||||
onChange={onChange}
|
onChange={handleSubscriptionChange}
|
||||||
/>
|
/>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
),
|
);
|
||||||
(prev, next) => prev.options.length === next.options.length && prev.selectedKey === next.selectedKey,
|
});
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,514 +1,37 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`AccountDropdown Snapshot Testing matches snapshot with all account options 1`] = `
|
exports[`AccountDropdown ID normalization should normalize account ID for Portal platform 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ms-Stack flex-row css-109"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-fixed-width css-110"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="field-label "
|
|
||||||
>
|
|
||||||
Account
|
|
||||||
:
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-grow-col css-110"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-Dropdown-container"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
aria-haspopup="listbox"
|
aria-haspopup="listbox"
|
||||||
aria-label="Account"
|
aria-label="Account"
|
||||||
aria-required="true"
|
aria-required="true"
|
||||||
class="ms-Dropdown is-required dropdown-111"
|
class="ms-Dropdown is-required dropdown-132"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
id="Dropdown0"
|
data-test="account-dropdown"
|
||||||
|
id="Dropdown21"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-invalid="false"
|
|
||||||
class="ms-Dropdown-title ms-Dropdown-titleIsPlaceHolder title-112"
|
|
||||||
id="Dropdown0-option"
|
|
||||||
>
|
|
||||||
Select an account
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
aria-hidden="true"
|
|
||||||
class="ms-Dropdown-caretDown caretDown-131"
|
|
||||||
data-icon-name="ChevronDown"
|
|
||||||
>
|
|
||||||
|
|
||||||
</i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`AccountDropdown Snapshot Testing matches snapshot with disabled dropdown 1`] = `
|
|
||||||
<div
|
|
||||||
class="ms-Stack flex-row css-109"
|
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-fixed-width css-110"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="field-label "
|
|
||||||
>
|
|
||||||
Account
|
|
||||||
:
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-grow-col css-110"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-Dropdown-container"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-disabled="true"
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-haspopup="listbox"
|
|
||||||
aria-label="Account"
|
|
||||||
aria-required="true"
|
|
||||||
class="ms-Dropdown is-disabled is-required dropdown-133"
|
|
||||||
data-is-focusable="false"
|
|
||||||
data-ktp-target="true"
|
|
||||||
id="Dropdown2"
|
|
||||||
role="combobox"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<span
|
<span
|
||||||
aria-invalid="false"
|
aria-invalid="false"
|
||||||
class="ms-Dropdown-title title-138"
|
class="ms-Dropdown-title title-137"
|
||||||
id="Dropdown2-option"
|
id="Dropdown21-option"
|
||||||
>
|
>
|
||||||
Development Account
|
test-account-1
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-135"
|
class="ms-Dropdown-caretDownWrapper caretDownWrapper-134"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="ms-Dropdown-caretDown caretDown-137"
|
class="ms-Dropdown-caretDown caretDown-136"
|
||||||
data-icon-name="ChevronDown"
|
data-icon-name="ChevronDown"
|
||||||
>
|
>
|
||||||
|
|
||||||
</i>
|
</i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`AccountDropdown Snapshot Testing matches snapshot with disabled state and no selection 1`] = `
|
|
||||||
<div
|
|
||||||
class="ms-Stack flex-row css-109"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-fixed-width css-110"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="field-label "
|
|
||||||
>
|
|
||||||
Account
|
|
||||||
:
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-grow-col css-110"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-Dropdown-container"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-disabled="true"
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-haspopup="listbox"
|
|
||||||
aria-label="Account"
|
|
||||||
aria-required="true"
|
|
||||||
class="ms-Dropdown is-disabled is-required dropdown-133"
|
|
||||||
data-is-focusable="false"
|
|
||||||
data-ktp-target="true"
|
|
||||||
id="Dropdown7"
|
|
||||||
role="combobox"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-invalid="false"
|
|
||||||
class="ms-Dropdown-title ms-Dropdown-titleIsPlaceHolder title-134"
|
|
||||||
id="Dropdown7-option"
|
|
||||||
>
|
|
||||||
Select an account
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-135"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
aria-hidden="true"
|
|
||||||
class="ms-Dropdown-caretDown caretDown-137"
|
|
||||||
data-icon-name="ChevronDown"
|
|
||||||
>
|
|
||||||
|
|
||||||
</i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`AccountDropdown Snapshot Testing matches snapshot with empty options 1`] = `
|
|
||||||
<div
|
|
||||||
class="ms-Stack flex-row css-109"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-fixed-width css-110"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="field-label "
|
|
||||||
>
|
|
||||||
Account
|
|
||||||
:
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-grow-col css-110"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-Dropdown-container"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-disabled="false"
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-haspopup="listbox"
|
|
||||||
aria-label="Account"
|
|
||||||
aria-required="true"
|
|
||||||
class="ms-Dropdown is-required dropdown-111"
|
|
||||||
data-is-focusable="true"
|
|
||||||
data-ktp-target="true"
|
|
||||||
id="Dropdown3"
|
|
||||||
role="combobox"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-invalid="false"
|
|
||||||
class="ms-Dropdown-title ms-Dropdown-titleIsPlaceHolder title-112"
|
|
||||||
id="Dropdown3-option"
|
|
||||||
>
|
|
||||||
Select an account
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
aria-hidden="true"
|
|
||||||
class="ms-Dropdown-caretDown caretDown-131"
|
|
||||||
data-icon-name="ChevronDown"
|
|
||||||
>
|
|
||||||
|
|
||||||
</i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`AccountDropdown Snapshot Testing matches snapshot with long account name 1`] = `
|
|
||||||
<div
|
|
||||||
class="ms-Stack flex-row css-109"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-fixed-width css-110"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="field-label "
|
|
||||||
>
|
|
||||||
Account
|
|
||||||
:
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-grow-col css-110"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-Dropdown-container"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-disabled="false"
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-haspopup="listbox"
|
|
||||||
aria-label="Account"
|
|
||||||
aria-required="true"
|
|
||||||
class="ms-Dropdown is-required dropdown-111"
|
|
||||||
data-is-focusable="true"
|
|
||||||
data-ktp-target="true"
|
|
||||||
id="Dropdown6"
|
|
||||||
role="combobox"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-invalid="false"
|
|
||||||
class="ms-Dropdown-title title-132"
|
|
||||||
id="Dropdown6-option"
|
|
||||||
>
|
|
||||||
This is an extremely long account name that tests how the component handles text overflow and layout constraints in the dropdown
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
aria-hidden="true"
|
|
||||||
class="ms-Dropdown-caretDown caretDown-131"
|
|
||||||
data-icon-name="ChevronDown"
|
|
||||||
>
|
|
||||||
|
|
||||||
</i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`AccountDropdown Snapshot Testing matches snapshot with multiple account types 1`] = `
|
|
||||||
<div
|
|
||||||
class="ms-Stack flex-row css-109"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-fixed-width css-110"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="field-label "
|
|
||||||
>
|
|
||||||
Account
|
|
||||||
:
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-grow-col css-110"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-Dropdown-container"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-disabled="false"
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-haspopup="listbox"
|
|
||||||
aria-label="Account"
|
|
||||||
aria-required="true"
|
|
||||||
class="ms-Dropdown is-required dropdown-111"
|
|
||||||
data-is-focusable="true"
|
|
||||||
data-ktp-target="true"
|
|
||||||
id="Dropdown8"
|
|
||||||
role="combobox"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-invalid="false"
|
|
||||||
class="ms-Dropdown-title title-132"
|
|
||||||
id="Dropdown8-option"
|
|
||||||
>
|
|
||||||
MongoDB Account
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
aria-hidden="true"
|
|
||||||
class="ms-Dropdown-caretDown caretDown-131"
|
|
||||||
data-icon-name="ChevronDown"
|
|
||||||
>
|
|
||||||
|
|
||||||
</i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`AccountDropdown Snapshot Testing matches snapshot with selected account 1`] = `
|
|
||||||
<div
|
|
||||||
class="ms-Stack flex-row css-109"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-fixed-width css-110"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="field-label "
|
|
||||||
>
|
|
||||||
Account
|
|
||||||
:
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-grow-col css-110"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-Dropdown-container"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-disabled="false"
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-haspopup="listbox"
|
|
||||||
aria-label="Account"
|
|
||||||
aria-required="true"
|
|
||||||
class="ms-Dropdown is-required dropdown-111"
|
|
||||||
data-is-focusable="true"
|
|
||||||
data-ktp-target="true"
|
|
||||||
id="Dropdown1"
|
|
||||||
role="combobox"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-invalid="false"
|
|
||||||
class="ms-Dropdown-title title-132"
|
|
||||||
id="Dropdown1-option"
|
|
||||||
>
|
|
||||||
Production Account
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
aria-hidden="true"
|
|
||||||
class="ms-Dropdown-caretDown caretDown-131"
|
|
||||||
data-icon-name="ChevronDown"
|
|
||||||
>
|
|
||||||
|
|
||||||
</i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`AccountDropdown Snapshot Testing matches snapshot with single option 1`] = `
|
|
||||||
<div
|
|
||||||
class="ms-Stack flex-row css-109"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-fixed-width css-110"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="field-label "
|
|
||||||
>
|
|
||||||
Account
|
|
||||||
:
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-grow-col css-110"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-Dropdown-container"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-disabled="false"
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-haspopup="listbox"
|
|
||||||
aria-label="Account"
|
|
||||||
aria-required="true"
|
|
||||||
class="ms-Dropdown is-required dropdown-111"
|
|
||||||
data-is-focusable="true"
|
|
||||||
data-ktp-target="true"
|
|
||||||
id="Dropdown4"
|
|
||||||
role="combobox"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-invalid="false"
|
|
||||||
class="ms-Dropdown-title title-132"
|
|
||||||
id="Dropdown4-option"
|
|
||||||
>
|
|
||||||
Development Account
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
aria-hidden="true"
|
|
||||||
class="ms-Dropdown-caretDown caretDown-131"
|
|
||||||
data-icon-name="ChevronDown"
|
|
||||||
>
|
|
||||||
|
|
||||||
</i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`AccountDropdown Snapshot Testing matches snapshot with special characters in options 1`] = `
|
|
||||||
<div
|
|
||||||
class="ms-Stack flex-row css-109"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-fixed-width css-110"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="field-label "
|
|
||||||
>
|
|
||||||
Account
|
|
||||||
:
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-grow-col css-110"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-Dropdown-container"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-disabled="false"
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-haspopup="listbox"
|
|
||||||
aria-label="Account"
|
|
||||||
aria-required="true"
|
|
||||||
class="ms-Dropdown is-required dropdown-111"
|
|
||||||
data-is-focusable="true"
|
|
||||||
data-ktp-target="true"
|
|
||||||
id="Dropdown5"
|
|
||||||
role="combobox"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-invalid="false"
|
|
||||||
class="ms-Dropdown-title ms-Dropdown-titleIsPlaceHolder title-112"
|
|
||||||
id="Dropdown5-option"
|
|
||||||
>
|
|
||||||
Select an account
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
aria-hidden="true"
|
|
||||||
class="ms-Dropdown-caretDown caretDown-131"
|
|
||||||
data-icon-name="ChevronDown"
|
|
||||||
>
|
|
||||||
|
|
||||||
</i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
exports[`MigrationTypeCheckbox Component Rendering should render in checked state 1`] = `
|
exports[`MigrationTypeCheckbox Component Rendering should render in checked state 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ms-Stack migrationTypeRow css-109"
|
class="ms-Stack migrationTypeRow css-109"
|
||||||
|
data-test="migration-type-checkbox"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Checkbox is-checked is-enabled root-119"
|
class="ms-Checkbox is-checked is-enabled root-119"
|
||||||
@@ -43,6 +44,7 @@ exports[`MigrationTypeCheckbox Component Rendering should render in checked stat
|
|||||||
exports[`MigrationTypeCheckbox Component Rendering should render with default props (unchecked state) 1`] = `
|
exports[`MigrationTypeCheckbox Component Rendering should render with default props (unchecked state) 1`] = `
|
||||||
<div
|
<div
|
||||||
class="ms-Stack migrationTypeRow css-109"
|
class="ms-Stack migrationTypeRow css-109"
|
||||||
|
data-test="migration-type-checkbox"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ms-Checkbox is-enabled root-110"
|
class="ms-Checkbox is-enabled root-110"
|
||||||
|
|||||||
@@ -1,337 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`SubscriptionDropdown Snapshot Testing matches snapshot with all subscription options 1`] = `
|
|
||||||
<div
|
|
||||||
class="ms-Stack flex-row css-109"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-fixed-width css-110"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="field-label "
|
|
||||||
>
|
|
||||||
Subscription
|
|
||||||
:
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-grow-col css-110"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-Dropdown-container"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-haspopup="listbox"
|
|
||||||
aria-label="Subscription"
|
|
||||||
aria-required="true"
|
|
||||||
class="ms-Dropdown is-required dropdown-111"
|
|
||||||
data-is-focusable="true"
|
|
||||||
data-ktp-target="true"
|
|
||||||
id="Dropdown0"
|
|
||||||
role="combobox"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-invalid="false"
|
|
||||||
class="ms-Dropdown-title ms-Dropdown-titleIsPlaceHolder title-112"
|
|
||||||
id="Dropdown0-option"
|
|
||||||
>
|
|
||||||
Select a subscription
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
aria-hidden="true"
|
|
||||||
class="ms-Dropdown-caretDown caretDown-131"
|
|
||||||
data-icon-name="ChevronDown"
|
|
||||||
>
|
|
||||||
|
|
||||||
</i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SubscriptionDropdown Snapshot Testing matches snapshot with empty options 1`] = `
|
|
||||||
<div
|
|
||||||
class="ms-Stack flex-row css-109"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-fixed-width css-110"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="field-label "
|
|
||||||
>
|
|
||||||
Subscription
|
|
||||||
:
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-grow-col css-110"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-Dropdown-container"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-haspopup="listbox"
|
|
||||||
aria-label="Subscription"
|
|
||||||
aria-required="true"
|
|
||||||
class="ms-Dropdown is-required dropdown-111"
|
|
||||||
data-is-focusable="true"
|
|
||||||
data-ktp-target="true"
|
|
||||||
id="Dropdown2"
|
|
||||||
role="combobox"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-invalid="false"
|
|
||||||
class="ms-Dropdown-title ms-Dropdown-titleIsPlaceHolder title-112"
|
|
||||||
id="Dropdown2-option"
|
|
||||||
>
|
|
||||||
Select a subscription
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
aria-hidden="true"
|
|
||||||
class="ms-Dropdown-caretDown caretDown-131"
|
|
||||||
data-icon-name="ChevronDown"
|
|
||||||
>
|
|
||||||
|
|
||||||
</i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SubscriptionDropdown Snapshot Testing matches snapshot with long subscription name 1`] = `
|
|
||||||
<div
|
|
||||||
class="ms-Stack flex-row css-109"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-fixed-width css-110"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="field-label "
|
|
||||||
>
|
|
||||||
Subscription
|
|
||||||
:
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-grow-col css-110"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-Dropdown-container"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-haspopup="listbox"
|
|
||||||
aria-label="Subscription"
|
|
||||||
aria-required="true"
|
|
||||||
class="ms-Dropdown is-required dropdown-111"
|
|
||||||
data-is-focusable="true"
|
|
||||||
data-ktp-target="true"
|
|
||||||
id="Dropdown5"
|
|
||||||
role="combobox"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-invalid="false"
|
|
||||||
class="ms-Dropdown-title title-132"
|
|
||||||
id="Dropdown5-option"
|
|
||||||
>
|
|
||||||
This is an extremely long subscription name that tests how the component handles text overflow and layout constraints
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
aria-hidden="true"
|
|
||||||
class="ms-Dropdown-caretDown caretDown-131"
|
|
||||||
data-icon-name="ChevronDown"
|
|
||||||
>
|
|
||||||
|
|
||||||
</i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SubscriptionDropdown Snapshot Testing matches snapshot with selected subscription 1`] = `
|
|
||||||
<div
|
|
||||||
class="ms-Stack flex-row css-109"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-fixed-width css-110"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="field-label "
|
|
||||||
>
|
|
||||||
Subscription
|
|
||||||
:
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-grow-col css-110"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-Dropdown-container"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-haspopup="listbox"
|
|
||||||
aria-label="Subscription"
|
|
||||||
aria-required="true"
|
|
||||||
class="ms-Dropdown is-required dropdown-111"
|
|
||||||
data-is-focusable="true"
|
|
||||||
data-ktp-target="true"
|
|
||||||
id="Dropdown1"
|
|
||||||
role="combobox"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-invalid="false"
|
|
||||||
class="ms-Dropdown-title title-132"
|
|
||||||
id="Dropdown1-option"
|
|
||||||
>
|
|
||||||
Production Subscription
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
aria-hidden="true"
|
|
||||||
class="ms-Dropdown-caretDown caretDown-131"
|
|
||||||
data-icon-name="ChevronDown"
|
|
||||||
>
|
|
||||||
|
|
||||||
</i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SubscriptionDropdown Snapshot Testing matches snapshot with single option 1`] = `
|
|
||||||
<div
|
|
||||||
class="ms-Stack flex-row css-109"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-fixed-width css-110"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="field-label "
|
|
||||||
>
|
|
||||||
Subscription
|
|
||||||
:
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-grow-col css-110"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-Dropdown-container"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-haspopup="listbox"
|
|
||||||
aria-label="Subscription"
|
|
||||||
aria-required="true"
|
|
||||||
class="ms-Dropdown is-required dropdown-111"
|
|
||||||
data-is-focusable="true"
|
|
||||||
data-ktp-target="true"
|
|
||||||
id="Dropdown3"
|
|
||||||
role="combobox"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-invalid="false"
|
|
||||||
class="ms-Dropdown-title title-132"
|
|
||||||
id="Dropdown3-option"
|
|
||||||
>
|
|
||||||
Development Subscription
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
aria-hidden="true"
|
|
||||||
class="ms-Dropdown-caretDown caretDown-131"
|
|
||||||
data-icon-name="ChevronDown"
|
|
||||||
>
|
|
||||||
|
|
||||||
</i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SubscriptionDropdown Snapshot Testing matches snapshot with special characters in options 1`] = `
|
|
||||||
<div
|
|
||||||
class="ms-Stack flex-row css-109"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-fixed-width css-110"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="field-label "
|
|
||||||
>
|
|
||||||
Subscription
|
|
||||||
:
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="ms-StackItem flex-grow-col css-110"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ms-Dropdown-container"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-haspopup="listbox"
|
|
||||||
aria-label="Subscription"
|
|
||||||
aria-required="true"
|
|
||||||
class="ms-Dropdown is-required dropdown-111"
|
|
||||||
data-is-focusable="true"
|
|
||||||
data-ktp-target="true"
|
|
||||||
id="Dropdown4"
|
|
||||||
role="combobox"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-invalid="false"
|
|
||||||
class="ms-Dropdown-title ms-Dropdown-titleIsPlaceHolder title-112"
|
|
||||||
id="Dropdown4-option"
|
|
||||||
>
|
|
||||||
Select a subscription
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
aria-hidden="true"
|
|
||||||
class="ms-Dropdown-caretDown caretDown-131"
|
|
||||||
data-icon-name="ChevronDown"
|
|
||||||
>
|
|
||||||
|
|
||||||
</i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,480 +1,170 @@
|
|||||||
import "@testing-library/jest-dom";
|
import "@testing-library/jest-dom";
|
||||||
import { fireEvent, render, screen } from "@testing-library/react";
|
import { fireEvent, render, screen } from "@testing-library/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { apiType } from "UserContext";
|
import { useCopyJobContext } from "../../../Context/CopyJobContext";
|
||||||
import { DatabaseAccount, Subscription } from "../../../../../Contracts/DataModels";
|
|
||||||
import { useDatabaseAccounts } from "../../../../../hooks/useDatabaseAccounts";
|
|
||||||
import { useSubscriptions } from "../../../../../hooks/useSubscriptions";
|
|
||||||
import { CopyJobMigrationType } from "../../../Enums/CopyJobEnums";
|
import { CopyJobMigrationType } from "../../../Enums/CopyJobEnums";
|
||||||
import { CopyJobContextProviderType, CopyJobContextState } from "../../../Types/CopyJobTypes";
|
import { CopyJobContextProviderType } from "../../../Types/CopyJobTypes";
|
||||||
import SelectAccount from "./SelectAccount";
|
import SelectAccount from "./SelectAccount";
|
||||||
|
|
||||||
jest.mock("UserContext", () => ({
|
|
||||||
apiType: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock("../../../../../hooks/useDatabaseAccounts");
|
|
||||||
jest.mock("../../../../../hooks/useSubscriptions");
|
|
||||||
jest.mock("../../../Context/CopyJobContext", () => ({
|
jest.mock("../../../Context/CopyJobContext", () => ({
|
||||||
useCopyJobContext: () => mockContextValue,
|
useCopyJobContext: jest.fn(),
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock("./Utils/selectAccountUtils", () => ({
|
|
||||||
useDropdownOptions: jest.fn(),
|
|
||||||
useEventHandlers: jest.fn(),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock("./Components/SubscriptionDropdown", () => ({
|
jest.mock("./Components/SubscriptionDropdown", () => ({
|
||||||
SubscriptionDropdown: jest.fn(({ options, selectedKey, onChange, ...props }) => (
|
SubscriptionDropdown: jest.fn(() => <div data-testid="subscription-dropdown">Subscription Dropdown</div>),
|
||||||
<div data-testid="subscription-dropdown" data-selected={selectedKey} {...props}>
|
|
||||||
{options?.map((option: any) => (
|
|
||||||
<div
|
|
||||||
key={option.key}
|
|
||||||
data-testid={`subscription-option-${option.key}`}
|
|
||||||
onClick={() => onChange?.(undefined, option)}
|
|
||||||
>
|
|
||||||
{option.text}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock("./Components/AccountDropdown", () => ({
|
jest.mock("./Components/AccountDropdown", () => ({
|
||||||
AccountDropdown: jest.fn(({ options, selectedKey, disabled, onChange, ...props }) => (
|
AccountDropdown: jest.fn(() => <div data-testid="account-dropdown">Account Dropdown</div>),
|
||||||
<div data-testid="account-dropdown" data-selected={selectedKey} data-disabled={disabled} {...props}>
|
|
||||||
{options?.map((option: any) => (
|
|
||||||
<div
|
|
||||||
key={option.key}
|
|
||||||
data-testid={`account-option-${option.key}`}
|
|
||||||
onClick={() => onChange?.(undefined, option)}
|
|
||||||
>
|
|
||||||
{option.text}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock("./Components/MigrationTypeCheckbox", () => ({
|
jest.mock("./Components/MigrationTypeCheckbox", () => ({
|
||||||
MigrationTypeCheckbox: jest.fn(({ checked, onChange, ...props }) => (
|
MigrationTypeCheckbox: jest.fn(({ checked, onChange }: { checked: boolean; onChange: () => void }) => (
|
||||||
<div data-testid="migration-type-checkbox" data-checked={checked} {...props}>
|
<div data-testid="migration-type-checkbox">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={checked}
|
checked={checked}
|
||||||
onChange={(e) => onChange?.(e, e.target.checked)}
|
onChange={onChange}
|
||||||
data-testid="migration-checkbox-input"
|
data-testid="migration-checkbox-input"
|
||||||
|
aria-label="Migration Type Checkbox"
|
||||||
/>
|
/>
|
||||||
|
Copy container in offline mode
|
||||||
</div>
|
</div>
|
||||||
)),
|
)),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock("../../../ContainerCopyMessages", () => ({
|
describe("SelectAccount", () => {
|
||||||
selectAccountDescription: "Select your source account and subscription",
|
const mockSetCopyJobState = jest.fn();
|
||||||
}));
|
|
||||||
|
|
||||||
const mockUseDatabaseAccounts = useDatabaseAccounts as jest.MockedFunction<typeof useDatabaseAccounts>;
|
const defaultContextValue: CopyJobContextProviderType = {
|
||||||
const mockUseSubscriptions = useSubscriptions as jest.MockedFunction<typeof useSubscriptions>;
|
|
||||||
const mockApiType = apiType as jest.MockedFunction<typeof apiType>;
|
|
||||||
|
|
||||||
import { useDropdownOptions, useEventHandlers } from "./Utils/selectAccountUtils";
|
|
||||||
const mockUseDropdownOptions = useDropdownOptions as jest.MockedFunction<typeof useDropdownOptions>;
|
|
||||||
const mockUseEventHandlers = useEventHandlers as jest.MockedFunction<typeof useEventHandlers>;
|
|
||||||
|
|
||||||
const mockSubscriptions = [
|
|
||||||
{
|
|
||||||
subscriptionId: "sub-1",
|
|
||||||
displayName: "Test Subscription 1",
|
|
||||||
authorizationSource: "RoleBased",
|
|
||||||
subscriptionPolicies: {
|
|
||||||
quotaId: "quota-1",
|
|
||||||
spendingLimit: "Off",
|
|
||||||
locationPlacementId: "loc-1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
subscriptionId: "sub-2",
|
|
||||||
displayName: "Test Subscription 2",
|
|
||||||
authorizationSource: "RoleBased",
|
|
||||||
subscriptionPolicies: {
|
|
||||||
quotaId: "quota-2",
|
|
||||||
spendingLimit: "On",
|
|
||||||
locationPlacementId: "loc-2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
] as Subscription[];
|
|
||||||
|
|
||||||
const mockAccounts = [
|
|
||||||
{
|
|
||||||
id: "/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1",
|
|
||||||
name: "test-cosmos-account-1",
|
|
||||||
location: "East US",
|
|
||||||
kind: "GlobalDocumentDB",
|
|
||||||
properties: {
|
|
||||||
documentEndpoint: "https://account-1.documents.azure.com/",
|
|
||||||
capabilities: [],
|
|
||||||
enableFreeTier: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-2",
|
|
||||||
name: "test-cosmos-account-2",
|
|
||||||
location: "West US",
|
|
||||||
kind: "MongoDB",
|
|
||||||
properties: {
|
|
||||||
documentEndpoint: "https://account-2.documents.azure.com/",
|
|
||||||
capabilities: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
] as DatabaseAccount[];
|
|
||||||
|
|
||||||
const mockDropdownOptions = {
|
|
||||||
subscriptionOptions: [
|
|
||||||
{ key: "sub-1", text: "Test Subscription 1", data: mockSubscriptions[0] },
|
|
||||||
{ key: "sub-2", text: "Test Subscription 2", data: mockSubscriptions[1] },
|
|
||||||
],
|
|
||||||
accountOptions: [{ key: mockAccounts[0].id, text: mockAccounts[0].name, data: mockAccounts[0] }],
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockEventHandlers = {
|
|
||||||
handleSelectSourceAccount: jest.fn(),
|
|
||||||
handleMigrationTypeChange: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mockContextValue = {
|
|
||||||
copyJobState: {
|
copyJobState: {
|
||||||
jobName: "",
|
jobName: "",
|
||||||
migrationType: CopyJobMigrationType.Offline,
|
migrationType: CopyJobMigrationType.Online,
|
||||||
source: {
|
source: {
|
||||||
subscription: null,
|
subscription: null as any,
|
||||||
account: null,
|
account: null as any,
|
||||||
databaseId: "",
|
databaseId: "",
|
||||||
containerId: "",
|
containerId: "",
|
||||||
},
|
},
|
||||||
target: {
|
target: {
|
||||||
subscriptionId: "",
|
subscriptionId: "",
|
||||||
account: null,
|
account: null as any,
|
||||||
databaseId: "",
|
databaseId: "",
|
||||||
containerId: "",
|
containerId: "",
|
||||||
},
|
},
|
||||||
sourceReadAccessFromTarget: false,
|
sourceReadAccessFromTarget: false,
|
||||||
} as CopyJobContextState,
|
},
|
||||||
setCopyJobState: jest.fn(),
|
setCopyJobState: mockSetCopyJobState,
|
||||||
flow: null,
|
flow: { currentScreen: "selectAccount" },
|
||||||
setFlow: jest.fn(),
|
setFlow: jest.fn(),
|
||||||
contextError: null,
|
contextError: null,
|
||||||
setContextError: jest.fn(),
|
setContextError: jest.fn(),
|
||||||
resetCopyJobState: jest.fn(),
|
|
||||||
explorer: {} as any,
|
explorer: {} as any,
|
||||||
} as CopyJobContextProviderType;
|
|
||||||
|
|
||||||
describe("SelectAccount Component", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
|
|
||||||
mockContextValue = {
|
|
||||||
copyJobState: {
|
|
||||||
jobName: "",
|
|
||||||
migrationType: CopyJobMigrationType.Offline,
|
|
||||||
source: {
|
|
||||||
subscription: null,
|
|
||||||
account: null,
|
|
||||||
databaseId: "",
|
|
||||||
containerId: "",
|
|
||||||
},
|
|
||||||
target: {
|
|
||||||
subscriptionId: "",
|
|
||||||
account: null,
|
|
||||||
databaseId: "",
|
|
||||||
containerId: "",
|
|
||||||
},
|
|
||||||
sourceReadAccessFromTarget: false,
|
|
||||||
} as CopyJobContextState,
|
|
||||||
setCopyJobState: jest.fn(),
|
|
||||||
flow: null,
|
|
||||||
setFlow: jest.fn(),
|
|
||||||
contextError: null,
|
|
||||||
setContextError: jest.fn(),
|
|
||||||
resetCopyJobState: jest.fn(),
|
resetCopyJobState: jest.fn(),
|
||||||
explorer: {} as any,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mockUseSubscriptions.mockReturnValue(mockSubscriptions);
|
beforeEach(() => {
|
||||||
mockUseDatabaseAccounts.mockReturnValue(mockAccounts);
|
jest.clearAllMocks();
|
||||||
mockApiType.mockReturnValue("SQL");
|
(useCopyJobContext as jest.Mock).mockReturnValue(defaultContextValue);
|
||||||
mockUseDropdownOptions.mockReturnValue(mockDropdownOptions);
|
|
||||||
mockUseEventHandlers.mockReturnValue(mockEventHandlers);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Rendering", () => {
|
afterEach(() => {
|
||||||
it("should render component with default state", () => {
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Component Rendering", () => {
|
||||||
|
it("should render the component with all required elements", () => {
|
||||||
const { container } = render(<SelectAccount />);
|
const { container } = render(<SelectAccount />);
|
||||||
|
|
||||||
expect(screen.getByText("Select your source account and subscription")).toBeInTheDocument();
|
expect(container.firstChild).toHaveAttribute("data-test", "Panel:SelectAccountContainer");
|
||||||
|
expect(container.firstChild).toHaveClass("selectAccountContainer");
|
||||||
|
|
||||||
|
expect(screen.getByText(/Please select a source account from which to copy/i)).toBeInTheDocument();
|
||||||
|
|
||||||
expect(screen.getByTestId("subscription-dropdown")).toBeInTheDocument();
|
expect(screen.getByTestId("subscription-dropdown")).toBeInTheDocument();
|
||||||
expect(screen.getByTestId("account-dropdown")).toBeInTheDocument();
|
expect(screen.getByTestId("account-dropdown")).toBeInTheDocument();
|
||||||
expect(screen.getByTestId("migration-type-checkbox")).toBeInTheDocument();
|
expect(screen.getByTestId("migration-type-checkbox")).toBeInTheDocument();
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render with selected subscription", () => {
|
it("should render correctly with snapshot", () => {
|
||||||
mockContextValue.copyJobState.source.subscription = mockSubscriptions[0];
|
|
||||||
|
|
||||||
const { container } = render(<SelectAccount />);
|
const { container } = render(<SelectAccount />);
|
||||||
|
expect(container.firstChild).toMatchSnapshot();
|
||||||
expect(screen.getByTestId("subscription-dropdown")).toHaveAttribute("data-selected", "sub-1");
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render with selected account", () => {
|
|
||||||
mockContextValue.copyJobState.source.subscription = mockSubscriptions[0];
|
|
||||||
mockContextValue.copyJobState.source.account = mockAccounts[0];
|
|
||||||
|
|
||||||
const { container } = render(<SelectAccount />);
|
|
||||||
|
|
||||||
expect(screen.getByTestId("account-dropdown")).toHaveAttribute("data-selected", mockAccounts[0].id);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render with offline migration type checked", () => {
|
|
||||||
mockContextValue.copyJobState.migrationType = CopyJobMigrationType.Offline;
|
|
||||||
|
|
||||||
const { container } = render(<SelectAccount />);
|
|
||||||
|
|
||||||
expect(screen.getByTestId("migration-type-checkbox")).toHaveAttribute("data-checked", "true");
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render with online migration type unchecked", () => {
|
|
||||||
mockContextValue.copyJobState.migrationType = CopyJobMigrationType.Online;
|
|
||||||
|
|
||||||
const { container } = render(<SelectAccount />);
|
|
||||||
|
|
||||||
expect(screen.getByTestId("migration-type-checkbox")).toHaveAttribute("data-checked", "false");
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Hook Integration", () => {
|
describe("Migration Type Functionality", () => {
|
||||||
it("should call useSubscriptions hook", () => {
|
it("should display migration type checkbox as unchecked when migrationType is Online", () => {
|
||||||
render(<SelectAccount />);
|
(useCopyJobContext as jest.Mock).mockReturnValue({
|
||||||
expect(mockUseSubscriptions).toHaveBeenCalledTimes(1);
|
...defaultContextValue,
|
||||||
|
copyJobState: {
|
||||||
|
...defaultContextValue.copyJobState,
|
||||||
|
migrationType: CopyJobMigrationType.Online,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should call useDatabaseAccounts with selected subscription ID", () => {
|
|
||||||
mockContextValue.copyJobState.source.subscription = mockSubscriptions[0];
|
|
||||||
render(<SelectAccount />);
|
|
||||||
|
|
||||||
expect(mockUseDatabaseAccounts).toHaveBeenCalledWith("sub-1");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call useDatabaseAccounts with undefined when no subscription selected", () => {
|
|
||||||
render(<SelectAccount />);
|
|
||||||
expect(mockUseDatabaseAccounts).toHaveBeenCalledWith(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should filter accounts to SQL API only", () => {
|
|
||||||
mockApiType.mockReturnValueOnce("SQL").mockReturnValueOnce("Mongo");
|
|
||||||
render(<SelectAccount />);
|
|
||||||
|
|
||||||
expect(mockApiType).toHaveBeenCalledTimes(2);
|
|
||||||
expect(mockApiType).toHaveBeenCalledWith(mockAccounts[0]);
|
|
||||||
expect(mockApiType).toHaveBeenCalledWith(mockAccounts[1]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call useDropdownOptions with correct parameters", () => {
|
|
||||||
const sqlOnlyAccounts = [mockAccounts[0]]; // Only SQL account
|
|
||||||
mockApiType.mockImplementation((account) => (account === mockAccounts[0] ? "SQL" : "Mongo"));
|
|
||||||
|
|
||||||
render(<SelectAccount />);
|
render(<SelectAccount />);
|
||||||
|
|
||||||
expect(mockUseDropdownOptions).toHaveBeenCalledWith(mockSubscriptions, sqlOnlyAccounts);
|
const checkbox = screen.getByTestId("migration-checkbox-input");
|
||||||
|
expect(checkbox).not.toBeChecked();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should call useEventHandlers with setCopyJobState", () => {
|
it("should display migration type checkbox as checked when migrationType is Offline", () => {
|
||||||
render(<SelectAccount />);
|
(useCopyJobContext as jest.Mock).mockReturnValue({
|
||||||
expect(mockUseEventHandlers).toHaveBeenCalledWith(mockContextValue.setCopyJobState);
|
...defaultContextValue,
|
||||||
});
|
copyJobState: {
|
||||||
|
...defaultContextValue.copyJobState,
|
||||||
|
migrationType: CopyJobMigrationType.Offline,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Event Handling", () => {
|
|
||||||
it("should handle subscription selection", () => {
|
|
||||||
render(<SelectAccount />);
|
render(<SelectAccount />);
|
||||||
|
|
||||||
const subscriptionOption = screen.getByTestId("subscription-option-sub-1");
|
const checkbox = screen.getByTestId("migration-checkbox-input");
|
||||||
fireEvent.click(subscriptionOption);
|
expect(checkbox).toBeChecked();
|
||||||
|
|
||||||
expect(mockEventHandlers.handleSelectSourceAccount).toHaveBeenCalledWith("subscription", mockSubscriptions[0]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should handle account selection", () => {
|
it("should call setCopyJobState with Online migration type when checkbox is unchecked", () => {
|
||||||
render(<SelectAccount />);
|
(useCopyJobContext as jest.Mock).mockReturnValue({
|
||||||
|
...defaultContextValue,
|
||||||
const accountOption = screen.getByTestId(`account-option-${mockAccounts[0].id}`);
|
copyJobState: {
|
||||||
fireEvent.click(accountOption);
|
...defaultContextValue.copyJobState,
|
||||||
|
migrationType: CopyJobMigrationType.Offline,
|
||||||
expect(mockEventHandlers.handleSelectSourceAccount).toHaveBeenCalledWith("account", mockAccounts[0]);
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should handle migration type change", () => {
|
|
||||||
render(<SelectAccount />);
|
render(<SelectAccount />);
|
||||||
|
|
||||||
const checkbox = screen.getByTestId("migration-checkbox-input");
|
const checkbox = screen.getByTestId("migration-checkbox-input");
|
||||||
fireEvent.click(checkbox);
|
fireEvent.click(checkbox);
|
||||||
|
|
||||||
expect(mockEventHandlers.handleMigrationTypeChange).toHaveBeenCalledWith(expect.any(Object), false);
|
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
||||||
|
|
||||||
|
const updateFunction = mockSetCopyJobState.mock.calls[0][0];
|
||||||
|
const previousState = {
|
||||||
|
...defaultContextValue.copyJobState,
|
||||||
|
migrationType: CopyJobMigrationType.Offline,
|
||||||
|
};
|
||||||
|
const result = updateFunction(previousState);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
...previousState,
|
||||||
|
migrationType: CopyJobMigrationType.Online,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Dropdown States", () => {
|
describe("Performance and Optimization", () => {
|
||||||
it("should disable account dropdown when no subscription is selected", () => {
|
it("should maintain referential equality of handler functions between renders", async () => {
|
||||||
render(<SelectAccount />);
|
const { rerender } = render(<SelectAccount />);
|
||||||
|
|
||||||
expect(screen.getByTestId("account-dropdown")).toHaveAttribute("data-disabled", "true");
|
const migrationCheckbox = (await import("./Components/MigrationTypeCheckbox")).MigrationTypeCheckbox as jest.Mock;
|
||||||
});
|
const firstRenderHandler = migrationCheckbox.mock.calls[migrationCheckbox.mock.calls.length - 1][0].onChange;
|
||||||
|
|
||||||
it("should enable account dropdown when subscription is selected", () => {
|
rerender(<SelectAccount />);
|
||||||
mockContextValue.copyJobState.source.subscription = mockSubscriptions[0];
|
|
||||||
|
|
||||||
render(<SelectAccount />);
|
const secondRenderHandler = migrationCheckbox.mock.calls[migrationCheckbox.mock.calls.length - 1][0].onChange;
|
||||||
|
|
||||||
expect(screen.getByTestId("account-dropdown")).toHaveAttribute("data-disabled", "false");
|
expect(firstRenderHandler).toBe(secondRenderHandler);
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Component Props", () => {
|
|
||||||
it("should pass correct props to SubscriptionDropdown", () => {
|
|
||||||
render(<SelectAccount />);
|
|
||||||
|
|
||||||
const dropdown = screen.getByTestId("subscription-dropdown");
|
|
||||||
expect(dropdown).not.toHaveAttribute("data-selected");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should pass selected subscription ID to SubscriptionDropdown", () => {
|
|
||||||
mockContextValue.copyJobState.source.subscription = mockSubscriptions[0];
|
|
||||||
|
|
||||||
render(<SelectAccount />);
|
|
||||||
|
|
||||||
const dropdown = screen.getByTestId("subscription-dropdown");
|
|
||||||
expect(dropdown).toHaveAttribute("data-selected", "sub-1");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should pass correct props to AccountDropdown", () => {
|
|
||||||
render(<SelectAccount />);
|
|
||||||
|
|
||||||
const dropdown = screen.getByTestId("account-dropdown");
|
|
||||||
expect(dropdown).not.toHaveAttribute("data-selected");
|
|
||||||
expect(dropdown).toHaveAttribute("data-disabled", "true");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should pass selected account ID to AccountDropdown", () => {
|
|
||||||
mockContextValue.copyJobState.source.account = mockAccounts[0];
|
|
||||||
|
|
||||||
render(<SelectAccount />);
|
|
||||||
|
|
||||||
const dropdown = screen.getByTestId("account-dropdown");
|
|
||||||
expect(dropdown).toHaveAttribute("data-selected", mockAccounts[0].id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should pass correct checked state to MigrationTypeCheckbox", () => {
|
|
||||||
mockContextValue.copyJobState.migrationType = CopyJobMigrationType.Offline;
|
|
||||||
|
|
||||||
render(<SelectAccount />);
|
|
||||||
|
|
||||||
const checkbox = screen.getByTestId("migration-type-checkbox");
|
|
||||||
expect(checkbox).toHaveAttribute("data-checked", "true");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Edge Cases", () => {
|
|
||||||
it("should handle empty subscriptions array", () => {
|
|
||||||
mockUseSubscriptions.mockReturnValue([]);
|
|
||||||
mockUseDropdownOptions.mockReturnValue({
|
|
||||||
subscriptionOptions: [],
|
|
||||||
accountOptions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const { container } = render(<SelectAccount />);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle empty accounts array", () => {
|
|
||||||
mockUseDatabaseAccounts.mockReturnValue([]);
|
|
||||||
mockUseDropdownOptions.mockReturnValue({
|
|
||||||
subscriptionOptions: mockDropdownOptions.subscriptionOptions,
|
|
||||||
accountOptions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const { container } = render(<SelectAccount />);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle null subscription in context", () => {
|
|
||||||
mockContextValue.copyJobState.source.subscription = null;
|
|
||||||
|
|
||||||
const { container } = render(<SelectAccount />);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle null account in context", () => {
|
|
||||||
mockContextValue.copyJobState.source.account = null;
|
|
||||||
|
|
||||||
const { container } = render(<SelectAccount />);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle undefined subscriptions from hook", () => {
|
|
||||||
mockUseSubscriptions.mockReturnValue(undefined as any);
|
|
||||||
mockUseDropdownOptions.mockReturnValue({
|
|
||||||
subscriptionOptions: [],
|
|
||||||
accountOptions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const { container } = render(<SelectAccount />);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle undefined accounts from hook", () => {
|
|
||||||
mockUseDatabaseAccounts.mockReturnValue(undefined as any);
|
|
||||||
mockUseDropdownOptions.mockReturnValue({
|
|
||||||
subscriptionOptions: mockDropdownOptions.subscriptionOptions,
|
|
||||||
accountOptions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const { container } = render(<SelectAccount />);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should filter out non-SQL accounts correctly", () => {
|
|
||||||
const mixedAccounts = [
|
|
||||||
{ ...mockAccounts[0], kind: "GlobalDocumentDB" },
|
|
||||||
{ ...mockAccounts[1], kind: "MongoDB" },
|
|
||||||
];
|
|
||||||
|
|
||||||
mockUseDatabaseAccounts.mockReturnValue(mixedAccounts);
|
|
||||||
mockApiType.mockImplementation((account) => (account.kind === "GlobalDocumentDB" ? "SQL" : "Mongo"));
|
|
||||||
|
|
||||||
render(<SelectAccount />);
|
|
||||||
expect(mockApiType).toHaveBeenCalledTimes(2);
|
|
||||||
|
|
||||||
const sqlOnlyAccounts = mixedAccounts.filter((account) => apiType(account) === "SQL");
|
|
||||||
expect(mockUseDropdownOptions).toHaveBeenCalledWith(mockSubscriptions, sqlOnlyAccounts);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Complete Workflow", () => {
|
|
||||||
it("should render complete workflow with all selections", () => {
|
|
||||||
mockContextValue.copyJobState.source.subscription = mockSubscriptions[0];
|
|
||||||
mockContextValue.copyJobState.source.account = mockAccounts[0];
|
|
||||||
mockContextValue.copyJobState.migrationType = CopyJobMigrationType.Online;
|
|
||||||
|
|
||||||
const { container } = render(<SelectAccount />);
|
|
||||||
|
|
||||||
expect(screen.getByTestId("subscription-dropdown")).toHaveAttribute("data-selected", "sub-1");
|
|
||||||
expect(screen.getByTestId("account-dropdown")).toHaveAttribute("data-selected", mockAccounts[0].id);
|
|
||||||
expect(screen.getByTestId("account-dropdown")).toHaveAttribute("data-disabled", "false");
|
|
||||||
expect(screen.getByTestId("migration-type-checkbox")).toHaveAttribute("data-checked", "false");
|
|
||||||
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,52 +1,37 @@
|
|||||||
/* eslint-disable react/display-name */
|
import { Stack, Text } from "@fluentui/react";
|
||||||
import { Stack } from "@fluentui/react";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { apiType } from "UserContext";
|
|
||||||
import { DatabaseAccount, Subscription } from "../../../../../Contracts/DataModels";
|
|
||||||
import { useDatabaseAccounts } from "../../../../../hooks/useDatabaseAccounts";
|
|
||||||
import { useSubscriptions } from "../../../../../hooks/useSubscriptions";
|
|
||||||
import ContainerCopyMessages from "../../../ContainerCopyMessages";
|
import ContainerCopyMessages from "../../../ContainerCopyMessages";
|
||||||
import { useCopyJobContext } from "../../../Context/CopyJobContext";
|
import { useCopyJobContext } from "../../../Context/CopyJobContext";
|
||||||
import { CopyJobMigrationType } from "../../../Enums/CopyJobEnums";
|
import { CopyJobMigrationType } from "../../../Enums/CopyJobEnums";
|
||||||
import { AccountDropdown } from "./Components/AccountDropdown";
|
import { AccountDropdown } from "./Components/AccountDropdown";
|
||||||
import { MigrationTypeCheckbox } from "./Components/MigrationTypeCheckbox";
|
import { MigrationTypeCheckbox } from "./Components/MigrationTypeCheckbox";
|
||||||
import { SubscriptionDropdown } from "./Components/SubscriptionDropdown";
|
import { SubscriptionDropdown } from "./Components/SubscriptionDropdown";
|
||||||
import { useDropdownOptions, useEventHandlers } from "./Utils/selectAccountUtils";
|
|
||||||
|
|
||||||
const SelectAccount = React.memo(() => {
|
const SelectAccount = React.memo(() => {
|
||||||
const { copyJobState, setCopyJobState } = useCopyJobContext();
|
const { copyJobState, setCopyJobState } = useCopyJobContext();
|
||||||
const selectedSubscriptionId = copyJobState?.source?.subscription?.subscriptionId;
|
|
||||||
const selectedSourceAccountId = copyJobState?.source?.account?.id;
|
|
||||||
|
|
||||||
const subscriptions: Subscription[] = useSubscriptions();
|
const handleMigrationTypeChange = (_ev?: React.FormEvent<HTMLElement>, checked?: boolean) => {
|
||||||
const allAccounts: DatabaseAccount[] = useDatabaseAccounts(selectedSubscriptionId);
|
setCopyJobState((prevState) => ({
|
||||||
const sqlApiOnlyAccounts: DatabaseAccount[] = allAccounts?.filter((account) => apiType(account) === "SQL");
|
...prevState,
|
||||||
|
migrationType: checked ? CopyJobMigrationType.Offline : CopyJobMigrationType.Online,
|
||||||
const { subscriptionOptions, accountOptions } = useDropdownOptions(subscriptions, sqlApiOnlyAccounts);
|
}));
|
||||||
const { handleSelectSourceAccount, handleMigrationTypeChange } = useEventHandlers(setCopyJobState);
|
};
|
||||||
|
|
||||||
const migrationTypeChecked = copyJobState?.migrationType === CopyJobMigrationType.Offline;
|
const migrationTypeChecked = copyJobState?.migrationType === CopyJobMigrationType.Offline;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack className="selectAccountContainer" tokens={{ childrenGap: 15 }}>
|
<Stack data-test="Panel:SelectAccountContainer" className="selectAccountContainer" tokens={{ childrenGap: 15 }}>
|
||||||
<span>{ContainerCopyMessages.selectAccountDescription}</span>
|
<Text className="themeText">{ContainerCopyMessages.selectAccountDescription}</Text>
|
||||||
|
|
||||||
<SubscriptionDropdown
|
<SubscriptionDropdown />
|
||||||
options={subscriptionOptions}
|
|
||||||
selectedKey={selectedSubscriptionId}
|
|
||||||
onChange={(_ev, option) => handleSelectSourceAccount("subscription", option?.data)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<AccountDropdown
|
<AccountDropdown />
|
||||||
options={accountOptions}
|
|
||||||
selectedKey={selectedSourceAccountId}
|
|
||||||
disabled={!selectedSubscriptionId}
|
|
||||||
onChange={(_ev, option) => handleSelectSourceAccount("account", option?.data)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MigrationTypeCheckbox checked={migrationTypeChecked} onChange={handleMigrationTypeChange} />
|
<MigrationTypeCheckbox checked={migrationTypeChecked} onChange={handleMigrationTypeChange} />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
SelectAccount.displayName = "SelectAccount";
|
||||||
|
|
||||||
export default SelectAccount;
|
export default SelectAccount;
|
||||||
|
|||||||
@@ -1,526 +0,0 @@
|
|||||||
import "@testing-library/jest-dom";
|
|
||||||
import { fireEvent, render } from "@testing-library/react";
|
|
||||||
import React from "react";
|
|
||||||
import { noop } from "underscore";
|
|
||||||
import { DatabaseAccount, Subscription } from "../../../../../../Contracts/DataModels";
|
|
||||||
import { CopyJobMigrationType } from "../../../../Enums/CopyJobEnums";
|
|
||||||
import { CopyJobContextState } from "../../../../Types/CopyJobTypes";
|
|
||||||
import { useDropdownOptions, useEventHandlers } from "./selectAccountUtils";
|
|
||||||
|
|
||||||
jest.mock("../../../Utils/useCopyJobPrerequisitesCache", () => ({
|
|
||||||
useCopyJobPrerequisitesCache: jest.fn(() => ({
|
|
||||||
setValidationCache: jest.fn(),
|
|
||||||
})),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const mockSubscriptions: Subscription[] = [
|
|
||||||
{
|
|
||||||
subscriptionId: "sub-1",
|
|
||||||
displayName: "Test Subscription 1",
|
|
||||||
state: "Enabled",
|
|
||||||
subscriptionPolicies: {
|
|
||||||
locationPlacementId: "test",
|
|
||||||
quotaId: "test",
|
|
||||||
spendingLimit: "Off",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
subscriptionId: "sub-2",
|
|
||||||
displayName: "Test Subscription 2",
|
|
||||||
state: "Enabled",
|
|
||||||
subscriptionPolicies: {
|
|
||||||
locationPlacementId: "test",
|
|
||||||
quotaId: "test",
|
|
||||||
spendingLimit: "Off",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const mockAccounts: DatabaseAccount[] = [
|
|
||||||
{
|
|
||||||
id: "account-1",
|
|
||||||
name: "Test Account 1",
|
|
||||||
location: "East US",
|
|
||||||
type: "Microsoft.DocumentDB/databaseAccounts",
|
|
||||||
kind: "GlobalDocumentDB",
|
|
||||||
properties: {
|
|
||||||
documentEndpoint: "https://test1.documents.azure.com:443/",
|
|
||||||
gremlinEndpoint: "https://test1.gremlin.cosmosdb.azure.com:443/",
|
|
||||||
tableEndpoint: "https://test1.table.cosmosdb.azure.com:443/",
|
|
||||||
cassandraEndpoint: "https://test1.cassandra.cosmosdb.azure.com:443/",
|
|
||||||
capabilities: [],
|
|
||||||
writeLocations: [],
|
|
||||||
readLocations: [],
|
|
||||||
locations: [],
|
|
||||||
ipRules: [],
|
|
||||||
enableMultipleWriteLocations: false,
|
|
||||||
isVirtualNetworkFilterEnabled: false,
|
|
||||||
enableFreeTier: false,
|
|
||||||
enableAnalyticalStorage: false,
|
|
||||||
publicNetworkAccess: "Enabled",
|
|
||||||
defaultIdentity: "",
|
|
||||||
disableLocalAuth: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "account-2",
|
|
||||||
name: "Test Account 2",
|
|
||||||
location: "West US",
|
|
||||||
type: "Microsoft.DocumentDB/databaseAccounts",
|
|
||||||
kind: "GlobalDocumentDB",
|
|
||||||
properties: {
|
|
||||||
documentEndpoint: "https://test2.documents.azure.com:443/",
|
|
||||||
gremlinEndpoint: "https://test2.gremlin.cosmosdb.azure.com:443/",
|
|
||||||
tableEndpoint: "https://test2.table.cosmosdb.azure.com:443/",
|
|
||||||
cassandraEndpoint: "https://test2.cassandra.cosmosdb.azure.com:443/",
|
|
||||||
capabilities: [],
|
|
||||||
writeLocations: [],
|
|
||||||
readLocations: [],
|
|
||||||
locations: [],
|
|
||||||
enableMultipleWriteLocations: false,
|
|
||||||
isVirtualNetworkFilterEnabled: false,
|
|
||||||
enableFreeTier: false,
|
|
||||||
enableAnalyticalStorage: false,
|
|
||||||
publicNetworkAccess: "Enabled",
|
|
||||||
defaultIdentity: "",
|
|
||||||
disableLocalAuth: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const DropdownOptionsTestComponent: React.FC<{
|
|
||||||
subscriptions: Subscription[];
|
|
||||||
accounts: DatabaseAccount[];
|
|
||||||
onResult?: (result: { subscriptionOptions: any[]; accountOptions: any[] }) => void;
|
|
||||||
}> = ({ subscriptions, accounts, onResult }) => {
|
|
||||||
const result = useDropdownOptions(subscriptions, accounts);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (onResult) {
|
|
||||||
onResult(result);
|
|
||||||
}
|
|
||||||
}, [result, onResult]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div data-testid="subscription-options-count">{result.subscriptionOptions.length}</div>
|
|
||||||
<div data-testid="account-options-count">{result.accountOptions.length}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const EventHandlersTestComponent: React.FC<{
|
|
||||||
setCopyJobState: jest.Mock;
|
|
||||||
onResult?: (result: any) => void;
|
|
||||||
}> = ({ setCopyJobState, onResult }) => {
|
|
||||||
const result = useEventHandlers(setCopyJobState);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (onResult) {
|
|
||||||
onResult(result);
|
|
||||||
}
|
|
||||||
}, [result, onResult]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
data-testid="select-subscription-button"
|
|
||||||
onClick={() => result.handleSelectSourceAccount("subscription", mockSubscriptions[0] as any)}
|
|
||||||
>
|
|
||||||
Select Subscription
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
data-testid="select-account-button"
|
|
||||||
onClick={() => result.handleSelectSourceAccount("account", mockAccounts[0] as any)}
|
|
||||||
>
|
|
||||||
Select Account
|
|
||||||
</button>
|
|
||||||
<button data-testid="migration-type-button" onClick={(e) => result.handleMigrationTypeChange(e, true)}>
|
|
||||||
Change Migration Type
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("selectAccountUtils", () => {
|
|
||||||
describe("useDropdownOptions", () => {
|
|
||||||
it("should return empty arrays when subscriptions and accounts are undefined", () => {
|
|
||||||
let capturedResult: any;
|
|
||||||
|
|
||||||
render(
|
|
||||||
<DropdownOptionsTestComponent
|
|
||||||
subscriptions={undefined as any}
|
|
||||||
accounts={undefined as any}
|
|
||||||
onResult={(result) => {
|
|
||||||
capturedResult = result;
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(capturedResult).toEqual({
|
|
||||||
subscriptionOptions: [],
|
|
||||||
accountOptions: [],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return empty arrays when subscriptions and accounts are empty arrays", () => {
|
|
||||||
let capturedResult: any;
|
|
||||||
|
|
||||||
render(
|
|
||||||
<DropdownOptionsTestComponent
|
|
||||||
subscriptions={[]}
|
|
||||||
accounts={[]}
|
|
||||||
onResult={(result) => {
|
|
||||||
capturedResult = result;
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(capturedResult).toEqual({
|
|
||||||
subscriptionOptions: [],
|
|
||||||
accountOptions: [],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should transform subscriptions into dropdown options correctly", () => {
|
|
||||||
let capturedResult: any;
|
|
||||||
|
|
||||||
render(
|
|
||||||
<DropdownOptionsTestComponent
|
|
||||||
subscriptions={mockSubscriptions}
|
|
||||||
accounts={[]}
|
|
||||||
onResult={(result) => {
|
|
||||||
capturedResult = result;
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(capturedResult.subscriptionOptions).toHaveLength(2);
|
|
||||||
expect(capturedResult.subscriptionOptions[0]).toEqual({
|
|
||||||
key: "sub-1",
|
|
||||||
text: "Test Subscription 1",
|
|
||||||
data: mockSubscriptions[0],
|
|
||||||
});
|
|
||||||
expect(capturedResult.subscriptionOptions[1]).toEqual({
|
|
||||||
key: "sub-2",
|
|
||||||
text: "Test Subscription 2",
|
|
||||||
data: mockSubscriptions[1],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should transform accounts into dropdown options correctly", () => {
|
|
||||||
let capturedResult: any;
|
|
||||||
|
|
||||||
render(
|
|
||||||
<DropdownOptionsTestComponent
|
|
||||||
subscriptions={[]}
|
|
||||||
accounts={mockAccounts}
|
|
||||||
onResult={(result) => {
|
|
||||||
capturedResult = result;
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(capturedResult.accountOptions).toHaveLength(2);
|
|
||||||
expect(capturedResult.accountOptions[0]).toEqual({
|
|
||||||
key: "account-1",
|
|
||||||
text: "Test Account 1",
|
|
||||||
data: mockAccounts[0],
|
|
||||||
});
|
|
||||||
expect(capturedResult.accountOptions[1]).toEqual({
|
|
||||||
key: "account-2",
|
|
||||||
text: "Test Account 2",
|
|
||||||
data: mockAccounts[1],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle both subscriptions and accounts correctly", () => {
|
|
||||||
let capturedResult: any;
|
|
||||||
|
|
||||||
render(
|
|
||||||
<DropdownOptionsTestComponent
|
|
||||||
subscriptions={mockSubscriptions}
|
|
||||||
accounts={mockAccounts}
|
|
||||||
onResult={(result) => {
|
|
||||||
capturedResult = result;
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(capturedResult.subscriptionOptions).toHaveLength(2);
|
|
||||||
expect(capturedResult.accountOptions).toHaveLength(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("useEventHandlers", () => {
|
|
||||||
let mockSetCopyJobState: jest.Mock;
|
|
||||||
let mockSetValidationCache: jest.Mock;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
mockSetCopyJobState = jest.fn();
|
|
||||||
mockSetValidationCache = jest.fn();
|
|
||||||
|
|
||||||
const { useCopyJobPrerequisitesCache } = await import("../../../Utils/useCopyJobPrerequisitesCache");
|
|
||||||
(useCopyJobPrerequisitesCache as unknown as jest.Mock).mockReturnValue({
|
|
||||||
setValidationCache: mockSetValidationCache,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle subscription selection correctly", () => {
|
|
||||||
const { getByTestId } = render(
|
|
||||||
<EventHandlersTestComponent setCopyJobState={mockSetCopyJobState} onResult={noop} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
fireEvent.click(getByTestId("select-subscription-button"));
|
|
||||||
|
|
||||||
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
|
||||||
expect(mockSetValidationCache).toHaveBeenCalledWith(new Map<string, boolean>());
|
|
||||||
|
|
||||||
const stateUpdater = mockSetCopyJobState.mock.calls[0][0];
|
|
||||||
const mockPrevState: CopyJobContextState = {
|
|
||||||
source: {
|
|
||||||
subscription: null,
|
|
||||||
account: { id: "existing-account" } as any,
|
|
||||||
},
|
|
||||||
migrationType: CopyJobMigrationType.Online,
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
const newState = stateUpdater(mockPrevState);
|
|
||||||
expect(newState).toEqual({
|
|
||||||
source: {
|
|
||||||
subscription: mockSubscriptions[0],
|
|
||||||
account: null,
|
|
||||||
},
|
|
||||||
migrationType: CopyJobMigrationType.Online,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle account selection correctly", () => {
|
|
||||||
const { getByTestId } = render(
|
|
||||||
<EventHandlersTestComponent setCopyJobState={mockSetCopyJobState} onResult={noop} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
fireEvent.click(getByTestId("select-account-button"));
|
|
||||||
|
|
||||||
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
|
||||||
expect(mockSetValidationCache).toHaveBeenCalledWith(new Map<string, boolean>());
|
|
||||||
|
|
||||||
const stateUpdater = mockSetCopyJobState.mock.calls[0][0];
|
|
||||||
const mockPrevState: CopyJobContextState = {
|
|
||||||
source: {
|
|
||||||
subscription: { subscriptionId: "existing-sub" } as any,
|
|
||||||
account: null,
|
|
||||||
},
|
|
||||||
migrationType: CopyJobMigrationType.Online,
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
const newState = stateUpdater(mockPrevState);
|
|
||||||
expect(newState).toEqual({
|
|
||||||
source: {
|
|
||||||
subscription: { subscriptionId: "existing-sub" },
|
|
||||||
account: mockAccounts[0],
|
|
||||||
},
|
|
||||||
migrationType: CopyJobMigrationType.Online,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle subscription selection with undefined data", () => {
|
|
||||||
let capturedHandlers: any;
|
|
||||||
|
|
||||||
render(
|
|
||||||
<EventHandlersTestComponent
|
|
||||||
setCopyJobState={mockSetCopyJobState}
|
|
||||||
onResult={(result) => {
|
|
||||||
capturedHandlers = result;
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
capturedHandlers.handleSelectSourceAccount("subscription", undefined);
|
|
||||||
|
|
||||||
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
|
||||||
|
|
||||||
const stateUpdater = mockSetCopyJobState.mock.calls[0][0];
|
|
||||||
const mockPrevState: CopyJobContextState = {
|
|
||||||
source: {
|
|
||||||
subscription: { subscriptionId: "existing-sub" } as any,
|
|
||||||
account: { id: "existing-account" } as any,
|
|
||||||
},
|
|
||||||
migrationType: CopyJobMigrationType.Online,
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
const newState = stateUpdater(mockPrevState);
|
|
||||||
expect(newState).toEqual({
|
|
||||||
source: {
|
|
||||||
subscription: null,
|
|
||||||
account: null,
|
|
||||||
},
|
|
||||||
migrationType: CopyJobMigrationType.Online,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle account selection with undefined data", () => {
|
|
||||||
let capturedHandlers: any;
|
|
||||||
|
|
||||||
render(
|
|
||||||
<EventHandlersTestComponent
|
|
||||||
setCopyJobState={mockSetCopyJobState}
|
|
||||||
onResult={(result) => {
|
|
||||||
capturedHandlers = result;
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
capturedHandlers.handleSelectSourceAccount("account", undefined);
|
|
||||||
|
|
||||||
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
|
||||||
|
|
||||||
const stateUpdater = mockSetCopyJobState.mock.calls[0][0];
|
|
||||||
const mockPrevState: CopyJobContextState = {
|
|
||||||
source: {
|
|
||||||
subscription: { subscriptionId: "existing-sub" } as any,
|
|
||||||
account: { id: "existing-account" } as any,
|
|
||||||
},
|
|
||||||
migrationType: CopyJobMigrationType.Online,
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
const newState = stateUpdater(mockPrevState);
|
|
||||||
expect(newState).toEqual({
|
|
||||||
source: {
|
|
||||||
subscription: { subscriptionId: "existing-sub" },
|
|
||||||
account: null,
|
|
||||||
},
|
|
||||||
migrationType: CopyJobMigrationType.Online,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle migration type change to offline", () => {
|
|
||||||
const { getByTestId } = render(<EventHandlersTestComponent setCopyJobState={mockSetCopyJobState} />);
|
|
||||||
|
|
||||||
fireEvent.click(getByTestId("migration-type-button"));
|
|
||||||
|
|
||||||
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
|
||||||
expect(mockSetValidationCache).toHaveBeenCalledWith(new Map<string, boolean>());
|
|
||||||
|
|
||||||
const stateUpdater = mockSetCopyJobState.mock.calls[0][0];
|
|
||||||
const mockPrevState: CopyJobContextState = {
|
|
||||||
source: {
|
|
||||||
subscription: null,
|
|
||||||
account: null,
|
|
||||||
},
|
|
||||||
migrationType: CopyJobMigrationType.Online,
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
const newState = stateUpdater(mockPrevState);
|
|
||||||
expect(newState).toEqual({
|
|
||||||
source: {
|
|
||||||
subscription: null,
|
|
||||||
account: null,
|
|
||||||
},
|
|
||||||
migrationType: CopyJobMigrationType.Offline,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle migration type change to online when checked is false", () => {
|
|
||||||
let capturedHandlers: any;
|
|
||||||
|
|
||||||
render(
|
|
||||||
<EventHandlersTestComponent
|
|
||||||
setCopyJobState={mockSetCopyJobState}
|
|
||||||
onResult={(result) => {
|
|
||||||
capturedHandlers = result;
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
capturedHandlers.handleMigrationTypeChange(undefined, false);
|
|
||||||
|
|
||||||
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
|
|
||||||
|
|
||||||
const stateUpdater = mockSetCopyJobState.mock.calls[0][0];
|
|
||||||
const mockPrevState: CopyJobContextState = {
|
|
||||||
source: {
|
|
||||||
subscription: null,
|
|
||||||
account: null,
|
|
||||||
},
|
|
||||||
migrationType: CopyJobMigrationType.Offline,
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
const newState = stateUpdater(mockPrevState);
|
|
||||||
expect(newState).toEqual({
|
|
||||||
source: {
|
|
||||||
subscription: null,
|
|
||||||
account: null,
|
|
||||||
},
|
|
||||||
migrationType: CopyJobMigrationType.Online,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should preserve other state properties when updating", () => {
|
|
||||||
let capturedHandlers: any;
|
|
||||||
|
|
||||||
render(
|
|
||||||
<EventHandlersTestComponent
|
|
||||||
setCopyJobState={mockSetCopyJobState}
|
|
||||||
onResult={(result) => {
|
|
||||||
capturedHandlers = result;
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
capturedHandlers.handleSelectSourceAccount("subscription", mockSubscriptions[0] as Subscription);
|
|
||||||
|
|
||||||
const stateUpdater = mockSetCopyJobState.mock.calls[0][0];
|
|
||||||
const mockPrevState = {
|
|
||||||
jobName: "Test Job",
|
|
||||||
source: {
|
|
||||||
subscription: null,
|
|
||||||
account: null,
|
|
||||||
databaseId: "test-database-id",
|
|
||||||
containerId: "test-container-id",
|
|
||||||
},
|
|
||||||
migrationType: CopyJobMigrationType.Online,
|
|
||||||
target: {
|
|
||||||
account: { id: "dest-account" } as DatabaseAccount,
|
|
||||||
databaseId: "test-database-id",
|
|
||||||
containerId: "test-container-id",
|
|
||||||
subscriptionId: "dest-sub-id",
|
|
||||||
},
|
|
||||||
} as CopyJobContextState;
|
|
||||||
|
|
||||||
const newState = stateUpdater(mockPrevState);
|
|
||||||
expect(newState.target).toEqual(mockPrevState.target);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the same state for unknown selection type", () => {
|
|
||||||
let capturedHandlers: any;
|
|
||||||
|
|
||||||
render(
|
|
||||||
<EventHandlersTestComponent
|
|
||||||
setCopyJobState={mockSetCopyJobState}
|
|
||||||
onResult={(result) => {
|
|
||||||
capturedHandlers = result;
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
capturedHandlers.handleSelectSourceAccount("unknown" as any, mockSubscriptions[0] as any);
|
|
||||||
|
|
||||||
const stateUpdater = mockSetCopyJobState.mock.calls[0][0];
|
|
||||||
const mockPrevState: CopyJobContextState = {
|
|
||||||
source: {
|
|
||||||
subscription: { subscriptionId: "existing-sub" } as any,
|
|
||||||
account: { id: "existing-account" } as any,
|
|
||||||
},
|
|
||||||
migrationType: CopyJobMigrationType.Online,
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
const newState = stateUpdater(mockPrevState);
|
|
||||||
expect(newState).toEqual(mockPrevState);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { DatabaseAccount, Subscription } from "../../../../../../Contracts/DataModels";
|
|
||||||
import { CopyJobMigrationType } from "../../../../Enums/CopyJobEnums";
|
|
||||||
import { CopyJobContextProviderType, CopyJobContextState, DropdownOptionType } from "../../../../Types/CopyJobTypes";
|
|
||||||
import { useCopyJobPrerequisitesCache } from "../../../Utils/useCopyJobPrerequisitesCache";
|
|
||||||
|
|
||||||
export function useDropdownOptions(
|
|
||||||
subscriptions: Subscription[],
|
|
||||||
accounts: DatabaseAccount[],
|
|
||||||
): {
|
|
||||||
subscriptionOptions: DropdownOptionType[];
|
|
||||||
accountOptions: DropdownOptionType[];
|
|
||||||
} {
|
|
||||||
const subscriptionOptions =
|
|
||||||
subscriptions?.map((sub) => ({
|
|
||||||
key: sub.subscriptionId,
|
|
||||||
text: sub.displayName,
|
|
||||||
data: sub,
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
const normalizeAccountId = (id: string) => {
|
|
||||||
if (!id) {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
return id.replace(/\/Microsoft\.DocumentDb\//i, "/Microsoft.DocumentDB/");
|
|
||||||
};
|
|
||||||
|
|
||||||
const accountOptions =
|
|
||||||
accounts?.map((account) => ({
|
|
||||||
key: normalizeAccountId(account.id),
|
|
||||||
text: account.name,
|
|
||||||
data: account,
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
return { subscriptionOptions, accountOptions };
|
|
||||||
}
|
|
||||||
|
|
||||||
type setCopyJobStateType = CopyJobContextProviderType["setCopyJobState"];
|
|
||||||
|
|
||||||
export function useEventHandlers(setCopyJobState: setCopyJobStateType) {
|
|
||||||
const { setValidationCache } = useCopyJobPrerequisitesCache();
|
|
||||||
const handleSelectSourceAccount = (
|
|
||||||
type: "subscription" | "account",
|
|
||||||
data: (Subscription & DatabaseAccount) | undefined,
|
|
||||||
) => {
|
|
||||||
setCopyJobState((prevState: CopyJobContextState) => {
|
|
||||||
if (type === "subscription") {
|
|
||||||
return {
|
|
||||||
...prevState,
|
|
||||||
source: {
|
|
||||||
...prevState.source,
|
|
||||||
subscription: data || null,
|
|
||||||
account: null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (type === "account") {
|
|
||||||
return {
|
|
||||||
...prevState,
|
|
||||||
source: {
|
|
||||||
...prevState.source,
|
|
||||||
account: data || null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return prevState;
|
|
||||||
});
|
|
||||||
setValidationCache(new Map<string, boolean>());
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMigrationTypeChange = React.useCallback((_ev?: React.FormEvent<HTMLElement>, checked?: boolean) => {
|
|
||||||
setCopyJobState((prevState: CopyJobContextState) => ({
|
|
||||||
...prevState,
|
|
||||||
migrationType: checked ? CopyJobMigrationType.Offline : CopyJobMigrationType.Online,
|
|
||||||
}));
|
|
||||||
setValidationCache(new Map<string, boolean>());
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return { handleSelectSourceAccount, handleMigrationTypeChange };
|
|
||||||
}
|
|
||||||
@@ -1,510 +1,34 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`SelectAccount Component Complete Workflow should render complete workflow with all selections 1`] = `
|
exports[`SelectAccount Component Rendering should render correctly with snapshot 1`] = `
|
||||||
<div>
|
<div
|
||||||
<div
|
|
||||||
class="ms-Stack selectAccountContainer css-109"
|
class="ms-Stack selectAccountContainer css-109"
|
||||||
|
data-test="Panel:SelectAccountContainer"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="themeText css-110"
|
||||||
>
|
>
|
||||||
<span>
|
Please select a source account from which to copy.
|
||||||
Select your source account and subscription
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
data-selected="sub-1"
|
|
||||||
data-testid="subscription-dropdown"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-option-sub-1"
|
|
||||||
>
|
|
||||||
Test Subscription 1
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-option-sub-2"
|
|
||||||
>
|
|
||||||
Test Subscription 2
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-disabled="false"
|
|
||||||
data-selected="/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1"
|
|
||||||
data-testid="account-dropdown"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-testid="account-option-/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1"
|
|
||||||
>
|
|
||||||
test-cosmos-account-1
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-checked="false"
|
|
||||||
data-testid="migration-type-checkbox"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
data-testid="migration-checkbox-input"
|
|
||||||
type="checkbox"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SelectAccount Component Edge Cases should handle empty accounts array 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="ms-Stack selectAccountContainer css-109"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Select your source account and subscription
|
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
data-testid="subscription-dropdown"
|
data-testid="subscription-dropdown"
|
||||||
>
|
>
|
||||||
<div
|
Subscription Dropdown
|
||||||
data-testid="subscription-option-sub-1"
|
|
||||||
>
|
|
||||||
Test Subscription 1
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
data-testid="subscription-option-sub-2"
|
|
||||||
>
|
|
||||||
Test Subscription 2
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-disabled="true"
|
|
||||||
data-testid="account-dropdown"
|
data-testid="account-dropdown"
|
||||||
/>
|
>
|
||||||
|
Account Dropdown
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
data-checked="true"
|
|
||||||
data-testid="migration-type-checkbox"
|
data-testid="migration-type-checkbox"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
checked=""
|
aria-label="Migration Type Checkbox"
|
||||||
data-testid="migration-checkbox-input"
|
data-testid="migration-checkbox-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
/>
|
/>
|
||||||
</div>
|
Copy container in offline mode
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SelectAccount Component Edge Cases should handle empty subscriptions array 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="ms-Stack selectAccountContainer css-109"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Select your source account and subscription
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-dropdown"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
data-disabled="true"
|
|
||||||
data-testid="account-dropdown"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
data-checked="true"
|
|
||||||
data-testid="migration-type-checkbox"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
checked=""
|
|
||||||
data-testid="migration-checkbox-input"
|
|
||||||
type="checkbox"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SelectAccount Component Edge Cases should handle null account in context 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="ms-Stack selectAccountContainer css-109"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Select your source account and subscription
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-dropdown"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-option-sub-1"
|
|
||||||
>
|
|
||||||
Test Subscription 1
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-option-sub-2"
|
|
||||||
>
|
|
||||||
Test Subscription 2
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-disabled="true"
|
|
||||||
data-testid="account-dropdown"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-testid="account-option-/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1"
|
|
||||||
>
|
|
||||||
test-cosmos-account-1
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-checked="true"
|
|
||||||
data-testid="migration-type-checkbox"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
checked=""
|
|
||||||
data-testid="migration-checkbox-input"
|
|
||||||
type="checkbox"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SelectAccount Component Edge Cases should handle null subscription in context 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="ms-Stack selectAccountContainer css-109"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Select your source account and subscription
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-dropdown"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-option-sub-1"
|
|
||||||
>
|
|
||||||
Test Subscription 1
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-option-sub-2"
|
|
||||||
>
|
|
||||||
Test Subscription 2
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-disabled="true"
|
|
||||||
data-testid="account-dropdown"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-testid="account-option-/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1"
|
|
||||||
>
|
|
||||||
test-cosmos-account-1
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-checked="true"
|
|
||||||
data-testid="migration-type-checkbox"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
checked=""
|
|
||||||
data-testid="migration-checkbox-input"
|
|
||||||
type="checkbox"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SelectAccount Component Edge Cases should handle undefined accounts from hook 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="ms-Stack selectAccountContainer css-109"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Select your source account and subscription
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-dropdown"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-option-sub-1"
|
|
||||||
>
|
|
||||||
Test Subscription 1
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-option-sub-2"
|
|
||||||
>
|
|
||||||
Test Subscription 2
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-disabled="true"
|
|
||||||
data-testid="account-dropdown"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
data-checked="true"
|
|
||||||
data-testid="migration-type-checkbox"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
checked=""
|
|
||||||
data-testid="migration-checkbox-input"
|
|
||||||
type="checkbox"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SelectAccount Component Edge Cases should handle undefined subscriptions from hook 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="ms-Stack selectAccountContainer css-109"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Select your source account and subscription
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-dropdown"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
data-disabled="true"
|
|
||||||
data-testid="account-dropdown"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
data-checked="true"
|
|
||||||
data-testid="migration-type-checkbox"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
checked=""
|
|
||||||
data-testid="migration-checkbox-input"
|
|
||||||
type="checkbox"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SelectAccount Component Rendering should render component with default state 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="ms-Stack selectAccountContainer css-109"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Select your source account and subscription
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-dropdown"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-option-sub-1"
|
|
||||||
>
|
|
||||||
Test Subscription 1
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-option-sub-2"
|
|
||||||
>
|
|
||||||
Test Subscription 2
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-disabled="true"
|
|
||||||
data-testid="account-dropdown"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-testid="account-option-/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1"
|
|
||||||
>
|
|
||||||
test-cosmos-account-1
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-checked="true"
|
|
||||||
data-testid="migration-type-checkbox"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
checked=""
|
|
||||||
data-testid="migration-checkbox-input"
|
|
||||||
type="checkbox"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SelectAccount Component Rendering should render with offline migration type checked 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="ms-Stack selectAccountContainer css-109"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Select your source account and subscription
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-dropdown"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-option-sub-1"
|
|
||||||
>
|
|
||||||
Test Subscription 1
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-option-sub-2"
|
|
||||||
>
|
|
||||||
Test Subscription 2
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-disabled="true"
|
|
||||||
data-testid="account-dropdown"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-testid="account-option-/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1"
|
|
||||||
>
|
|
||||||
test-cosmos-account-1
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-checked="true"
|
|
||||||
data-testid="migration-type-checkbox"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
checked=""
|
|
||||||
data-testid="migration-checkbox-input"
|
|
||||||
type="checkbox"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SelectAccount Component Rendering should render with online migration type unchecked 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="ms-Stack selectAccountContainer css-109"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Select your source account and subscription
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-dropdown"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-option-sub-1"
|
|
||||||
>
|
|
||||||
Test Subscription 1
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-option-sub-2"
|
|
||||||
>
|
|
||||||
Test Subscription 2
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-disabled="true"
|
|
||||||
data-testid="account-dropdown"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-testid="account-option-/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1"
|
|
||||||
>
|
|
||||||
test-cosmos-account-1
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-checked="false"
|
|
||||||
data-testid="migration-type-checkbox"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
data-testid="migration-checkbox-input"
|
|
||||||
type="checkbox"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SelectAccount Component Rendering should render with selected account 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="ms-Stack selectAccountContainer css-109"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Select your source account and subscription
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
data-selected="sub-1"
|
|
||||||
data-testid="subscription-dropdown"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-option-sub-1"
|
|
||||||
>
|
|
||||||
Test Subscription 1
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-option-sub-2"
|
|
||||||
>
|
|
||||||
Test Subscription 2
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-disabled="false"
|
|
||||||
data-selected="/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1"
|
|
||||||
data-testid="account-dropdown"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-testid="account-option-/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1"
|
|
||||||
>
|
|
||||||
test-cosmos-account-1
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-checked="true"
|
|
||||||
data-testid="migration-type-checkbox"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
checked=""
|
|
||||||
data-testid="migration-checkbox-input"
|
|
||||||
type="checkbox"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`SelectAccount Component Rendering should render with selected subscription 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="ms-Stack selectAccountContainer css-109"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Select your source account and subscription
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
data-selected="sub-1"
|
|
||||||
data-testid="subscription-dropdown"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-option-sub-1"
|
|
||||||
>
|
|
||||||
Test Subscription 1
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-testid="subscription-option-sub-2"
|
|
||||||
>
|
|
||||||
Test Subscription 2
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-disabled="false"
|
|
||||||
data-testid="account-dropdown"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-testid="account-option-/subscriptions/sub-1/resourceGroups/rg-1/providers/Microsoft.DocumentDB/databaseAccounts/account-1"
|
|
||||||
>
|
|
||||||
test-cosmos-account-1
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
data-checked="true"
|
|
||||||
data-testid="migration-type-checkbox"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
checked=""
|
|
||||||
data-testid="migration-checkbox-input"
|
|
||||||
type="checkbox"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -47,8 +47,12 @@ const SelectSourceAndTargetContainers = ({ showAddCollectionPanel }: SelectSourc
|
|||||||
const onDropdownChange = dropDownChangeHandler(setCopyJobState);
|
const onDropdownChange = dropDownChangeHandler(setCopyJobState);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack className="selectSourceAndTargetContainers" tokens={{ childrenGap: 25 }}>
|
<Stack
|
||||||
<span>{ContainerCopyMessages.selectSourceAndTargetContainersDescription}</span>
|
data-test="Panel:SelectSourceAndTargetContainers"
|
||||||
|
className="selectSourceAndTargetContainers"
|
||||||
|
tokens={{ childrenGap: 25 }}
|
||||||
|
>
|
||||||
|
<span className="themeText">{ContainerCopyMessages.selectSourceAndTargetContainersDescription}</span>
|
||||||
<DatabaseContainerSection
|
<DatabaseContainerSection
|
||||||
heading={ContainerCopyMessages.sourceContainerSubHeading}
|
heading={ContainerCopyMessages.sourceContainerSubHeading}
|
||||||
databaseOptions={sourceDatabaseOptions}
|
databaseOptions={sourceDatabaseOptions}
|
||||||
@@ -59,6 +63,7 @@ const SelectSourceAndTargetContainers = ({ showAddCollectionPanel }: SelectSourc
|
|||||||
selectedContainer={source?.containerId}
|
selectedContainer={source?.containerId}
|
||||||
containerDisabled={!source?.databaseId}
|
containerDisabled={!source?.databaseId}
|
||||||
containerOnChange={onDropdownChange("sourceContainer")}
|
containerOnChange={onDropdownChange("sourceContainer")}
|
||||||
|
sectionType="source"
|
||||||
/>
|
/>
|
||||||
<DatabaseContainerSection
|
<DatabaseContainerSection
|
||||||
heading={ContainerCopyMessages.targetContainerSubHeading}
|
heading={ContainerCopyMessages.targetContainerSubHeading}
|
||||||
@@ -71,6 +76,7 @@ const SelectSourceAndTargetContainers = ({ showAddCollectionPanel }: SelectSourc
|
|||||||
containerDisabled={!target?.databaseId}
|
containerDisabled={!target?.databaseId}
|
||||||
containerOnChange={onDropdownChange("targetContainer")}
|
containerOnChange={onDropdownChange("targetContainer")}
|
||||||
handleOnDemandCreateContainer={showAddCollectionPanel}
|
handleOnDemandCreateContainer={showAddCollectionPanel}
|
||||||
|
sectionType="target"
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ describe("DatabaseContainerSection", () => {
|
|||||||
selectedContainer: "container1",
|
selectedContainer: "container1",
|
||||||
containerDisabled: false,
|
containerDisabled: false,
|
||||||
containerOnChange: mockContainerOnChange,
|
containerOnChange: mockContainerOnChange,
|
||||||
|
sectionType: "source",
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -292,6 +293,7 @@ describe("DatabaseContainerSection", () => {
|
|||||||
containerOptions: mockContainerOptions,
|
containerOptions: mockContainerOptions,
|
||||||
selectedContainer: "container1",
|
selectedContainer: "container1",
|
||||||
containerOnChange: mockContainerOnChange,
|
containerOnChange: mockContainerOnChange,
|
||||||
|
sectionType: "source",
|
||||||
};
|
};
|
||||||
|
|
||||||
render(<DatabaseContainerSection {...minimalProps} />);
|
render(<DatabaseContainerSection {...minimalProps} />);
|
||||||
@@ -393,6 +395,7 @@ describe("DatabaseContainerSection", () => {
|
|||||||
containerOptions: [{ key: "c1", text: "Container 1", data: { id: "c1" } }],
|
containerOptions: [{ key: "c1", text: "Container 1", data: { id: "c1" } }],
|
||||||
selectedContainer: "c1",
|
selectedContainer: "c1",
|
||||||
containerOnChange: jest.fn(),
|
containerOnChange: jest.fn(),
|
||||||
|
sectionType: "source",
|
||||||
};
|
};
|
||||||
|
|
||||||
const { container } = render(<DatabaseContainerSection {...minimalProps} />);
|
const { container } = render(<DatabaseContainerSection {...minimalProps} />);
|
||||||
@@ -411,6 +414,7 @@ describe("DatabaseContainerSection", () => {
|
|||||||
containerDisabled: false,
|
containerDisabled: false,
|
||||||
containerOnChange: jest.fn(),
|
containerOnChange: jest.fn(),
|
||||||
handleOnDemandCreateContainer: jest.fn(),
|
handleOnDemandCreateContainer: jest.fn(),
|
||||||
|
sectionType: "target",
|
||||||
};
|
};
|
||||||
|
|
||||||
const { container } = render(<DatabaseContainerSection {...fullProps} />);
|
const { container } = render(<DatabaseContainerSection {...fullProps} />);
|
||||||
@@ -428,6 +432,7 @@ describe("DatabaseContainerSection", () => {
|
|||||||
selectedContainer: "container1",
|
selectedContainer: "container1",
|
||||||
containerDisabled: true,
|
containerDisabled: true,
|
||||||
containerOnChange: jest.fn(),
|
containerOnChange: jest.fn(),
|
||||||
|
sectionType: "target",
|
||||||
};
|
};
|
||||||
|
|
||||||
const { container } = render(<DatabaseContainerSection {...disabledProps} />);
|
const { container } = render(<DatabaseContainerSection {...disabledProps} />);
|
||||||
@@ -443,6 +448,7 @@ describe("DatabaseContainerSection", () => {
|
|||||||
containerOptions: [],
|
containerOptions: [],
|
||||||
selectedContainer: "",
|
selectedContainer: "",
|
||||||
containerOnChange: jest.fn(),
|
containerOnChange: jest.fn(),
|
||||||
|
sectionType: "target",
|
||||||
};
|
};
|
||||||
|
|
||||||
const { container } = render(<DatabaseContainerSection {...emptyOptionsProps} />);
|
const { container } = render(<DatabaseContainerSection {...emptyOptionsProps} />);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export const DatabaseContainerSection = ({
|
|||||||
containerDisabled,
|
containerDisabled,
|
||||||
containerOnChange,
|
containerOnChange,
|
||||||
handleOnDemandCreateContainer,
|
handleOnDemandCreateContainer,
|
||||||
|
sectionType,
|
||||||
}: DatabaseContainerSectionProps) => (
|
}: DatabaseContainerSectionProps) => (
|
||||||
<Stack tokens={{ childrenGap: 15 }} className="databaseContainerSection">
|
<Stack tokens={{ childrenGap: 15 }} className="databaseContainerSection">
|
||||||
<label className="subHeading">{heading}</label>
|
<label className="subHeading">{heading}</label>
|
||||||
@@ -27,6 +28,7 @@ export const DatabaseContainerSection = ({
|
|||||||
disabled={!!databaseDisabled}
|
disabled={!!databaseDisabled}
|
||||||
selectedKey={selectedDatabase}
|
selectedKey={selectedDatabase}
|
||||||
onChange={databaseOnChange}
|
onChange={databaseOnChange}
|
||||||
|
data-test={`${sectionType}-databaseDropdown`}
|
||||||
/>
|
/>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
<FieldRow label={ContainerCopyMessages.containerDropdownLabel}>
|
<FieldRow label={ContainerCopyMessages.containerDropdownLabel}>
|
||||||
@@ -39,9 +41,14 @@ export const DatabaseContainerSection = ({
|
|||||||
disabled={!!containerDisabled}
|
disabled={!!containerDisabled}
|
||||||
selectedKey={selectedContainer}
|
selectedKey={selectedContainer}
|
||||||
onChange={containerOnChange}
|
onChange={containerOnChange}
|
||||||
|
data-test={`${sectionType}-containerDropdown`}
|
||||||
/>
|
/>
|
||||||
{handleOnDemandCreateContainer && (
|
{handleOnDemandCreateContainer && (
|
||||||
<ActionButton className="create-container-link-btn" onClick={() => handleOnDemandCreateContainer()}>
|
<ActionButton
|
||||||
|
className="create-container-link-btn"
|
||||||
|
style={{ color: "var(--colorBrandForeground1)" }}
|
||||||
|
onClick={() => handleOnDemandCreateContainer()}
|
||||||
|
>
|
||||||
{ContainerCopyMessages.createContainerButtonLabel}
|
{ContainerCopyMessages.createContainerButtonLabel}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with all pro
|
|||||||
class="ms-Dropdown is-required dropdown-112"
|
class="ms-Dropdown is-required dropdown-112"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="target-databaseDropdown"
|
||||||
id="Dropdown98"
|
id="Dropdown98"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@@ -94,6 +95,7 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with all pro
|
|||||||
class="ms-Dropdown is-required dropdown-112"
|
class="ms-Dropdown is-required dropdown-112"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="target-containerDropdown"
|
||||||
id="Dropdown99"
|
id="Dropdown99"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@@ -182,6 +184,7 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with disable
|
|||||||
class="ms-Dropdown is-disabled is-required dropdown-143"
|
class="ms-Dropdown is-disabled is-required dropdown-143"
|
||||||
data-is-focusable="false"
|
data-is-focusable="false"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="target-databaseDropdown"
|
||||||
id="Dropdown103"
|
id="Dropdown103"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
@@ -239,6 +242,7 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with disable
|
|||||||
class="ms-Dropdown is-disabled is-required dropdown-143"
|
class="ms-Dropdown is-disabled is-required dropdown-143"
|
||||||
data-is-focusable="false"
|
data-is-focusable="false"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="target-containerDropdown"
|
||||||
id="Dropdown104"
|
id="Dropdown104"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
@@ -306,6 +310,7 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with empty o
|
|||||||
class="ms-Dropdown is-required dropdown-112"
|
class="ms-Dropdown is-required dropdown-112"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="target-databaseDropdown"
|
||||||
id="Dropdown105"
|
id="Dropdown105"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@@ -363,6 +368,7 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with empty o
|
|||||||
class="ms-Dropdown is-required dropdown-112"
|
class="ms-Dropdown is-required dropdown-112"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="target-containerDropdown"
|
||||||
id="Dropdown106"
|
id="Dropdown106"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@@ -430,6 +436,7 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with minimal
|
|||||||
class="ms-Dropdown is-required dropdown-112"
|
class="ms-Dropdown is-required dropdown-112"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="source-databaseDropdown"
|
||||||
id="Dropdown96"
|
id="Dropdown96"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@@ -487,6 +494,7 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with minimal
|
|||||||
class="ms-Dropdown is-required dropdown-112"
|
class="ms-Dropdown is-required dropdown-112"
|
||||||
data-is-focusable="true"
|
data-is-focusable="true"
|
||||||
data-ktp-target="true"
|
data-ktp-target="true"
|
||||||
|
data-test="source-containerDropdown"
|
||||||
id="Dropdown97"
|
id="Dropdown97"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export function useCopyJobNavigation() {
|
|||||||
const [state, dispatch] = useReducer(navigationReducer, { screenHistory: [SCREEN_KEYS.SelectAccount] });
|
const [state, dispatch] = useReducer(navigationReducer, { screenHistory: [SCREEN_KEYS.SelectAccount] });
|
||||||
|
|
||||||
const handlePrevious = useCallback(() => {
|
const handlePrevious = useCallback(() => {
|
||||||
|
setContextError(null);
|
||||||
dispatch({ type: "PREVIOUS" });
|
dispatch({ type: "PREVIOUS" });
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ const CopyJobActionMenu: React.FC<CopyJobActionMenuProps> = ({ job, handleClick
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
|
data-test={`CopyJobActionMenu/Button:${job.Name}`}
|
||||||
role="button"
|
role="button"
|
||||||
iconProps={{ iconName: "More", styles: { root: { fontSize: "20px", fontWeight: "bold" } } }}
|
iconProps={{ iconName: "More", styles: { root: { fontSize: "20px", fontWeight: "bold" } } }}
|
||||||
menuProps={{ items: getMenuItems() }}
|
menuProps={{ items: getMenuItems() }}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { DetailsList, DetailsListLayoutMode, IColumn, Stack, Text } from "@fluentui/react";
|
import { DetailsList, DetailsListLayoutMode, IColumn, Stack, Text } from "@fluentui/react";
|
||||||
import React, { memo } from "react";
|
import React, { memo } from "react";
|
||||||
|
import { useThemeStore } from "../../../../hooks/useTheme";
|
||||||
import ContainerCopyMessages from "../../ContainerCopyMessages";
|
import ContainerCopyMessages from "../../ContainerCopyMessages";
|
||||||
import { CopyJobStatusType } from "../../Enums/CopyJobEnums";
|
import { CopyJobStatusType } from "../../Enums/CopyJobEnums";
|
||||||
import { CopyJobType } from "../../Types/CopyJobTypes";
|
import { CopyJobType } from "../../Types/CopyJobTypes";
|
||||||
@@ -63,6 +64,19 @@ const getCopyJobDetailsListColumns = (): IColumn[] => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const CopyJobDetails: React.FC<CopyJobDetailsProps> = ({ job }) => {
|
const CopyJobDetails: React.FC<CopyJobDetailsProps> = ({ job }) => {
|
||||||
|
const isDarkMode = useThemeStore((state) => state.isDarkMode);
|
||||||
|
|
||||||
|
const errorMessageStyle: React.CSSProperties = {
|
||||||
|
whiteSpace: "pre-wrap",
|
||||||
|
...(isDarkMode && {
|
||||||
|
whiteSpace: "pre-wrap",
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
padding: "10px",
|
||||||
|
borderRadius: "4px",
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
const selectedContainers = [
|
const selectedContainers = [
|
||||||
{
|
{
|
||||||
sourceContainerName: job?.Source?.containerName || "N/A",
|
sourceContainerName: job?.Source?.containerName || "N/A",
|
||||||
@@ -77,10 +91,10 @@ const CopyJobDetails: React.FC<CopyJobDetailsProps> = ({ job }) => {
|
|||||||
<Stack className="copyJobDetailsContainer" tokens={{ childrenGap: 15 }} data-testid="copy-job-details">
|
<Stack className="copyJobDetailsContainer" tokens={{ childrenGap: 15 }} data-testid="copy-job-details">
|
||||||
{job.Error ? (
|
{job.Error ? (
|
||||||
<Stack.Item data-testid="error-stack" style={sectionCss.verticalAlign}>
|
<Stack.Item data-testid="error-stack" style={sectionCss.verticalAlign}>
|
||||||
<Text className="bold" style={sectionCss.headingText}>
|
<Text className="bold themeText" style={sectionCss.headingText}>
|
||||||
{ContainerCopyMessages.errorTitle}
|
{ContainerCopyMessages.errorTitle}
|
||||||
</Text>
|
</Text>
|
||||||
<Text as="pre" style={{ whiteSpace: "pre-wrap" }}>
|
<Text as="pre" style={errorMessageStyle}>
|
||||||
{job.Error.message}
|
{job.Error.message}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
@@ -88,16 +102,16 @@ const CopyJobDetails: React.FC<CopyJobDetailsProps> = ({ job }) => {
|
|||||||
<Stack.Item data-testid="selectedcollection-stack">
|
<Stack.Item data-testid="selectedcollection-stack">
|
||||||
<Stack tokens={{ childrenGap: 15 }}>
|
<Stack tokens={{ childrenGap: 15 }}>
|
||||||
<Stack.Item style={sectionCss.verticalAlign}>
|
<Stack.Item style={sectionCss.verticalAlign}>
|
||||||
<Text className="bold">{ContainerCopyMessages.MonitorJobs.Columns.lastUpdatedTime}</Text>
|
<Text className="bold themeText">{ContainerCopyMessages.MonitorJobs.Columns.lastUpdatedTime}</Text>
|
||||||
<Text>{job.LastUpdatedTime}</Text>
|
<Text className="themeText">{job.LastUpdatedTime}</Text>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<Stack.Item style={sectionCss.verticalAlign}>
|
<Stack.Item style={sectionCss.verticalAlign}>
|
||||||
<Text className="bold">{ContainerCopyMessages.sourceAccountLabel}</Text>
|
<Text className="bold themeText">{ContainerCopyMessages.sourceAccountLabel}</Text>
|
||||||
<Text>{job.Source?.remoteAccountName}</Text>
|
<Text className="themeText">{job.Source?.remoteAccountName}</Text>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<Stack.Item style={sectionCss.verticalAlign}>
|
<Stack.Item style={sectionCss.verticalAlign}>
|
||||||
<Text className="bold">{ContainerCopyMessages.MonitorJobs.Columns.mode}</Text>
|
<Text className="bold themeText">{ContainerCopyMessages.MonitorJobs.Columns.mode}</Text>
|
||||||
<Text>{job.Mode}</Text>
|
<Text className="themeText">{job.Mode}</Text>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ describe("CopyJobStatusWithIcon", () => {
|
|||||||
|
|
||||||
const spinner = container.querySelector('[class*="ms-Spinner"]');
|
const spinner = container.querySelector('[class*="ms-Spinner"]');
|
||||||
expect(spinner).toBeInTheDocument();
|
expect(spinner).toBeInTheDocument();
|
||||||
expect(container).toHaveTextContent("In Progress");
|
expect(container).toHaveTextContent("Running");
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(container.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -83,18 +83,18 @@ describe("CopyJobStatusWithIcon", () => {
|
|||||||
it("provides meaningful text content for screen readers", () => {
|
it("provides meaningful text content for screen readers", () => {
|
||||||
const { container } = render(<CopyJobStatusWithIcon status={CopyJobStatusType.InProgress} />);
|
const { container } = render(<CopyJobStatusWithIcon status={CopyJobStatusType.InProgress} />);
|
||||||
|
|
||||||
expect(container).toHaveTextContent("In Progress");
|
expect(container).toHaveTextContent("Running");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Icon and Status Mapping", () => {
|
describe("Icon and Status Mapping", () => {
|
||||||
it("renders correct status text based on mapping", () => {
|
it("renders correct status text based on mapping", () => {
|
||||||
const statusMappings = [
|
const statusMappings = [
|
||||||
{ status: CopyJobStatusType.Pending, expectedText: "Pending" },
|
{ status: CopyJobStatusType.Pending, expectedText: "Queued" },
|
||||||
{ status: CopyJobStatusType.Paused, expectedText: "Paused" },
|
{ status: CopyJobStatusType.Paused, expectedText: "Paused" },
|
||||||
{ status: CopyJobStatusType.Failed, expectedText: "Failed" },
|
{ status: CopyJobStatusType.Failed, expectedText: "Failed" },
|
||||||
{ status: CopyJobStatusType.Completed, expectedText: "Completed" },
|
{ status: CopyJobStatusType.Completed, expectedText: "Completed" },
|
||||||
{ status: CopyJobStatusType.Running, expectedText: "In Progress" },
|
{ status: CopyJobStatusType.Running, expectedText: "Running" },
|
||||||
];
|
];
|
||||||
|
|
||||||
statusMappings.forEach(({ status, expectedText }) => {
|
statusMappings.forEach(({ status, expectedText }) => {
|
||||||
|
|||||||
@@ -1,30 +1,14 @@
|
|||||||
import { FontIcon, getTheme, mergeStyles, mergeStyleSets, Spinner, SpinnerSize, Stack, Text } from "@fluentui/react";
|
import { FontIcon, mergeStyles, Spinner, SpinnerSize, Stack, Text } from "@fluentui/react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import ContainerCopyMessages from "../../ContainerCopyMessages";
|
import ContainerCopyMessages from "../../ContainerCopyMessages";
|
||||||
import { CopyJobStatusType } from "../../Enums/CopyJobEnums";
|
import { CopyJobStatusType } from "../../Enums/CopyJobEnums";
|
||||||
|
|
||||||
const theme = getTheme();
|
|
||||||
|
|
||||||
const iconClass = mergeStyles({
|
const iconClass = mergeStyles({
|
||||||
fontSize: "16px",
|
fontSize: "16px",
|
||||||
marginRight: "8px",
|
marginRight: "8px",
|
||||||
});
|
});
|
||||||
|
|
||||||
const classNames = mergeStyleSets({
|
|
||||||
[CopyJobStatusType.Pending]: [{ color: theme.semanticColors.bodySubtext }, iconClass],
|
|
||||||
[CopyJobStatusType.InProgress]: [{ color: theme.palette.themePrimary }, iconClass],
|
|
||||||
[CopyJobStatusType.Running]: [{ color: theme.palette.themePrimary }, iconClass],
|
|
||||||
[CopyJobStatusType.Partitioning]: [{ color: theme.palette.themePrimary }, iconClass],
|
|
||||||
[CopyJobStatusType.Paused]: [{ color: theme.palette.themePrimary }, iconClass],
|
|
||||||
[CopyJobStatusType.Skipped]: [{ color: theme.semanticColors.bodySubtext }, iconClass],
|
|
||||||
[CopyJobStatusType.Cancelled]: [{ color: theme.semanticColors.bodySubtext }, iconClass],
|
|
||||||
[CopyJobStatusType.Failed]: [{ color: theme.semanticColors.errorIcon }, iconClass],
|
|
||||||
[CopyJobStatusType.Faulted]: [{ color: theme.semanticColors.errorIcon }, iconClass],
|
|
||||||
[CopyJobStatusType.Completed]: [{ color: theme.semanticColors.successIcon }, iconClass],
|
|
||||||
unknown: [{ color: theme.semanticColors.bodySubtext }, iconClass],
|
|
||||||
});
|
|
||||||
|
|
||||||
const iconMap: Partial<Record<CopyJobStatusType, string>> = {
|
const iconMap: Partial<Record<CopyJobStatusType, string>> = {
|
||||||
[CopyJobStatusType.Pending]: "Clock",
|
[CopyJobStatusType.Pending]: "Clock",
|
||||||
[CopyJobStatusType.Paused]: "CirclePause",
|
[CopyJobStatusType.Paused]: "CirclePause",
|
||||||
@@ -35,6 +19,17 @@ const iconMap: Partial<Record<CopyJobStatusType, string>> = {
|
|||||||
[CopyJobStatusType.Completed]: "CompletedSolid",
|
[CopyJobStatusType.Completed]: "CompletedSolid",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Icon colors for different statuses
|
||||||
|
const statusIconColors: Partial<Record<CopyJobStatusType, string>> = {
|
||||||
|
[CopyJobStatusType.Failed]: "var(--colorPaletteRedForeground1)",
|
||||||
|
[CopyJobStatusType.Faulted]: "var(--colorPaletteRedForeground1)",
|
||||||
|
[CopyJobStatusType.Completed]: "var(--colorSuccessGreen)",
|
||||||
|
[CopyJobStatusType.InProgress]: "var(--colorBrandForeground1)",
|
||||||
|
[CopyJobStatusType.Running]: "var(--colorBrandForeground1)",
|
||||||
|
[CopyJobStatusType.Partitioning]: "var(--colorBrandForeground1)",
|
||||||
|
[CopyJobStatusType.Paused]: "var(--colorBrandForeground1)",
|
||||||
|
};
|
||||||
|
|
||||||
export interface CopyJobStatusWithIconProps {
|
export interface CopyJobStatusWithIconProps {
|
||||||
status: CopyJobStatusType;
|
status: CopyJobStatusType;
|
||||||
}
|
}
|
||||||
@@ -47,19 +42,17 @@ const CopyJobStatusWithIcon: React.FC<CopyJobStatusWithIconProps> = React.memo((
|
|||||||
CopyJobStatusType.InProgress,
|
CopyJobStatusType.InProgress,
|
||||||
CopyJobStatusType.Partitioning,
|
CopyJobStatusType.Partitioning,
|
||||||
].includes(status);
|
].includes(status);
|
||||||
|
const iconColor = statusIconColors[status] || "var(--colorNeutralForeground2)";
|
||||||
|
const iconStyle = mergeStyles(iconClass, { color: iconColor });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack horizontal verticalAlign="center">
|
<Stack horizontal verticalAlign="center">
|
||||||
{isSpinnerStatus ? (
|
{isSpinnerStatus ? (
|
||||||
<Spinner size={SpinnerSize.small} style={{ marginRight: "8px" }} />
|
<Spinner size={SpinnerSize.small} style={{ marginRight: "8px" }} />
|
||||||
) : (
|
) : (
|
||||||
<FontIcon
|
<FontIcon aria-label={status} iconName={iconMap[status] || "UnknownSolid"} className={iconStyle} />
|
||||||
aria-label={status}
|
|
||||||
iconName={iconMap[status] || "UnknownSolid"}
|
|
||||||
className={classNames[status] || classNames.unknown}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<Text>{statusText}</Text>
|
<Text className="themeText">{statusText}</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import {
|
|||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import Pager from "../../../../Common/Pager";
|
import Pager from "../../../../Common/Pager";
|
||||||
|
import { useThemeStore } from "../../../../hooks/useTheme";
|
||||||
|
import { getThemeTokens } from "../../../Theme/ThemeUtil";
|
||||||
import { openCopyJobDetailsPanel } from "../../Actions/CopyJobActions";
|
import { openCopyJobDetailsPanel } from "../../Actions/CopyJobActions";
|
||||||
import { CopyJobType, HandleJobActionClickType } from "../../Types/CopyJobTypes";
|
import { CopyJobType, HandleJobActionClickType } from "../../Types/CopyJobTypes";
|
||||||
import { getColumns } from "./CopyJobColumns";
|
import { getColumns } from "./CopyJobColumns";
|
||||||
@@ -26,13 +28,15 @@ interface CopyJobsListProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
container: { height: "calc(100vh - 25em)" } as React.CSSProperties,
|
container: { height: "100%" } as React.CSSProperties,
|
||||||
stackItem: { position: "relative", marginBottom: "20px" } as React.CSSProperties,
|
stackItem: { position: "relative", marginBottom: "20px" } as React.CSSProperties,
|
||||||
};
|
};
|
||||||
|
|
||||||
const PAGE_SIZE = 10;
|
const PAGE_SIZE = 10;
|
||||||
|
|
||||||
const CopyJobsList: React.FC<CopyJobsListProps> = ({ jobs, handleActionClick, pageSize = PAGE_SIZE }) => {
|
const CopyJobsList: React.FC<CopyJobsListProps> = ({ jobs, handleActionClick, pageSize = PAGE_SIZE }) => {
|
||||||
|
const isDarkMode = useThemeStore((state) => state.isDarkMode);
|
||||||
|
const themeTokens = getThemeTokens(isDarkMode);
|
||||||
const [startIndex, setStartIndex] = React.useState(0);
|
const [startIndex, setStartIndex] = React.useState(0);
|
||||||
const [sortedJobs, setSortedJobs] = React.useState<CopyJobType[]>(jobs);
|
const [sortedJobs, setSortedJobs] = React.useState<CopyJobType[]>(jobs);
|
||||||
const [sortedColumnKey, setSortedColumnKey] = React.useState<string | undefined>(undefined);
|
const [sortedColumnKey, setSortedColumnKey] = React.useState<string | undefined>(undefined);
|
||||||
@@ -80,6 +84,7 @@ const CopyJobsList: React.FC<CopyJobsListProps> = ({ jobs, handleActionClick, pa
|
|||||||
<Stack.Item verticalFill={true} grow={1} shrink={1} style={styles.stackItem}>
|
<Stack.Item verticalFill={true} grow={1} shrink={1} style={styles.stackItem}>
|
||||||
<ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto}>
|
<ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto}>
|
||||||
<ShimmeredDetailsList
|
<ShimmeredDetailsList
|
||||||
|
className="CopyJobListContainer"
|
||||||
onRenderRow={_onRenderRow}
|
onRenderRow={_onRenderRow}
|
||||||
checkboxVisibility={2}
|
checkboxVisibility={2}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
@@ -87,11 +92,28 @@ const CopyJobsList: React.FC<CopyJobsListProps> = ({ jobs, handleActionClick, pa
|
|||||||
enableShimmer={false}
|
enableShimmer={false}
|
||||||
constrainMode={ConstrainMode.unconstrained}
|
constrainMode={ConstrainMode.unconstrained}
|
||||||
layoutMode={DetailsListLayoutMode.justified}
|
layoutMode={DetailsListLayoutMode.justified}
|
||||||
onRenderDetailsHeader={(props, defaultRender) => (
|
onRenderDetailsHeader={(props, defaultRender) => {
|
||||||
<Sticky stickyPosition={StickyPositionType.Header} isScrollSynced>
|
const bgColor = themeTokens.colorNeutralBackground3;
|
||||||
{defaultRender({ ...props })}
|
const textColor = themeTokens.colorNeutralForeground1;
|
||||||
|
return (
|
||||||
|
<Sticky stickyPosition={StickyPositionType.Header} isScrollSynced stickyBackgroundColor={bgColor}>
|
||||||
|
<div style={{ backgroundColor: bgColor }}>
|
||||||
|
{defaultRender({
|
||||||
|
...props,
|
||||||
|
styles: {
|
||||||
|
root: {
|
||||||
|
backgroundColor: bgColor,
|
||||||
|
selectors: {
|
||||||
|
".ms-DetailsHeader-cellTitle": { color: textColor },
|
||||||
|
".ms-DetailsHeader-cellName": { color: textColor },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</Sticky>
|
</Sticky>
|
||||||
)}
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</ScrollablePane>
|
</ScrollablePane>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ exports[`CopyJobStatusWithIcon Spinner Status Types renders InProgress with spin
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
class="css-112"
|
class="themeText css-112"
|
||||||
>
|
>
|
||||||
In Progress
|
Running
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -33,9 +33,9 @@ exports[`CopyJobStatusWithIcon Spinner Status Types renders Partitioning with sp
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
class="css-112"
|
class="themeText css-112"
|
||||||
>
|
>
|
||||||
In Progress
|
Running
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -53,9 +53,9 @@ exports[`CopyJobStatusWithIcon Spinner Status Types renders Running with spinner
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
class="css-112"
|
class="themeText css-112"
|
||||||
>
|
>
|
||||||
In Progress
|
Running
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -66,7 +66,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
|
|||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
aria-label="Cancelled"
|
aria-label="Cancelled"
|
||||||
class="ms-Icon root-105 css-118 mocked-style-Cancelled"
|
class="ms-Icon root-105 css-118 mocked-styles"
|
||||||
data-icon-name="StatusErrorFull"
|
data-icon-name="StatusErrorFull"
|
||||||
role="img"
|
role="img"
|
||||||
style="font-family: "FabricMDL2Icons-4";"
|
style="font-family: "FabricMDL2Icons-4";"
|
||||||
@@ -74,7 +74,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
|
|||||||
|
|
||||||
</i>
|
</i>
|
||||||
<span
|
<span
|
||||||
class="css-112"
|
class="themeText css-112"
|
||||||
>
|
>
|
||||||
Cancelled
|
Cancelled
|
||||||
</span>
|
</span>
|
||||||
@@ -87,7 +87,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
|
|||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
aria-label="Completed"
|
aria-label="Completed"
|
||||||
class="ms-Icon root-105 css-120 mocked-style-Completed"
|
class="ms-Icon root-105 css-120 mocked-styles"
|
||||||
data-icon-name="CompletedSolid"
|
data-icon-name="CompletedSolid"
|
||||||
role="img"
|
role="img"
|
||||||
style="font-family: "FabricMDL2Icons-5";"
|
style="font-family: "FabricMDL2Icons-5";"
|
||||||
@@ -95,7 +95,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
|
|||||||
|
|
||||||
</i>
|
</i>
|
||||||
<span
|
<span
|
||||||
class="css-112"
|
class="themeText css-112"
|
||||||
>
|
>
|
||||||
Completed
|
Completed
|
||||||
</span>
|
</span>
|
||||||
@@ -108,7 +108,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
|
|||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
aria-label="Failed"
|
aria-label="Failed"
|
||||||
class="ms-Icon root-105 css-118 mocked-style-Failed"
|
class="ms-Icon root-105 css-118 mocked-styles"
|
||||||
data-icon-name="StatusErrorFull"
|
data-icon-name="StatusErrorFull"
|
||||||
role="img"
|
role="img"
|
||||||
style="font-family: "FabricMDL2Icons-4";"
|
style="font-family: "FabricMDL2Icons-4";"
|
||||||
@@ -116,7 +116,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
|
|||||||
|
|
||||||
</i>
|
</i>
|
||||||
<span
|
<span
|
||||||
class="css-112"
|
class="themeText css-112"
|
||||||
>
|
>
|
||||||
Failed
|
Failed
|
||||||
</span>
|
</span>
|
||||||
@@ -129,7 +129,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
|
|||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
aria-label="Faulted"
|
aria-label="Faulted"
|
||||||
class="ms-Icon root-105 css-118 mocked-style-Faulted"
|
class="ms-Icon root-105 css-118 mocked-styles"
|
||||||
data-icon-name="StatusErrorFull"
|
data-icon-name="StatusErrorFull"
|
||||||
role="img"
|
role="img"
|
||||||
style="font-family: "FabricMDL2Icons-4";"
|
style="font-family: "FabricMDL2Icons-4";"
|
||||||
@@ -137,7 +137,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
|
|||||||
|
|
||||||
</i>
|
</i>
|
||||||
<span
|
<span
|
||||||
class="css-112"
|
class="themeText css-112"
|
||||||
>
|
>
|
||||||
Failed
|
Failed
|
||||||
</span>
|
</span>
|
||||||
@@ -150,7 +150,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
|
|||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
aria-label="Paused"
|
aria-label="Paused"
|
||||||
class="ms-Icon root-105 css-114 mocked-style-Paused"
|
class="ms-Icon root-105 css-114 mocked-styles"
|
||||||
data-icon-name="CirclePause"
|
data-icon-name="CirclePause"
|
||||||
role="img"
|
role="img"
|
||||||
style="font-family: "FabricMDL2Icons-11";"
|
style="font-family: "FabricMDL2Icons-11";"
|
||||||
@@ -158,7 +158,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
|
|||||||
|
|
||||||
</i>
|
</i>
|
||||||
<span
|
<span
|
||||||
class="css-112"
|
class="themeText css-112"
|
||||||
>
|
>
|
||||||
Paused
|
Paused
|
||||||
</span>
|
</span>
|
||||||
@@ -171,7 +171,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
|
|||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
aria-label="Pending"
|
aria-label="Pending"
|
||||||
class="ms-Icon root-105 css-111 mocked-style-Pending"
|
class="ms-Icon root-105 css-111 mocked-styles"
|
||||||
data-icon-name="Clock"
|
data-icon-name="Clock"
|
||||||
role="img"
|
role="img"
|
||||||
style="font-family: "FabricMDL2Icons-2";"
|
style="font-family: "FabricMDL2Icons-2";"
|
||||||
@@ -179,9 +179,9 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
|
|||||||
|
|
||||||
</i>
|
</i>
|
||||||
<span
|
<span
|
||||||
class="css-112"
|
class="themeText css-112"
|
||||||
>
|
>
|
||||||
Pending
|
Queued
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -192,7 +192,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
|
|||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
aria-label="Skipped"
|
aria-label="Skipped"
|
||||||
class="ms-Icon root-105 css-116 mocked-style-Skipped"
|
class="ms-Icon root-105 css-116 mocked-styles"
|
||||||
data-icon-name="StatusCircleBlock2"
|
data-icon-name="StatusCircleBlock2"
|
||||||
role="img"
|
role="img"
|
||||||
style="font-family: "FabricMDL2Icons-9";"
|
style="font-family: "FabricMDL2Icons-9";"
|
||||||
@@ -200,7 +200,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
|
|||||||
|
|
||||||
</i>
|
</i>
|
||||||
<span
|
<span
|
||||||
class="css-112"
|
class="themeText css-112"
|
||||||
>
|
>
|
||||||
Cancelled
|
Cancelled
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import CopyJobsNotFound from "../MonitorCopyJobs/Components/CopyJobs.NotFound";
|
|||||||
import { CopyJobType, JobActionUpdatorType } from "../Types/CopyJobTypes";
|
import { CopyJobType, JobActionUpdatorType } from "../Types/CopyJobTypes";
|
||||||
import CopyJobsList from "./Components/CopyJobsList";
|
import CopyJobsList from "./Components/CopyJobsList";
|
||||||
|
|
||||||
const FETCH_INTERVAL_MS = 30 * 1000;
|
const FETCH_INTERVAL = 2 * 60 * 1000;
|
||||||
const SHIMMER_INDENT_LEVELS: IndentLevel[] = Array(7).fill({ level: 0, width: "100%" });
|
const SHIMMER_INDENT_LEVELS: IndentLevel[] = Array(7).fill({ level: 0, width: "100%" });
|
||||||
|
|
||||||
interface MonitorCopyJobsProps {
|
interface MonitorCopyJobsProps {
|
||||||
@@ -44,7 +44,9 @@ const MonitorCopyJobs = forwardRef<MonitorCopyJobsRef, MonitorCopyJobsProps>(({
|
|||||||
return isEqual(prevJobs, normalizedResponse) ? prevJobs : normalizedResponse;
|
return isEqual(prevJobs, normalizedResponse) ? prevJobs : normalizedResponse;
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error.message !== "Previous copy job request was cancelled.") {
|
||||||
setError(error.message || "Failed to load copy jobs. Please try again later.");
|
setError(error.message || "Failed to load copy jobs. Please try again later.");
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (isFirstFetchRef.current) {
|
if (isFirstFetchRef.current) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -55,7 +57,7 @@ const MonitorCopyJobs = forwardRef<MonitorCopyJobsRef, MonitorCopyJobsProps>(({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchJobs();
|
fetchJobs();
|
||||||
const intervalId = setInterval(fetchJobs, FETCH_INTERVAL_MS);
|
const intervalId = setInterval(fetchJobs, FETCH_INTERVAL);
|
||||||
|
|
||||||
return () => clearInterval(intervalId);
|
return () => clearInterval(intervalId);
|
||||||
}, [fetchJobs]);
|
}, [fetchJobs]);
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export interface DatabaseContainerSectionProps {
|
|||||||
containerDisabled?: boolean;
|
containerDisabled?: boolean;
|
||||||
containerOnChange: (ev: React.FormEvent<HTMLDivElement>, option: DropdownOptionType) => void;
|
containerOnChange: (ev: React.FormEvent<HTMLDivElement>, option: DropdownOptionType) => void;
|
||||||
handleOnDemandCreateContainer?: () => void;
|
handleOnDemandCreateContainer?: () => void;
|
||||||
|
sectionType: "source" | "target";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CopyJobContextState {
|
export interface CopyJobContextState {
|
||||||
@@ -56,14 +57,14 @@ export interface CopyJobContextState {
|
|||||||
migrationType: CopyJobMigrationType;
|
migrationType: CopyJobMigrationType;
|
||||||
sourceReadAccessFromTarget?: boolean;
|
sourceReadAccessFromTarget?: boolean;
|
||||||
source: {
|
source: {
|
||||||
subscription: Subscription;
|
subscription: Subscription | null;
|
||||||
account: DatabaseAccount;
|
account: DatabaseAccount | null;
|
||||||
databaseId: string;
|
databaseId: string;
|
||||||
containerId: string;
|
containerId: string;
|
||||||
};
|
};
|
||||||
target: {
|
target: {
|
||||||
subscriptionId: string;
|
subscriptionId: string;
|
||||||
account: DatabaseAccount;
|
account: DatabaseAccount | null;
|
||||||
databaseId: string;
|
databaseId: string;
|
||||||
containerId: string;
|
containerId: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,30 @@
|
|||||||
@import "../../../less/Common/Constants.less";
|
@import "../../../less/Common/Constants.less";
|
||||||
|
|
||||||
|
// Common theme-aware classes
|
||||||
|
.themeText {
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.themeTextSecondary {
|
||||||
|
color: var(--colorNeutralForeground2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.themeLinkText {
|
||||||
|
color: var(--colorBrandForeground1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.themeBackground {
|
||||||
|
background-color: var(--colorNeutralBackground1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.themeBackgroundSecondary {
|
||||||
|
background-color: var(--colorNeutralBackground2);
|
||||||
|
}
|
||||||
|
|
||||||
#containerCopyWrapper {
|
#containerCopyWrapper {
|
||||||
|
background-color: var(--colorNeutralBackground1);
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
|
|
||||||
.centerContent {
|
.centerContent {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -9,21 +33,31 @@
|
|||||||
.noCopyJobsMessage {
|
.noCopyJobsMessage {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
color: @FocusColor;
|
color: var(--colorNeutralForeground2);
|
||||||
}
|
}
|
||||||
button.createCopyJobButton {
|
button.createCopyJobButton {
|
||||||
color: @LinkColor;
|
color: var(--colorBrandForeground1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.createCopyJobScreensContainer {
|
.createCopyJobScreensContainer {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 1em 1.5em;
|
padding: 1em 1.5em;
|
||||||
|
background-color: var(--colorNeutralBackground1);
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
|
|
||||||
.pointInTimeRestoreContainer, .onlineCopyContainer {
|
.pointInTimeRestoreContainer, .onlineCopyContainer {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toggle-label {
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordionHeaderText {
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
@@ -71,7 +105,7 @@
|
|||||||
}
|
}
|
||||||
.foreground {
|
.foreground {
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
background-color: #f9f9f9;
|
background-color: var(--colorNeutralBackground2);
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||||
transform: translate(0%, -9%);
|
transform: translate(0%, -9%);
|
||||||
@@ -80,14 +114,40 @@
|
|||||||
.createCopyJobErrorMessageBar {
|
.createCopyJobErrorMessageBar {
|
||||||
margin-bottom: 2em;
|
margin-bottom: 2em;
|
||||||
}
|
}
|
||||||
|
body.isDarkMode & {
|
||||||
|
.ms-TooltipHost .ms-Image {
|
||||||
|
filter: invert(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-TextField {
|
||||||
|
.ms-TextField-fieldGroup {
|
||||||
|
background-color: var(--colorNeutralBackground1);
|
||||||
|
border-color: var(--colorNeutralStroke1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-TextField-field {
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
|
background-color: var(--colorNeutralBackground1);
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: var(--colorNeutralForeground4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-Label {
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.create-container-link-btn {
|
.create-container-link-btn {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
color: @LinkColor;
|
color: var(--colorBrandForeground1);
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create collection panel */
|
/* Create collection panel */
|
||||||
@@ -105,7 +165,6 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|
||||||
.ms-DetailsList {
|
.ms-DetailsList {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
@@ -114,33 +173,33 @@
|
|||||||
padding: @DefaultSpace 20px;
|
padding: @DefaultSpace 20px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: @DefaultFontSize;
|
font-size: @DefaultFontSize;
|
||||||
color: @BaseHigh;
|
color: var(--colorNeutralForeground1);
|
||||||
background-color: @BaseLow;
|
background-color: var(--colorNeutralBackground2);
|
||||||
border-bottom: @ButtonBorderWidth solid @BaseMedium;
|
border-bottom: @ButtonBorderWidth solid var(--colorNeutralStroke1);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: @BaseMediumLow;
|
background-color: var(--colorNeutralBackground3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ms-DetailsRow {
|
.ms-DetailsRow {
|
||||||
border-bottom: @ButtonBorderWidth solid @BaseMedium;
|
border-bottom: @ButtonBorderWidth solid var(--colorNeutralStroke1);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: @BaseMediumLow;
|
background-color: var(--colorNeutralBackground2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ms-DetailsRow-cell {
|
.ms-DetailsRow-cell {
|
||||||
padding: @MediumSpace 20px;
|
padding: @MediumSpace 20px;
|
||||||
font-size: @DefaultFontSize;
|
font-size: @DefaultFontSize;
|
||||||
color: @BaseHigh;
|
color: var(--colorNeutralForeground1);
|
||||||
min-height: 48px;
|
min-height: 48px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.jobNameLink {
|
.jobNameLink {
|
||||||
color: @LinkColor;
|
color: var(--colorBrandForeground1);
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -168,7 +227,7 @@
|
|||||||
}
|
}
|
||||||
.ms-DetailsRow-cell {
|
.ms-DetailsRow-cell {
|
||||||
font-size: @DefaultFontSize;
|
font-size: @DefaultFontSize;
|
||||||
color: @BaseHigh;
|
color: var(--colorNeutralForeground1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
|
|||||||
aria-expanded={this.state.isExpanded}
|
aria-expanded={this.state.isExpanded}
|
||||||
>
|
>
|
||||||
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} />
|
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} />
|
||||||
<Label>{this.props.title}</Label>
|
<Label styles={{ root: { color: "var(--colorNeutralForeground1)" } }}>{this.props.title}</Label>
|
||||||
{this.props.tooltipContent && (
|
{this.props.tooltipContent && (
|
||||||
<TooltipHost
|
<TooltipHost
|
||||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||||
@@ -79,6 +79,14 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
|
|||||||
id={`delete-${this.props.title.split(" ").join("-")}`}
|
id={`delete-${this.props.title.split(" ").join("-")}`}
|
||||||
iconProps={{ iconName: "Delete" }}
|
iconProps={{ iconName: "Delete" }}
|
||||||
style={{ height: 27, marginRight: "20px" }}
|
style={{ height: 27, marginRight: "20px" }}
|
||||||
|
styles={{
|
||||||
|
rootHovered: {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
rootPressed: {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
}}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
this.props.onDelete();
|
this.props.onDelete();
|
||||||
|
|||||||
@@ -20,7 +20,15 @@ exports[`CollapsibleSectionComponent renders 1`] = `
|
|||||||
<Icon
|
<Icon
|
||||||
iconName="ChevronDown"
|
iconName="ChevronDown"
|
||||||
/>
|
/>
|
||||||
<StyledLabelBase>
|
<StyledLabelBase
|
||||||
|
styles={
|
||||||
|
{
|
||||||
|
"root": {
|
||||||
|
"color": "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
Sample title
|
Sample title
|
||||||
</StyledLabelBase>
|
</StyledLabelBase>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -58,6 +58,26 @@ export interface CommandButtonComponentProps {
|
|||||||
*/
|
*/
|
||||||
tooltipText?: string;
|
tooltipText?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom styles to apply to the button using Fluent UI theme tokens
|
||||||
|
*/
|
||||||
|
styles?: {
|
||||||
|
root?: {
|
||||||
|
backgroundColor?: string;
|
||||||
|
color?: string;
|
||||||
|
selectors?: {
|
||||||
|
":hover"?: {
|
||||||
|
backgroundColor?: string;
|
||||||
|
color?: string;
|
||||||
|
};
|
||||||
|
":active"?: {
|
||||||
|
backgroundColor?: string;
|
||||||
|
color?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tabindex for the command button
|
* tabindex for the command button
|
||||||
*/
|
*/
|
||||||
@@ -250,6 +270,8 @@ export class CommandButtonComponent extends React.Component<CommandButtonCompone
|
|||||||
contentClassName += " hasHiddenItems";
|
contentClassName += " hasHiddenItems";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const style = this.props.styles?.root || {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="commandButtonReact">
|
<div className="commandButtonReact">
|
||||||
<span
|
<span
|
||||||
@@ -262,6 +284,7 @@ export class CommandButtonComponent extends React.Component<CommandButtonCompone
|
|||||||
aria-disabled={this.props.disabled}
|
aria-disabled={this.props.disabled}
|
||||||
aria-haspopup={this.props.hasPopup}
|
aria-haspopup={this.props.hasPopup}
|
||||||
aria-label={this.props.ariaLabel}
|
aria-label={this.props.ariaLabel}
|
||||||
|
style={style}
|
||||||
onClick={(e: React.MouseEvent<HTMLSpanElement>) => this.commandClickCallback(e)}
|
onClick={(e: React.MouseEvent<HTMLSpanElement>) => this.commandClickCallback(e)}
|
||||||
>
|
>
|
||||||
<div className={contentClassName}>
|
<div className={contentClassName}>
|
||||||
|
|||||||
@@ -179,8 +179,18 @@ export const Dialog: FC = () => {
|
|||||||
title,
|
title,
|
||||||
subText,
|
subText,
|
||||||
styles: {
|
styles: {
|
||||||
title: { fontSize: DIALOG_TITLE_FONT_SIZE, fontWeight: DIALOG_TITLE_FONT_WEIGHT },
|
title: {
|
||||||
subText: { fontSize: DIALOG_SUBTEXT_FONT_SIZE },
|
fontSize: DIALOG_TITLE_FONT_SIZE,
|
||||||
|
fontWeight: DIALOG_TITLE_FONT_WEIGHT,
|
||||||
|
},
|
||||||
|
subText: {
|
||||||
|
fontSize: DIALOG_SUBTEXT_FONT_SIZE,
|
||||||
|
color: "var(--colorNeutralForeground2)",
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
showCloseButton: showCloseButton || false,
|
showCloseButton: showCloseButton || false,
|
||||||
onDismiss,
|
onDismiss,
|
||||||
@@ -188,18 +198,60 @@ export const Dialog: FC = () => {
|
|||||||
modalProps: { isBlocking: isModal, isDarkOverlay: false },
|
modalProps: { isBlocking: isModal, isDarkOverlay: false },
|
||||||
minWidth: DIALOG_MIN_WIDTH,
|
minWidth: DIALOG_MIN_WIDTH,
|
||||||
maxWidth: DIALOG_MAX_WIDTH,
|
maxWidth: DIALOG_MAX_WIDTH,
|
||||||
|
styles: {
|
||||||
|
main: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1)",
|
||||||
|
selectors: {
|
||||||
|
".ms-Dialog-title": { color: "var(--colorNeutralForeground1)" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const primaryButtonProps: IButtonProps = {
|
const primaryButtonProps: IButtonProps = {
|
||||||
text: primaryButtonText,
|
text: primaryButtonText,
|
||||||
disabled: primaryButtonDisabled || false,
|
disabled: primaryButtonDisabled || false,
|
||||||
onClick: onPrimaryButtonClick,
|
onClick: onPrimaryButtonClick,
|
||||||
|
styles: {
|
||||||
|
root: {
|
||||||
|
backgroundColor: "var(--colorBrandBackground)",
|
||||||
|
color: "var(--colorNeutralForegroundOnBrand)",
|
||||||
|
selectors: {
|
||||||
|
":hover": {
|
||||||
|
backgroundColor: "var(--colorBrandBackgroundHover)",
|
||||||
|
color: "var(--colorNeutralForegroundOnBrand)",
|
||||||
|
},
|
||||||
|
":active": {
|
||||||
|
backgroundColor: "var(--colorBrandBackgroundPressed)",
|
||||||
|
color: "var(--colorNeutralForegroundOnBrand)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const secondaryButtonProps: IButtonProps =
|
const secondaryButtonProps: IButtonProps =
|
||||||
secondaryButtonText && onSecondaryButtonClick
|
secondaryButtonText && onSecondaryButtonClick
|
||||||
? {
|
? {
|
||||||
text: secondaryButtonText,
|
text: secondaryButtonText,
|
||||||
onClick: onSecondaryButtonClick,
|
onClick: onSecondaryButtonClick,
|
||||||
|
styles: {
|
||||||
|
root: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
borderColor: "var(--colorNeutralStroke1)",
|
||||||
|
selectors: {
|
||||||
|
":hover": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground3)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
":active": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground3)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
borderColor: "var(--colorCompoundBrandStroke1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
return visible ? (
|
return visible ? (
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Spinner, SpinnerSize } from "@fluentui/react";
|
import { Spinner, SpinnerSize } from "@fluentui/react";
|
||||||
|
import { monacoTheme, useThemeStore } from "hooks/useTheme";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { loadMonaco, monaco } from "../../LazyMonaco";
|
import { loadMonaco, monaco } from "../../LazyMonaco";
|
||||||
// import "./EditorReact.less";
|
// import "./EditorReact.less";
|
||||||
@@ -66,6 +67,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
|||||||
private rootNode: HTMLElement;
|
private rootNode: HTMLElement;
|
||||||
public editor: monaco.editor.IStandaloneCodeEditor;
|
public editor: monaco.editor.IStandaloneCodeEditor;
|
||||||
private selectionListener: monaco.IDisposable;
|
private selectionListener: monaco.IDisposable;
|
||||||
|
private themeUnsubscribe: () => void;
|
||||||
monacoApi: {
|
monacoApi: {
|
||||||
default: typeof monaco;
|
default: typeof monaco;
|
||||||
Emitter: typeof monaco.Emitter;
|
Emitter: typeof monaco.Emitter;
|
||||||
@@ -94,6 +96,13 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
|||||||
public componentDidMount(): void {
|
public componentDidMount(): void {
|
||||||
this.createEditor(this.configureEditor.bind(this));
|
this.createEditor(this.configureEditor.bind(this));
|
||||||
|
|
||||||
|
this.themeUnsubscribe = useThemeStore.subscribe((state) => {
|
||||||
|
if (this.editor) {
|
||||||
|
const newTheme = state.isDarkMode ? "vs-dark" : "vs";
|
||||||
|
this.monacoApi?.editor.setTheme(newTheme);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const suggestionWidget = this.editor?.getDomNode()?.querySelector(".suggest-widget") as HTMLElement;
|
const suggestionWidget = this.editor?.getDomNode()?.querySelector(".suggest-widget") as HTMLElement;
|
||||||
if (suggestionWidget) {
|
if (suggestionWidget) {
|
||||||
@@ -128,6 +137,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
|||||||
|
|
||||||
public componentWillUnmount(): void {
|
public componentWillUnmount(): void {
|
||||||
this.selectionListener && this.selectionListener.dispose();
|
this.selectionListener && this.selectionListener.dispose();
|
||||||
|
this.themeUnsubscribe && this.themeUnsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
@@ -211,7 +221,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
|||||||
ariaLabel: this.props.ariaLabel,
|
ariaLabel: this.props.ariaLabel,
|
||||||
fontSize: this.props.fontSize || 12,
|
fontSize: this.props.fontSize || 12,
|
||||||
automaticLayout: true,
|
automaticLayout: true,
|
||||||
theme: this.props.theme,
|
theme: monacoTheme(),
|
||||||
wordWrap: this.props.wordWrap || "off",
|
wordWrap: this.props.wordWrap || "off",
|
||||||
lineNumbers: this.props.lineNumbers || "off",
|
lineNumbers: this.props.lineNumbers || "off",
|
||||||
lineNumbersMinChars: this.props.lineNumbersMinChars,
|
lineNumbersMinChars: this.props.lineNumbersMinChars,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
DefaultButton,
|
DefaultButton,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
IDropdownOption,
|
IDropdownOption,
|
||||||
|
IDropdownStyles,
|
||||||
IStyleFunctionOrObject,
|
IStyleFunctionOrObject,
|
||||||
ITextFieldStyleProps,
|
ITextFieldStyleProps,
|
||||||
ITextFieldStyles,
|
ITextFieldStyles,
|
||||||
@@ -35,31 +36,167 @@ export interface FullTextPolicyData {
|
|||||||
const labelStyles = {
|
const labelStyles = {
|
||||||
root: {
|
root: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const textFieldStyles: IStyleFunctionOrObject<ITextFieldStyleProps, ITextFieldStyles> = {
|
const textFieldStyles: IStyleFunctionOrObject<ITextFieldStyleProps, ITextFieldStyles> = {
|
||||||
fieldGroup: {
|
fieldGroup: {
|
||||||
height: 27,
|
height: 27,
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
borderColor: "var(--colorNeutralStroke1)",
|
||||||
},
|
},
|
||||||
field: {
|
field: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
padding: "0 8px",
|
padding: "0 8px",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
},
|
||||||
|
root: {
|
||||||
|
selectors: {
|
||||||
|
input: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"input:hover": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
borderColor: "var(--colorNeutralStroke1)",
|
||||||
|
},
|
||||||
|
"input:focus": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
borderColor: "var(--colorBrandBackground)",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const dropdownStyles = {
|
const dropdownStyles: Partial<IDropdownStyles> = {
|
||||||
title: {
|
root: {
|
||||||
height: 27,
|
width: "40%",
|
||||||
lineHeight: "24px",
|
marginTop: "10px",
|
||||||
fontSize: 12,
|
selectors: {
|
||||||
|
"&:hover .ms-Dropdown-title": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
borderColor: "var(--colorNeutralStroke1)",
|
||||||
|
},
|
||||||
|
"&:hover span.ms-Dropdown-title": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"&:focus .ms-Dropdown-title": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
},
|
||||||
|
"&:focus span.ms-Dropdown-title": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
},
|
},
|
||||||
dropdown: {
|
dropdown: {
|
||||||
height: 27,
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
lineHeight: "24px",
|
borderColor: "var(--colorNeutralStroke1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
borderColor: "var(--colorNeutralStroke1)",
|
||||||
|
selectors: {
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"&:focus": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"&:hover .ms-Dropdown-titleText": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"&:focus .ms-Dropdown-titleText": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"& .ms-Dropdown-titleText": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"&.ms-Dropdown-title--hasPlaceholder": {
|
||||||
|
color: "var(--colorNeutralForeground2)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errorMessage: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
caretDown: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
callout: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
border: "1px solid var(--colorNeutralStroke1)",
|
||||||
|
},
|
||||||
|
dropdownItems: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
},
|
},
|
||||||
dropdownItem: {
|
dropdownItem: {
|
||||||
fontSize: 12,
|
backgroundColor: "transparent",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
minHeight: "36px",
|
||||||
|
lineHeight: "36px",
|
||||||
|
selectors: {
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"&:hover .ms-Dropdown-optionText": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"&:focus": {
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"&:active": {
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.15)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"& .ms-Dropdown-optionText": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dropdownItemSelected: {
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.08)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
minHeight: "36px",
|
||||||
|
lineHeight: "36px",
|
||||||
|
selectors: {
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"&:hover .ms-Dropdown-optionText": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"&:focus": {
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"&:active": {
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.15)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"& .ms-Dropdown-optionText": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dropdownOptionText: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
dropdownItemHeader: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -226,7 +363,32 @@ export const FullTextPoliciesComponent: React.FunctionComponent<FullTextPolicies
|
|||||||
</Stack>
|
</Stack>
|
||||||
</CollapsibleSectionComponent>
|
</CollapsibleSectionComponent>
|
||||||
))}
|
))}
|
||||||
<DefaultButton id={`add-vector-policy`} styles={{ root: { maxWidth: 170, fontSize: 12 } }} onClick={onAdd}>
|
<DefaultButton
|
||||||
|
id={`add-vector-policy`}
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
maxWidth: 170,
|
||||||
|
fontSize: 12,
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
borderColor: "var(--colorNeutralStroke1)",
|
||||||
|
},
|
||||||
|
rootHovered: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
borderColor: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
rootPressed: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
borderColor: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
rootDisabled: {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onClick={onAdd}
|
||||||
|
>
|
||||||
Add full text path
|
Add full text path
|
||||||
</DefaultButton>
|
</DefaultButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
background-color: var(--colorNeutralBackground1);
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settingsV2ToolTip {
|
.settingsV2ToolTip {
|
||||||
@@ -23,6 +25,8 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-family: @DataExplorerFont;
|
font-family: @DataExplorerFont;
|
||||||
|
background-color: var(--colorNeutralBackground1);
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settingsV2Editor {
|
.settingsV2Editor {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "@fluentui/react";
|
import { IPivotItemProps, IPivotProps, Pivot, PivotItem, Stack } from "@fluentui/react";
|
||||||
import { sendMessage } from "Common/MessageHandler";
|
import { sendMessage } from "Common/MessageHandler";
|
||||||
import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
|
import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
|
||||||
import {
|
import {
|
||||||
@@ -1477,10 +1477,98 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
selectedKey: SettingsV2TabTypes[this.state.selectedTab],
|
selectedKey: SettingsV2TabTypes[this.state.selectedTab],
|
||||||
};
|
};
|
||||||
|
|
||||||
const pivotItems = tabs.map((tab) => {
|
const pivotStyles = {
|
||||||
|
root: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
selectors: {
|
||||||
|
"& .ms-Pivot-link": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"& .ms-Pivot-link.is-selected::before": {
|
||||||
|
backgroundColor: "var(--colorCompoundBrandBackground)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
selectors: {
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"&:active": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
'&[aria-selected="true"]': {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
selectors: {
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"&:active": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
itemContainer: {
|
||||||
|
// padding: '20px 24px',
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const contentStyles = {
|
||||||
|
root: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
// padding: '20px 24px'
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="settingsV2MainContainer"
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
position: "relative",
|
||||||
|
} as React.CSSProperties
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{this.shouldShowKeyspaceSharedThroughputMessage() && (
|
||||||
|
<div>This table shared throughput is configured at the keyspace</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="settingsV2TabsContainer"
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
position: "relative",
|
||||||
|
padding: "20px 24px",
|
||||||
|
} as React.CSSProperties
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Pivot {...pivotProps} styles={pivotStyles}>
|
||||||
|
{tabs.map((tab) => {
|
||||||
const pivotItemProps: IPivotItemProps = {
|
const pivotItemProps: IPivotItemProps = {
|
||||||
itemKey: SettingsV2TabTypes[tab.tab],
|
itemKey: SettingsV2TabTypes[tab.tab],
|
||||||
style: { marginTop: 20 },
|
style: {
|
||||||
|
marginTop: 20,
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
headerText: getTabTitle(tab.tab),
|
headerText: getTabTitle(tab.tab),
|
||||||
headerButtonProps: {
|
headerButtonProps: {
|
||||||
"data-test": `settings-tab-header/${SettingsV2TabTypes[tab.tab]}`,
|
"data-test": `settings-tab-header/${SettingsV2TabTypes[tab.tab]}`,
|
||||||
@@ -1489,19 +1577,11 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PivotItem key={pivotItemProps.itemKey} {...pivotItemProps}>
|
<PivotItem key={pivotItemProps.itemKey} {...pivotItemProps}>
|
||||||
{tab.content}
|
<Stack styles={contentStyles}>{tab.content}</Stack>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
);
|
);
|
||||||
});
|
})}
|
||||||
|
</Pivot>
|
||||||
return (
|
|
||||||
<div className="settingsV2MainContainer">
|
|
||||||
{this.shouldShowKeyspaceSharedThroughputMessage() && (
|
|
||||||
<div>This table shared throughput is configured at the keyspace</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="settingsV2TabsContainer">
|
|
||||||
<Pivot {...pivotProps}>{pivotItems}</Pivot>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export interface PriceBreakdown {
|
|||||||
|
|
||||||
export type editorType = "indexPolicy" | "computedProperties" | "dataMasking";
|
export type editorType = "indexPolicy" | "computedProperties" | "dataMasking";
|
||||||
|
|
||||||
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14, color: "windowtext" } };
|
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14, color: "var(--colorNeutralForeground1)" } };
|
||||||
|
|
||||||
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
||||||
label: {
|
label: {
|
||||||
@@ -119,15 +119,89 @@ export const addMongoIndexSubElementsTokens: IStackTokens = {
|
|||||||
|
|
||||||
export const mediumWidthStackStyles: IStackStyles = { root: { width: 600 } };
|
export const mediumWidthStackStyles: IStackStyles = { root: { width: 600 } };
|
||||||
|
|
||||||
export const shortWidthTextFieldStyles: Partial<ITextFieldStyles> = { root: { paddingLeft: 10, width: 210 } };
|
export const shortWidthTextFieldStyles: Partial<ITextFieldStyles> = {
|
||||||
|
root: { paddingLeft: 10, width: 210 },
|
||||||
|
fieldGroup: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
borderColor: "var(--colorNeutralStroke1)",
|
||||||
|
},
|
||||||
|
field: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const shortWidthDropDownStyles: Partial<IDropdownStyles> = { dropdown: { paddingleft: 10, width: 202 } };
|
export const shortWidthDropDownStyles: Partial<IDropdownStyles> = {
|
||||||
|
dropdown: { paddingLeft: 10, width: 202 },
|
||||||
|
title: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
borderColor: "var(--colorNeutralStroke1)",
|
||||||
|
},
|
||||||
|
caretDown: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
callout: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
border: "1px solid var(--colorNeutralStroke1)",
|
||||||
|
},
|
||||||
|
dropdownItems: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
},
|
||||||
|
dropdownItem: {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
selectors: {
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"&:focus": {
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dropdownItemSelected: {
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.08)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
selectors: {
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dropdownOptionText: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const transparentDetailsRowStyles: Partial<IDetailsRowStyles> = {
|
export const transparentDetailsRowStyles: Partial<IDetailsRowStyles> = {
|
||||||
root: {
|
root: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
selectors: {
|
selectors: {
|
||||||
":hover": {
|
":hover": {
|
||||||
background: "transparent",
|
backgroundColor: "var(--colorNeutralBackground1Hover)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
":hover .ms-DetailsRow-cell": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1Hover)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"&.ms-DetailsRow": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cell: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
selectors: {
|
||||||
|
":hover": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1Hover)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -135,9 +209,11 @@ export const transparentDetailsRowStyles: Partial<IDetailsRowStyles> = {
|
|||||||
|
|
||||||
export const transparentDetailsHeaderStyle: Partial<IDetailsColumnStyles> = {
|
export const transparentDetailsHeaderStyle: Partial<IDetailsColumnStyles> = {
|
||||||
root: {
|
root: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
selectors: {
|
selectors: {
|
||||||
":hover": {
|
":hover": {
|
||||||
background: "transparent",
|
background: "var(--colorNeutralBackground1Hover)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -149,6 +225,35 @@ export const customDetailsListStyles: Partial<IDetailsListStyles> = {
|
|||||||
".ms-FocusZone": {
|
".ms-FocusZone": {
|
||||||
paddingTop: 0,
|
paddingTop: 0,
|
||||||
},
|
},
|
||||||
|
".ms-DetailsHeader": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1)",
|
||||||
|
},
|
||||||
|
".ms-DetailsHeader-cell": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1)",
|
||||||
|
selectors: {
|
||||||
|
":hover": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1Hover)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
".ms-DetailsHeader-cellTitle": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
".ms-DetailsRow": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
".ms-DetailsRow-cell": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
// Tooltip styling for cells
|
||||||
|
".ms-TooltipHost": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
".ms-DetailsRow-cell .ms-TooltipHost": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -166,7 +271,18 @@ export const separatorStyles: Partial<ISeparatorStyles> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const messageBarStyles: Partial<IMessageBarStyles> = {
|
export const messageBarStyles: Partial<IMessageBarStyles> = {
|
||||||
root: { marginTop: "5px", backgroundColor: "white" },
|
root: {
|
||||||
|
marginTop: "5px",
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1)",
|
||||||
|
selectors: {
|
||||||
|
"&.ms-MessageBar--severeWarning": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground4)",
|
||||||
|
},
|
||||||
|
"&.ms-MessageBar--warning": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground3)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
text: { fontSize: 14 },
|
text: { fontSize: 14 },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -222,9 +338,11 @@ export const getEstimatedSpendingElement = (
|
|||||||
const ruRange: string = isAutoscale ? throughput / 10 + " RU/s - " : "";
|
const ruRange: string = isAutoscale ? throughput / 10 + " RU/s - " : "";
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
<Text style={{ fontWeight: 600 }}>Cost estimate*</Text>
|
<Text style={{ fontWeight: 600, color: "var(--colorNeutralForeground1)" }}>Cost estimate*</Text>
|
||||||
{costElement}
|
{costElement}
|
||||||
<Text style={{ fontWeight: 600, marginTop: 15 }}>How we calculate this</Text>
|
<Text style={{ fontWeight: 600, marginTop: 15, color: "var(--colorNeutralForeground1)" }}>
|
||||||
|
How we calculate this
|
||||||
|
</Text>
|
||||||
<Stack id="throughputSpendElement" style={{ marginTop: 5 }}>
|
<Stack id="throughputSpendElement" style={{ marginTop: 5 }}>
|
||||||
<span>
|
<span>
|
||||||
{numberOfRegions} region{numberOfRegions > 1 && <span>s</span>}
|
{numberOfRegions} region{numberOfRegions > 1 && <span>s</span>}
|
||||||
@@ -238,7 +356,7 @@ export const getEstimatedSpendingElement = (
|
|||||||
{priceBreakdown.pricePerRu}/RU
|
{priceBreakdown.pricePerRu}/RU
|
||||||
</span>
|
</span>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Text style={{ marginTop: 15 }}>
|
<Text style={{ marginTop: 15, color: "var(--colorNeutralForeground1)" }}>
|
||||||
<em>*{estimatedCostDisclaimer}</em>
|
<em>*{estimatedCostDisclaimer}</em>
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -285,7 +403,7 @@ export const updateThroughputDelayedApplyWarningMessage: JSX.Element = (
|
|||||||
|
|
||||||
export const getUpdateThroughputBeyondInstantLimitMessage = (instantMaximumThroughput: number): JSX.Element => {
|
export const getUpdateThroughputBeyondInstantLimitMessage = (instantMaximumThroughput: number): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<Text styles={infoAndToolTipTextStyle} id="updateThroughputDelayedApplyWarningMessage">
|
<Text id="updateThroughputDelayedApplyWarningMessage">
|
||||||
Scaling up will take 4-6 hours as it exceeds what Azure Cosmos DB can instantly support currently based on your
|
Scaling up will take 4-6 hours as it exceeds what Azure Cosmos DB can instantly support currently based on your
|
||||||
number of physical partitions. You can increase your throughput to {instantMaximumThroughput} instantly or proceed
|
number of physical partitions. You can increase your throughput to {instantMaximumThroughput} instantly or proceed
|
||||||
with this value and wait until the scale-up is completed.
|
with this value and wait until the scale-up is completed.
|
||||||
@@ -303,7 +421,7 @@ export const getUpdateThroughputBeyondSupportLimitMessage = (
|
|||||||
Your request to increase throughput exceeds the pre-allocated capacity which may take longer than expected.
|
Your request to increase throughput exceeds the pre-allocated capacity which may take longer than expected.
|
||||||
There are three options you can choose from to proceed:
|
There are three options you can choose from to proceed:
|
||||||
</Text>
|
</Text>
|
||||||
<ol style={{ fontSize: 14, color: "windowtext", marginTop: "5px" }}>
|
<ol style={{ fontSize: 14, color: "var(--colorNeutralForeground1)", marginTop: "5px" }}>
|
||||||
<li>You can instantly scale up to {instantMaximumThroughput} RU/s.</li>
|
<li>You can instantly scale up to {instantMaximumThroughput} RU/s.</li>
|
||||||
{instantMaximumThroughput < maximumThroughput && (
|
{instantMaximumThroughput < maximumThroughput && (
|
||||||
<li>You can asynchronously scale up to any value under {maximumThroughput} RU/s in 4-6 hours.</li>
|
<li>You can asynchronously scale up to any value under {maximumThroughput} RU/s in 4-6 hours.</li>
|
||||||
@@ -339,7 +457,7 @@ export const getUpdateThroughputBelowMinimumMessage = (minimum: number): JSX.Ele
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const saveThroughputWarningMessage: JSX.Element = (
|
export const saveThroughputWarningMessage: JSX.Element = (
|
||||||
<Text styles={infoAndToolTipTextStyle}>
|
<Text>
|
||||||
Your bill will be affected as you update your throughput settings. Please review the updated cost estimate below
|
Your bill will be affected as you update your throughput settings. Please review the updated cost estimate below
|
||||||
before saving your changes
|
before saving your changes
|
||||||
</Text>
|
</Text>
|
||||||
@@ -459,9 +577,13 @@ export const changeFeedPolicyToolTip: JSX.Element = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const mongoIndexingPolicyDisclaimer: JSX.Element = (
|
export const mongoIndexingPolicyDisclaimer: JSX.Element = (
|
||||||
<Text>
|
<Text style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||||
For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.
|
For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.
|
||||||
<Link href="https://docs.microsoft.com/azure/cosmos-db/mongodb-indexing#index-types" target="_blank">
|
<Link
|
||||||
|
href="https://docs.microsoft.com/azure/cosmos-db/mongodb-indexing#index-types"
|
||||||
|
target="_blank"
|
||||||
|
style={{ color: "var(--colorBrandForeground1)" }}
|
||||||
|
>
|
||||||
{` Compound indexes `}
|
{` Compound indexes `}
|
||||||
</Link>
|
</Link>
|
||||||
are only used for sorting query results. If you need to add a compound index, you can create one using the Mongo
|
are only used for sorting query results. If you need to add a compound index, you can create one using the Mongo
|
||||||
@@ -470,7 +592,7 @@ export const mongoIndexingPolicyDisclaimer: JSX.Element = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const mongoCompoundIndexNotSupportedMessage: JSX.Element = (
|
export const mongoCompoundIndexNotSupportedMessage: JSX.Element = (
|
||||||
<Text>
|
<Text style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||||
Collections with compound indexes are not yet supported in the indexing editor. To modify indexing policy for this
|
Collections with compound indexes are not yet supported in the indexing editor. To modify indexing policy for this
|
||||||
collection, use the Mongo Shell.
|
collection, use the Mongo Shell.
|
||||||
</Text>
|
</Text>
|
||||||
@@ -519,14 +641,50 @@ export const getTextFieldStyles = (current: isDirtyTypes, baseline: isDirtyTypes
|
|||||||
fieldGroup: {
|
fieldGroup: {
|
||||||
height: 25,
|
height: 25,
|
||||||
width: 300,
|
width: 300,
|
||||||
borderColor: isDirty(current, baseline) ? StyleConstants.Dirty : "",
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
borderColor: isDirty(current, baseline) ? StyleConstants.Dirty : "var(--colorNeutralStroke1)",
|
||||||
selectors: {
|
selectors: {
|
||||||
":disabled": {
|
":disabled": {
|
||||||
backgroundColor: StyleConstants.BaseMedium,
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
borderColor: StyleConstants.BaseMediumHigh,
|
borderColor: "var(--colorNeutralStroke1)",
|
||||||
|
color: "var(--colorNeutralForeground2)",
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"input:disabled": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
color: "var(--colorNeutralForeground2)",
|
||||||
|
},
|
||||||
|
"input#autopilotInput": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground4)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
field: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
selectors: {
|
||||||
|
":disabled": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
color: "var(--colorNeutralForeground2)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
subComponentStyles: {
|
||||||
|
label: {
|
||||||
|
root: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
suffix: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
border: "1px solid var(--colorNeutralStroke1)",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getChoiceGroupStyles = (
|
export const getChoiceGroupStyles = (
|
||||||
@@ -534,6 +692,28 @@ export const getChoiceGroupStyles = (
|
|||||||
baseline: isDirtyTypes,
|
baseline: isDirtyTypes,
|
||||||
isHorizontal?: boolean,
|
isHorizontal?: boolean,
|
||||||
): Partial<IChoiceGroupStyles> => ({
|
): Partial<IChoiceGroupStyles> => ({
|
||||||
|
label: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
root: {
|
||||||
|
selectors: {
|
||||||
|
".ms-ChoiceFieldLabel": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
".ms-ChoiceField-field:hover .ms-ChoiceFieldLabel": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
".ms-ChoiceField:hover .ms-ChoiceFieldLabel": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
".ms-ChoiceField:hover .ms-ChoiceField-innerField": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
".ms-ChoiceField-innerField": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
flexContainer: [
|
flexContainer: [
|
||||||
{
|
{
|
||||||
selectors: {
|
selectors: {
|
||||||
@@ -548,6 +728,16 @@ export const getChoiceGroupStyles = (
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontFamily: StyleConstants.DataExplorerFont,
|
fontFamily: StyleConstants.DataExplorerFont,
|
||||||
padding: "2px 5px",
|
padding: "2px 5px",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
".ms-ChoiceFieldLabel": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
".ms-ChoiceFieldLabel:hover": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
".ms-ChoiceField-field:hover .ms-ChoiceFieldLabel": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
display: isHorizontal ? "inline-flex" : "default",
|
display: isHorizontal ? "inline-flex" : "default",
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { FontIcon, Link, MessageBar, MessageBarType, Stack, Text } from "@fluentui/react";
|
import { FontIcon, IMessageBarStyles, Link, MessageBar, MessageBarType, Stack, Text } from "@fluentui/react";
|
||||||
import * as DataModels from "Contracts/DataModels";
|
import * as DataModels from "Contracts/DataModels";
|
||||||
import { titleAndInputStackProps, unsavedEditorWarningMessage } from "Explorer/Controls/Settings/SettingsRenderUtils";
|
import { titleAndInputStackProps, unsavedEditorWarningMessage } from "Explorer/Controls/Settings/SettingsRenderUtils";
|
||||||
import { isDirty } from "Explorer/Controls/Settings/SettingsUtils";
|
import { isDirty } from "Explorer/Controls/Settings/SettingsUtils";
|
||||||
import { loadMonaco } from "Explorer/LazyMonaco";
|
import { loadMonaco } from "Explorer/LazyMonaco";
|
||||||
|
import { monacoTheme, useThemeStore } from "hooks/useTheme";
|
||||||
import * as monaco from "monaco-editor";
|
import * as monaco from "monaco-editor";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
export interface ComputedPropertiesComponentProps {
|
export interface ComputedPropertiesComponentProps {
|
||||||
computedPropertiesContent: DataModels.ComputedProperties;
|
computedPropertiesContent: DataModels.ComputedProperties;
|
||||||
computedPropertiesContentBaseline: DataModels.ComputedProperties;
|
computedPropertiesContentBaseline: DataModels.ComputedProperties;
|
||||||
@@ -27,6 +27,24 @@ export class ComputedPropertiesComponent extends React.Component<
|
|||||||
private shouldCheckComponentIsDirty = true;
|
private shouldCheckComponentIsDirty = true;
|
||||||
private computedPropertiesDiv = React.createRef<HTMLDivElement>();
|
private computedPropertiesDiv = React.createRef<HTMLDivElement>();
|
||||||
private computedPropertiesEditor: monaco.editor.IStandaloneCodeEditor;
|
private computedPropertiesEditor: monaco.editor.IStandaloneCodeEditor;
|
||||||
|
private themeUnsubscribe: () => void;
|
||||||
|
|
||||||
|
private darkThemeMessageBarStyles: Partial<IMessageBarStyles> = {
|
||||||
|
root: {
|
||||||
|
selectors: {
|
||||||
|
"&.ms-MessageBar--warning": {
|
||||||
|
backgroundColor: "var(--colorStatusWarningBackground1)",
|
||||||
|
border: "1px solid var(--colorStatusWarningBorder1)",
|
||||||
|
},
|
||||||
|
".ms-MessageBar-icon": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
".ms-MessageBar-text": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
constructor(props: ComputedPropertiesComponentProps) {
|
constructor(props: ComputedPropertiesComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -48,6 +66,10 @@ export class ComputedPropertiesComponent extends React.Component<
|
|||||||
this.onComponentUpdate();
|
this.onComponentUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(): void {
|
||||||
|
this.themeUnsubscribe && this.themeUnsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
public resetComputedPropertiesEditor = (): void => {
|
public resetComputedPropertiesEditor = (): void => {
|
||||||
if (!this.computedPropertiesEditor) {
|
if (!this.computedPropertiesEditor) {
|
||||||
this.createComputedPropertiesEditor();
|
this.createComputedPropertiesEditor();
|
||||||
@@ -86,8 +108,16 @@ export class ComputedPropertiesComponent extends React.Component<
|
|||||||
value: value,
|
value: value,
|
||||||
language: "json",
|
language: "json",
|
||||||
ariaLabel: "Computed properties",
|
ariaLabel: "Computed properties",
|
||||||
|
theme: monacoTheme(),
|
||||||
});
|
});
|
||||||
if (this.computedPropertiesEditor) {
|
if (this.computedPropertiesEditor) {
|
||||||
|
// Subscribe to theme changes
|
||||||
|
this.themeUnsubscribe = useThemeStore.subscribe(() => {
|
||||||
|
if (this.computedPropertiesEditor) {
|
||||||
|
monaco.editor.setTheme(monacoTheme());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const computedPropertiesEditorModel = this.computedPropertiesEditor.getModel();
|
const computedPropertiesEditorModel = this.computedPropertiesEditor.getModel();
|
||||||
computedPropertiesEditorModel.onDidChangeContent(this.onEditorContentChange.bind(this));
|
computedPropertiesEditorModel.onDidChangeContent(this.onEditorContentChange.bind(this));
|
||||||
this.props.logComputedPropertiesSuccessMessage();
|
this.props.logComputedPropertiesSuccessMessage();
|
||||||
@@ -111,11 +141,15 @@ export class ComputedPropertiesComponent extends React.Component<
|
|||||||
return (
|
return (
|
||||||
<Stack {...titleAndInputStackProps}>
|
<Stack {...titleAndInputStackProps}>
|
||||||
{isDirty(this.props.computedPropertiesContent, this.props.computedPropertiesContentBaseline) && (
|
{isDirty(this.props.computedPropertiesContent, this.props.computedPropertiesContentBaseline) && (
|
||||||
<MessageBar messageBarType={MessageBarType.warning}>
|
<MessageBar
|
||||||
|
messageBarType={MessageBarType.warning}
|
||||||
|
messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}
|
||||||
|
styles={this.darkThemeMessageBarStyles}
|
||||||
|
>
|
||||||
{unsavedEditorWarningMessage("computedProperties")}
|
{unsavedEditorWarningMessage("computedProperties")}
|
||||||
</MessageBar>
|
</MessageBar>
|
||||||
)}
|
)}
|
||||||
<Text style={{ marginLeft: "30px", marginBottom: "10px" }}>
|
<Text style={{ marginLeft: "30px", marginBottom: "10px", color: "var(--colorNeutralForeground1)" }}>
|
||||||
<Link target="_blank" href="https://aka.ms/computed-properties-preview/">
|
<Link target="_blank" href="https://aka.ms/computed-properties-preview/">
|
||||||
{"Learn more"} <FontIcon iconName="NavigateExternalInline" />
|
{"Learn more"} <FontIcon iconName="NavigateExternalInline" />
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
conflictResolutionCustomToolTip,
|
conflictResolutionCustomToolTip,
|
||||||
conflictResolutionLwwTooltip,
|
conflictResolutionLwwTooltip,
|
||||||
getChoiceGroupStyles,
|
getChoiceGroupStyles,
|
||||||
getTextFieldStyles,
|
|
||||||
subComponentStackProps,
|
subComponentStackProps,
|
||||||
} from "../SettingsRenderUtils";
|
} from "../SettingsRenderUtils";
|
||||||
import { isDirty } from "../SettingsUtils";
|
import { isDirty } from "../SettingsUtils";
|
||||||
@@ -106,10 +105,46 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
|
|||||||
id="conflictResolutionLwwTextField"
|
id="conflictResolutionLwwTextField"
|
||||||
label={"Conflict Resolver Property"}
|
label={"Conflict Resolver Property"}
|
||||||
onRenderLabel={this.onRenderLwwComponentTextField}
|
onRenderLabel={this.onRenderLwwComponentTextField}
|
||||||
styles={getTextFieldStyles(
|
styles={{
|
||||||
this.props.conflictResolutionPolicyPath,
|
fieldGroup: {
|
||||||
this.props.conflictResolutionPolicyPathBaseline,
|
height: 25,
|
||||||
)}
|
width: 300,
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
borderColor: "var(--colorNeutralStroke1)",
|
||||||
|
selectors: {
|
||||||
|
":disabled": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
borderColor: "var(--colorNeutralStroke1)",
|
||||||
|
color: "var(--colorNeutralForeground2)",
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"input:disabled": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
color: "var(--colorNeutralForeground2)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
field: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
selectors: {
|
||||||
|
":disabled": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
color: "var(--colorNeutralForeground2)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
subComponentStyles: {
|
||||||
|
label: {
|
||||||
|
root: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
value={this.props.conflictResolutionPolicyPath}
|
value={this.props.conflictResolutionPolicyPath}
|
||||||
onChange={this.onConflictResolutionPolicyPathChange}
|
onChange={this.onConflictResolutionPolicyPathChange}
|
||||||
/>
|
/>
|
||||||
@@ -119,19 +154,57 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
|
|||||||
<ToolTipLabelComponent label={props.label} toolTipElement={conflictResolutionCustomToolTip} />
|
<ToolTipLabelComponent label={props.label} toolTipElement={conflictResolutionCustomToolTip} />
|
||||||
);
|
);
|
||||||
|
|
||||||
private getConflictResolutionCustomComponent = (): JSX.Element => (
|
private getConflictResolutionCustomComponent = (): JSX.Element => {
|
||||||
|
return (
|
||||||
<TextField
|
<TextField
|
||||||
id="conflictResolutionCustomTextField"
|
id="conflictResolutionCustomTextField"
|
||||||
label="Stored procedure"
|
label="Stored procedure"
|
||||||
onRenderLabel={this.onRenderCustomComponentTextField}
|
onRenderLabel={this.onRenderCustomComponentTextField}
|
||||||
styles={getTextFieldStyles(
|
styles={{
|
||||||
this.props.conflictResolutionPolicyProcedure,
|
fieldGroup: {
|
||||||
this.props.conflictResolutionPolicyProcedureBaseline,
|
height: 25,
|
||||||
)}
|
width: 300,
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
borderColor: "var(--colorNeutralStroke1)",
|
||||||
|
selectors: {
|
||||||
|
":disabled": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
borderColor: "var(--colorNeutralStroke1)",
|
||||||
|
color: "var(--colorNeutralForeground2)",
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"input:disabled": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
color: "var(--colorNeutralForeground2)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
field: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
selectors: {
|
||||||
|
":disabled": {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground2)",
|
||||||
|
color: "var(--colorNeutralForeground2)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
subComponentStyles: {
|
||||||
|
label: {
|
||||||
|
root: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
value={this.props.conflictResolutionPolicyProcedure}
|
value={this.props.conflictResolutionPolicyProcedure}
|
||||||
onChange={this.onConflictResolutionPolicyProcedureChange}
|
onChange={this.onConflictResolutionPolicyProcedureChange}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -102,11 +102,57 @@ export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> =
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Pivot onLinkClick={onPivotChange} selectedKey={ContainerPolicyTabTypes[selectedTab]}>
|
<Pivot
|
||||||
|
onLinkClick={onPivotChange}
|
||||||
|
selectedKey={ContainerPolicyTabTypes[selectedTab]}
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
selectors: {
|
||||||
|
":hover": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
":active": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
linkIsSelected: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
selectors: {
|
||||||
|
":before": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
backgroundColor: "var(--colorBrandForeground1)",
|
||||||
|
},
|
||||||
|
":hover": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
":active": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
linkContent: {
|
||||||
|
color: "inherit",
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
color: "inherit",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
{isVectorSearchEnabled && (
|
{isVectorSearchEnabled && (
|
||||||
<PivotItem
|
<PivotItem
|
||||||
itemKey={ContainerPolicyTabTypes[ContainerPolicyTabTypes.VectorPolicyTab]}
|
itemKey={ContainerPolicyTabTypes[ContainerPolicyTabTypes.VectorPolicyTab]}
|
||||||
style={{ marginTop: 20 }}
|
style={{ marginTop: 20, color: "var(--colorNeutralForeground1)" }}
|
||||||
headerText="Vector Policy"
|
headerText="Vector Policy"
|
||||||
>
|
>
|
||||||
<Stack {...titleAndInputStackProps} styles={{ root: { position: "relative", maxWidth: "400px" } }}>
|
<Stack {...titleAndInputStackProps} styles={{ root: { position: "relative", maxWidth: "400px" } }}>
|
||||||
@@ -128,7 +174,7 @@ export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> =
|
|||||||
{isFullTextSearchEnabled && (
|
{isFullTextSearchEnabled && (
|
||||||
<PivotItem
|
<PivotItem
|
||||||
itemKey={ContainerPolicyTabTypes[ContainerPolicyTabTypes.FullTextPolicyTab]}
|
itemKey={ContainerPolicyTabTypes[ContainerPolicyTabTypes.FullTextPolicyTab]}
|
||||||
style={{ marginTop: 20 }}
|
style={{ marginTop: 20, color: "var(--colorNeutralForeground1)" }}
|
||||||
headerText="Full Text Policy"
|
headerText="Full Text Policy"
|
||||||
>
|
>
|
||||||
<Stack {...titleAndInputStackProps} styles={{ root: { position: "relative", maxWidth: "400px" } }}>
|
<Stack {...titleAndInputStackProps} styles={{ root: { position: "relative", maxWidth: "400px" } }}>
|
||||||
@@ -144,7 +190,27 @@ export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> =
|
|||||||
) : (
|
) : (
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
id={"create-full-text-policy"}
|
id={"create-full-text-policy"}
|
||||||
styles={{ root: { fontSize: 12 } }}
|
styles={{
|
||||||
|
root: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
borderColor: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
rootHovered: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
borderColor: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
rootPressed: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
borderColor: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
rootDisabled: {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
checkAndSendFullTextPolicyToSettings({
|
checkAndSendFullTextPolicyToSettings({
|
||||||
defaultLanguage: getFullTextLanguageOptions()[0].key as never,
|
defaultLanguage: getFullTextLanguageOptions()[0].key as never,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { MessageBar, MessageBarType, Stack } from "@fluentui/react";
|
import { IMessageBarStyles, MessageBar, MessageBarType, Stack } from "@fluentui/react";
|
||||||
|
import { monacoTheme, useThemeStore } from "hooks/useTheme";
|
||||||
import * as monaco from "monaco-editor";
|
import * as monaco from "monaco-editor";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as DataModels from "../../../../Contracts/DataModels";
|
import * as DataModels from "../../../../Contracts/DataModels";
|
||||||
@@ -6,7 +7,6 @@ import { loadMonaco } from "../../../LazyMonaco";
|
|||||||
import { titleAndInputStackProps, unsavedEditorWarningMessage } from "../SettingsRenderUtils";
|
import { titleAndInputStackProps, unsavedEditorWarningMessage } from "../SettingsRenderUtils";
|
||||||
import { isDirty, isIndexTransforming } from "../SettingsUtils";
|
import { isDirty, isIndexTransforming } from "../SettingsUtils";
|
||||||
import { IndexingPolicyRefreshComponent } from "./IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
|
import { IndexingPolicyRefreshComponent } from "./IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
|
||||||
|
|
||||||
export interface IndexingPolicyComponentProps {
|
export interface IndexingPolicyComponentProps {
|
||||||
shouldDiscardIndexingPolicy: boolean;
|
shouldDiscardIndexingPolicy: boolean;
|
||||||
resetShouldDiscardIndexingPolicy: () => void;
|
resetShouldDiscardIndexingPolicy: () => void;
|
||||||
@@ -31,6 +31,24 @@ export class IndexingPolicyComponent extends React.Component<
|
|||||||
private shouldCheckComponentIsDirty = true;
|
private shouldCheckComponentIsDirty = true;
|
||||||
private indexingPolicyDiv = React.createRef<HTMLDivElement>();
|
private indexingPolicyDiv = React.createRef<HTMLDivElement>();
|
||||||
private indexingPolicyEditor: monaco.editor.IStandaloneCodeEditor;
|
private indexingPolicyEditor: monaco.editor.IStandaloneCodeEditor;
|
||||||
|
private themeUnsubscribe: () => void;
|
||||||
|
|
||||||
|
private darkThemeMessageBarStyles: Partial<IMessageBarStyles> = {
|
||||||
|
root: {
|
||||||
|
selectors: {
|
||||||
|
"&.ms-MessageBar--warning": {
|
||||||
|
backgroundColor: "var(--colorStatusWarningBackground1)",
|
||||||
|
border: "1px solid var(--colorStatusWarningBorder1)",
|
||||||
|
},
|
||||||
|
".ms-MessageBar-icon": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
".ms-MessageBar-text": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
constructor(props: IndexingPolicyComponentProps) {
|
constructor(props: IndexingPolicyComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -52,6 +70,10 @@ export class IndexingPolicyComponent extends React.Component<
|
|||||||
this.onComponentUpdate();
|
this.onComponentUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(): void {
|
||||||
|
this.themeUnsubscribe && this.themeUnsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
public resetIndexingPolicyEditor = (): void => {
|
public resetIndexingPolicyEditor = (): void => {
|
||||||
if (!this.indexingPolicyEditor) {
|
if (!this.indexingPolicyEditor) {
|
||||||
this.createIndexingPolicyEditor();
|
this.createIndexingPolicyEditor();
|
||||||
@@ -87,20 +109,32 @@ export class IndexingPolicyComponent extends React.Component<
|
|||||||
};
|
};
|
||||||
|
|
||||||
private async createIndexingPolicyEditor(): Promise<void> {
|
private async createIndexingPolicyEditor(): Promise<void> {
|
||||||
|
if (!this.indexingPolicyDiv.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const value: string = JSON.stringify(this.props.indexingPolicyContent, undefined, 4);
|
const value: string = JSON.stringify(this.props.indexingPolicyContent, undefined, 4);
|
||||||
const monaco = await loadMonaco();
|
const monaco = await loadMonaco();
|
||||||
|
if (this.indexingPolicyDiv.current) {
|
||||||
this.indexingPolicyEditor = monaco.editor.create(this.indexingPolicyDiv.current, {
|
this.indexingPolicyEditor = monaco.editor.create(this.indexingPolicyDiv.current, {
|
||||||
value: value,
|
value: value,
|
||||||
language: "json",
|
language: "json",
|
||||||
readOnly: isIndexTransforming(this.props.indexTransformationProgress),
|
readOnly: isIndexTransforming(this.props.indexTransformationProgress),
|
||||||
ariaLabel: "Indexing Policy",
|
ariaLabel: "Indexing Policy",
|
||||||
|
theme: monacoTheme(),
|
||||||
});
|
});
|
||||||
if (this.indexingPolicyEditor) {
|
if (this.indexingPolicyEditor) {
|
||||||
|
this.themeUnsubscribe = useThemeStore.subscribe(() => {
|
||||||
|
if (this.indexingPolicyEditor) {
|
||||||
|
monaco.editor.setTheme(monacoTheme());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel();
|
const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel();
|
||||||
indexingPolicyEditorModel.onDidChangeContent(this.onEditorContentChange.bind(this));
|
indexingPolicyEditorModel.onDidChangeContent(this.onEditorContentChange.bind(this));
|
||||||
this.props.logIndexingPolicySuccessMessage();
|
this.props.logIndexingPolicySuccessMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private onEditorContentChange = (): void => {
|
private onEditorContentChange = (): void => {
|
||||||
const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel();
|
const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel();
|
||||||
@@ -121,7 +155,13 @@ export class IndexingPolicyComponent extends React.Component<
|
|||||||
refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress}
|
refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress}
|
||||||
/>
|
/>
|
||||||
{isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && (
|
{isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && (
|
||||||
<MessageBar messageBarType={MessageBarType.warning}>{unsavedEditorWarningMessage("indexPolicy")}</MessageBar>
|
<MessageBar
|
||||||
|
messageBarType={MessageBarType.warning}
|
||||||
|
messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}
|
||||||
|
styles={this.darkThemeMessageBarStyles}
|
||||||
|
>
|
||||||
|
{unsavedEditorWarningMessage("indexPolicy")}
|
||||||
|
</MessageBar>
|
||||||
)}
|
)}
|
||||||
<div className="settingsV2Editor" tabIndex={0} ref={this.indexingPolicyDiv}></div>
|
<div className="settingsV2Editor" tabIndex={0} ref={this.indexingPolicyDiv}></div>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ exports[`IndexingPolicyRefreshComponent renders 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
{
|
{
|
||||||
"root": {
|
"root": {
|
||||||
"color": "windowtext",
|
"color": "var(--colorNeutralForeground1)",
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
DetailsListLayoutMode,
|
DetailsListLayoutMode,
|
||||||
IColumn,
|
IColumn,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
IMessageBarStyles,
|
||||||
MessageBar,
|
MessageBar,
|
||||||
MessageBarType,
|
MessageBarType,
|
||||||
SelectionMode,
|
SelectionMode,
|
||||||
@@ -30,12 +31,12 @@ import {
|
|||||||
} from "../../SettingsRenderUtils";
|
} from "../../SettingsRenderUtils";
|
||||||
import {
|
import {
|
||||||
AddMongoIndexProps,
|
AddMongoIndexProps,
|
||||||
MongoIndexIdField,
|
|
||||||
MongoIndexTypes,
|
|
||||||
MongoNotificationType,
|
|
||||||
getMongoIndexType,
|
getMongoIndexType,
|
||||||
getMongoIndexTypeText,
|
getMongoIndexTypeText,
|
||||||
isIndexTransforming,
|
isIndexTransforming,
|
||||||
|
MongoIndexIdField,
|
||||||
|
MongoIndexTypes,
|
||||||
|
MongoNotificationType,
|
||||||
} from "../../SettingsUtils";
|
} from "../../SettingsUtils";
|
||||||
import { IndexingPolicyRefreshComponent } from "../IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
|
import { IndexingPolicyRefreshComponent } from "../IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
|
||||||
import { AddMongoIndexComponent } from "./AddMongoIndexComponent";
|
import { AddMongoIndexComponent } from "./AddMongoIndexComponent";
|
||||||
@@ -63,6 +64,24 @@ interface MongoIndexDisplayProps {
|
|||||||
export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingPolicyComponentProps> {
|
export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingPolicyComponentProps> {
|
||||||
private shouldCheckComponentIsDirty = true;
|
private shouldCheckComponentIsDirty = true;
|
||||||
private addMongoIndexComponentRefs: React.RefObject<AddMongoIndexComponent>[] = [];
|
private addMongoIndexComponentRefs: React.RefObject<AddMongoIndexComponent>[] = [];
|
||||||
|
|
||||||
|
private darkThemeMessageBarStyles: Partial<IMessageBarStyles> = {
|
||||||
|
root: {
|
||||||
|
selectors: {
|
||||||
|
"&.ms-MessageBar--warning": {
|
||||||
|
backgroundColor: "var(--colorStatusWarningBackground1)",
|
||||||
|
border: "1px solid var(--colorStatusWarningBorder1)",
|
||||||
|
},
|
||||||
|
".ms-MessageBar-icon": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
".ms-MessageBar-text": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
private initialIndexesColumns: IColumn[] = [
|
private initialIndexesColumns: IColumn[] = [
|
||||||
{ key: "definition", name: "Definition", fieldName: "definition", minWidth: 100, maxWidth: 200, isResizable: true },
|
{ key: "definition", name: "Definition", fieldName: "definition", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||||
{ key: "type", name: "Type", fieldName: "type", minWidth: 100, maxWidth: 200, isResizable: true },
|
{ key: "type", name: "Type", fieldName: "type", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||||
@@ -171,8 +190,8 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
|||||||
let mongoIndexDisplayProps: MongoIndexDisplayProps;
|
let mongoIndexDisplayProps: MongoIndexDisplayProps;
|
||||||
if (type) {
|
if (type) {
|
||||||
mongoIndexDisplayProps = {
|
mongoIndexDisplayProps = {
|
||||||
definition: <Text>{definition}</Text>,
|
definition: <Text style={{ color: "var(--colorNeutralForeground1)" }}>{definition}</Text>,
|
||||||
type: <Text>{getMongoIndexTypeText(type)}</Text>,
|
type: <Text style={{ color: "var(--colorNeutralForeground1)" }}>{getMongoIndexTypeText(type)}</Text>,
|
||||||
actionButton: definition === MongoIndexIdField ? <></> : this.getActionButton(arrayPosition, isCurrentIndex),
|
actionButton: definition === MongoIndexIdField ? <></> : this.getActionButton(arrayPosition, isCurrentIndex),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -306,7 +325,15 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
|||||||
indexTransformationProgress={this.props.indexTransformationProgress}
|
indexTransformationProgress={this.props.indexTransformationProgress}
|
||||||
refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress}
|
refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress}
|
||||||
/>
|
/>
|
||||||
{warningMessage && <MessageBar messageBarType={MessageBarType.warning}>{warningMessage}</MessageBar>}
|
{warningMessage && (
|
||||||
|
<MessageBar
|
||||||
|
messageBarType={MessageBarType.warning}
|
||||||
|
messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}
|
||||||
|
styles={this.darkThemeMessageBarStyles}
|
||||||
|
>
|
||||||
|
{warningMessage}
|
||||||
|
</MessageBar>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,6 +22,14 @@ exports[`AddMongoIndexComponent renders 1`] = `
|
|||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
styles={
|
styles={
|
||||||
{
|
{
|
||||||
|
"field": {
|
||||||
|
"backgroundColor": "var(--colorNeutralBackground2)",
|
||||||
|
"color": "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"fieldGroup": {
|
||||||
|
"backgroundColor": "var(--colorNeutralBackground2)",
|
||||||
|
"borderColor": "var(--colorNeutralStroke1)",
|
||||||
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"paddingLeft": 10,
|
"paddingLeft": 10,
|
||||||
"width": 210,
|
"width": 210,
|
||||||
@@ -49,10 +57,52 @@ exports[`AddMongoIndexComponent renders 1`] = `
|
|||||||
selectedKey="Single"
|
selectedKey="Single"
|
||||||
styles={
|
styles={
|
||||||
{
|
{
|
||||||
|
"callout": {
|
||||||
|
"backgroundColor": "var(--colorNeutralBackground2)",
|
||||||
|
"border": "1px solid var(--colorNeutralStroke1)",
|
||||||
|
},
|
||||||
|
"caretDown": {
|
||||||
|
"color": "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
"dropdown": {
|
"dropdown": {
|
||||||
"paddingleft": 10,
|
"paddingLeft": 10,
|
||||||
"width": 202,
|
"width": 202,
|
||||||
},
|
},
|
||||||
|
"dropdownItem": {
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"color": "var(--colorNeutralForeground1)",
|
||||||
|
"selectors": {
|
||||||
|
"&:focus": {
|
||||||
|
"backgroundColor": "rgba(255, 255, 255, 0.1)",
|
||||||
|
"color": "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"&:hover": {
|
||||||
|
"backgroundColor": "rgba(255, 255, 255, 0.1)",
|
||||||
|
"color": "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"dropdownItemSelected": {
|
||||||
|
"backgroundColor": "rgba(255, 255, 255, 0.08)",
|
||||||
|
"color": "var(--colorNeutralForeground1)",
|
||||||
|
"selectors": {
|
||||||
|
"&:hover": {
|
||||||
|
"backgroundColor": "rgba(255, 255, 255, 0.1)",
|
||||||
|
"color": "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"dropdownItems": {
|
||||||
|
"backgroundColor": "var(--colorNeutralBackground2)",
|
||||||
|
},
|
||||||
|
"dropdownOptionText": {
|
||||||
|
"color": "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"backgroundColor": "var(--colorNeutralBackground2)",
|
||||||
|
"borderColor": "var(--colorNeutralStroke1)",
|
||||||
|
"color": "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`MongoIndexingPolicyComponent error shown for collection with compound indexes 1`] = `
|
exports[`MongoIndexingPolicyComponent error shown for collection with compound indexes 1`] = `
|
||||||
<Text>
|
<Text
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"color": "var(--colorNeutralForeground1)",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
Collections with compound indexes are not yet supported in the indexing editor. To modify indexing policy for this collection, use the Mongo Shell.
|
Collections with compound indexes are not yet supported in the indexing editor. To modify indexing policy for this collection, use the Mongo Shell.
|
||||||
</Text>
|
</Text>
|
||||||
`;
|
`;
|
||||||
@@ -17,10 +23,21 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
|
|||||||
<IndexingPolicyRefreshComponent
|
<IndexingPolicyRefreshComponent
|
||||||
refreshIndexTransformationProgress={[Function]}
|
refreshIndexTransformationProgress={[Function]}
|
||||||
/>
|
/>
|
||||||
<Text>
|
<Text
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"color": "var(--colorNeutralForeground1)",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.
|
For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.
|
||||||
<StyledLinkBase
|
<StyledLinkBase
|
||||||
href="https://docs.microsoft.com/azure/cosmos-db/mongodb-indexing#index-types"
|
href="https://docs.microsoft.com/azure/cosmos-db/mongodb-indexing#index-types"
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"color": "var(--colorBrandForeground1)",
|
||||||
|
}
|
||||||
|
}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Compound indexes
|
Compound indexes
|
||||||
@@ -83,9 +100,37 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
|
|||||||
{
|
{
|
||||||
"root": {
|
"root": {
|
||||||
"selectors": {
|
"selectors": {
|
||||||
|
".ms-DetailsHeader": {
|
||||||
|
"backgroundColor": "var(--colorNeutralBackground1)",
|
||||||
|
},
|
||||||
|
".ms-DetailsHeader-cell": {
|
||||||
|
"backgroundColor": "var(--colorNeutralBackground1)",
|
||||||
|
"color": "var(--colorNeutralForeground1)",
|
||||||
|
"selectors": {
|
||||||
|
":hover": {
|
||||||
|
"backgroundColor": "var(--colorNeutralBackground1Hover)",
|
||||||
|
"color": "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
".ms-DetailsHeader-cellTitle": {
|
||||||
|
"color": "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
".ms-DetailsRow": {
|
||||||
|
"color": "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
".ms-DetailsRow-cell": {
|
||||||
|
"color": "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
".ms-DetailsRow-cell .ms-TooltipHost": {
|
||||||
|
"color": "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
".ms-FocusZone": {
|
".ms-FocusZone": {
|
||||||
"paddingTop": 0,
|
"paddingTop": 0,
|
||||||
},
|
},
|
||||||
|
".ms-TooltipHost": {
|
||||||
|
"color": "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
DefaultButton,
|
DefaultButton,
|
||||||
FontWeights,
|
FontWeights,
|
||||||
|
IMessageBarStyles,
|
||||||
Link,
|
Link,
|
||||||
MessageBar,
|
MessageBar,
|
||||||
MessageBarType,
|
MessageBarType,
|
||||||
@@ -32,6 +33,23 @@ export interface PartitionKeyComponentProps {
|
|||||||
isReadOnly?: boolean; // true: cannot change partition key
|
isReadOnly?: boolean; // true: cannot change partition key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const darkThemeMessageBarStyles: Partial<IMessageBarStyles> = {
|
||||||
|
root: {
|
||||||
|
selectors: {
|
||||||
|
"&.ms-MessageBar--warning": {
|
||||||
|
backgroundColor: "var(--colorStatusWarningBackground1)",
|
||||||
|
border: "1px solid var(--colorStatusWarningBorder1)",
|
||||||
|
},
|
||||||
|
".ms-MessageBar-icon": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
".ms-MessageBar-text": {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
|
export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
|
||||||
database,
|
database,
|
||||||
collection,
|
collection,
|
||||||
@@ -66,13 +84,15 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
|
|||||||
const partitionKeyValue = getPartitionKeyValue();
|
const partitionKeyValue = getPartitionKeyValue();
|
||||||
|
|
||||||
const textHeadingStyle = {
|
const textHeadingStyle = {
|
||||||
root: { fontWeight: FontWeights.semibold, fontSize: 16 },
|
root: { fontWeight: FontWeights.semibold, fontSize: 16, color: "var(--colorNeutralForeground1)" },
|
||||||
};
|
};
|
||||||
|
|
||||||
const textSubHeadingStyle = {
|
const textSubHeadingStyle = {
|
||||||
root: { fontWeight: FontWeights.semibold },
|
root: { fontWeight: FontWeights.semibold, color: "var(--colorNeutralForeground1)" },
|
||||||
|
};
|
||||||
|
const textSubHeadingStyle1 = {
|
||||||
|
root: { color: "var(--colorNeutralForeground1)" },
|
||||||
};
|
};
|
||||||
|
|
||||||
const startPollingforUpdate = (currentJob: DataTransferJobGetResults) => {
|
const startPollingforUpdate = (currentJob: DataTransferJobGetResults) => {
|
||||||
if (isCurrentJobInProgress(currentJob)) {
|
if (isCurrentJobInProgress(currentJob)) {
|
||||||
const jobName = currentJob?.properties?.jobName;
|
const jobName = currentJob?.properties?.jobName;
|
||||||
@@ -167,32 +187,41 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
|
|||||||
<Text styles={textSubHeadingStyle}>Current {partitionKeyName.toLowerCase()}</Text>
|
<Text styles={textSubHeadingStyle}>Current {partitionKeyName.toLowerCase()}</Text>
|
||||||
<Text styles={textSubHeadingStyle}>Partitioning</Text>
|
<Text styles={textSubHeadingStyle}>Partitioning</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack tokens={{ childrenGap: 5 }}>
|
<Stack tokens={{ childrenGap: 5 }} data-test="partition-key-values">
|
||||||
<Text>{partitionKeyValue}</Text>
|
<Text styles={textSubHeadingStyle1}>{partitionKeyValue}</Text>
|
||||||
<Text>{isHierarchicalPartitionedContainer() ? "Hierarchical" : "Non-hierarchical"}</Text>
|
<Text styles={textSubHeadingStyle1}>
|
||||||
|
{isHierarchicalPartitionedContainer() ? "Hierarchical" : "Non-hierarchical"}
|
||||||
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{!isReadOnly && (
|
{!isReadOnly && (
|
||||||
<>
|
<>
|
||||||
<MessageBar messageBarType={MessageBarType.warning}>
|
<MessageBar
|
||||||
|
data-test="partition-key-warning"
|
||||||
|
messageBarType={MessageBarType.warning}
|
||||||
|
messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}
|
||||||
|
styles={darkThemeMessageBarStyles}
|
||||||
|
>
|
||||||
To safeguard the integrity of the data being copied to the new container, ensure that no updates are made to
|
To safeguard the integrity of the data being copied to the new container, ensure that no updates are made to
|
||||||
the source container for the entire duration of the partition key change process.
|
the source container for the entire duration of the partition key change process.
|
||||||
<Link
|
<Link
|
||||||
href="https://learn.microsoft.com/azure/cosmos-db/container-copy#how-does-container-copy-work"
|
href="https://learn.microsoft.com/azure/cosmos-db/container-copy#how-does-container-copy-work"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
underline
|
underline
|
||||||
|
style={{ color: "var(--colorBrandForeground1)" }}
|
||||||
>
|
>
|
||||||
Learn more
|
Learn more
|
||||||
</Link>
|
</Link>
|
||||||
</MessageBar>
|
</MessageBar>
|
||||||
<Text>
|
<Text styles={{ root: { color: "var(--colorNeutralForeground1)" } }}>
|
||||||
To change the partition key, a new destination container must be created or an existing destination
|
To change the partition key, a new destination container must be created or an existing destination
|
||||||
container selected. Data will then be copied to the destination container.
|
container selected. Data will then be copied to the destination container.
|
||||||
</Text>
|
</Text>
|
||||||
{configContext.platform !== Platform.Emulator && (
|
{configContext.platform !== Platform.Emulator && (
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
|
data-test="change-partition-key-button"
|
||||||
styles={{ root: { width: "fit-content" } }}
|
styles={{ root: { width: "fit-content" } }}
|
||||||
text="Change"
|
text="Change"
|
||||||
onClick={startPartitionkeyChangeWorkflow}
|
onClick={startPartitionkeyChangeWorkflow}
|
||||||
|
|||||||
@@ -1,4 +1,15 @@
|
|||||||
import { ChoiceGroup, IChoiceGroupOption, Label, Link, MessageBar, Stack, Text, TextField } from "@fluentui/react";
|
import {
|
||||||
|
ChoiceGroup,
|
||||||
|
IChoiceGroupOption,
|
||||||
|
Label,
|
||||||
|
Link,
|
||||||
|
MessageBar,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextField,
|
||||||
|
TooltipHost,
|
||||||
|
mergeStyleSets,
|
||||||
|
} from "@fluentui/react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ViewModels from "../../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../../Contracts/ViewModels";
|
||||||
import { userContext } from "../../../../UserContext";
|
import { userContext } from "../../../../UserContext";
|
||||||
@@ -25,6 +36,11 @@ import {
|
|||||||
} from "../SettingsUtils";
|
} from "../SettingsUtils";
|
||||||
import { ToolTipLabelComponent } from "./ToolTipLabelComponent";
|
import { ToolTipLabelComponent } from "./ToolTipLabelComponent";
|
||||||
|
|
||||||
|
const classNames = mergeStyleSets({
|
||||||
|
hintText: {
|
||||||
|
color: "var(--colorNeutralForeground1)", // theme-aware
|
||||||
|
},
|
||||||
|
});
|
||||||
export interface SubSettingsComponentProps {
|
export interface SubSettingsComponentProps {
|
||||||
collection: ViewModels.Collection;
|
collection: ViewModels.Collection;
|
||||||
timeToLive: TtlType;
|
timeToLive: TtlType;
|
||||||
@@ -185,13 +201,31 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
|||||||
userContext.apiType === "Mongo" ? (
|
userContext.apiType === "Mongo" ? (
|
||||||
<MessageBar
|
<MessageBar
|
||||||
messageBarIconProps={{ iconName: "InfoSolid", className: "messageBarInfoIcon" }}
|
messageBarIconProps={{ iconName: "InfoSolid", className: "messageBarInfoIcon" }}
|
||||||
styles={{ text: { fontSize: 14 } }}
|
styles={{
|
||||||
|
root: {
|
||||||
|
backgroundColor: "var(--colorNeutralBackground1)",
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
color: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={{ color: "var(--colorNeutralForeground1)" }}>
|
||||||
|
To enable time-to-live (TTL) for your collection/documents,{" "}
|
||||||
|
<Link
|
||||||
|
href="https://docs.microsoft.com/en-us/azure/cosmos-db/mongodb-time-to-live"
|
||||||
|
target="_blank"
|
||||||
|
style={{ color: "var(--colorBrandForeground1)" }}
|
||||||
>
|
>
|
||||||
To enable time-to-live (TTL) for your collection/documents,
|
|
||||||
<Link href="https://docs.microsoft.com/en-us/azure/cosmos-db/mongodb-time-to-live" target="_blank">
|
|
||||||
create a TTL index
|
create a TTL index
|
||||||
</Link>
|
</Link>
|
||||||
.
|
.
|
||||||
|
</Text>
|
||||||
</MessageBar>
|
</MessageBar>
|
||||||
) : (
|
) : (
|
||||||
<Stack {...titleAndInputStackProps}>
|
<Stack {...titleAndInputStackProps}>
|
||||||
@@ -319,23 +353,34 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
|||||||
private getPartitionKeyComponent = (): JSX.Element => (
|
private getPartitionKeyComponent = (): JSX.Element => (
|
||||||
<Stack {...titleAndInputStackProps}>
|
<Stack {...titleAndInputStackProps}>
|
||||||
{this.getPartitionKeyVisible() && (
|
{this.getPartitionKeyVisible() && (
|
||||||
|
<TooltipHost
|
||||||
|
content={`This ${this.partitionKeyName.toLowerCase()} is used to distribute data across multiple partitions for scalability. The value "${
|
||||||
|
this.partitionKeyValue
|
||||||
|
}" determines how documents are partitioned.`}
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
display: "block",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TextField
|
<TextField
|
||||||
label={this.partitionKeyName}
|
label={this.partitionKeyName}
|
||||||
disabled
|
disabled
|
||||||
styles={getTextFieldStyles(undefined, undefined)}
|
styles={getTextFieldStyles(undefined, undefined)}
|
||||||
defaultValue={this.partitionKeyValue}
|
defaultValue={this.partitionKeyValue}
|
||||||
/>
|
/>
|
||||||
|
</TooltipHost>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{userContext.apiType === "SQL" && this.isLargePartitionKeyEnabled() && (
|
{userContext.apiType === "SQL" && this.isLargePartitionKeyEnabled() && (
|
||||||
<Text>Large {this.partitionKeyName.toLowerCase()} has been enabled.</Text>
|
<Text className={classNames.hintText}>Large {this.partitionKeyName.toLowerCase()} has been enabled.</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{userContext.apiType === "SQL" &&
|
{userContext.apiType === "SQL" &&
|
||||||
(this.isHierarchicalPartitionedContainer() ? (
|
(this.isHierarchicalPartitionedContainer() ? (
|
||||||
<Text>Hierarchically partitioned container.</Text>
|
<Text className={classNames.hintText}>Hierarchically partitioned container.</Text>
|
||||||
) : (
|
) : (
|
||||||
<Text>Non-hierarchically partitioned container.</Text>
|
<Text className={classNames.hintText}>Non-hierarchically partitioned container.</Text>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack tokens={{ childrenGap: "m" }} styles={{ root: { width: "70%", maxWidth: 700 } }}>
|
<Stack tokens={{ childrenGap: "m" }} styles={{ root: { width: "70%", maxWidth: 700 } }}>
|
||||||
<Label>Throughput Buckets</Label>
|
<Label styles={{ root: { color: "var(--colorNeutralForeground1)" } }}>Throughput Buckets</Label>
|
||||||
<Stack>
|
<Stack>
|
||||||
{throughputBuckets?.map((bucket) => (
|
{throughputBuckets?.map((bucket) => (
|
||||||
<Stack key={bucket.id} horizontal tokens={{ childrenGap: 8 }} verticalAlign="center">
|
<Stack key={bucket.id} horizontal tokens={{ childrenGap: 8 }} verticalAlign="center">
|
||||||
@@ -77,7 +77,15 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
|
|||||||
onChange={(newValue) => handleBucketChange(bucket.id, newValue)}
|
onChange={(newValue) => handleBucketChange(bucket.id, newValue)}
|
||||||
showValue={false}
|
showValue={false}
|
||||||
label={`Bucket ${bucket.id}${bucket.id === 1 ? " (Data Explorer Query Bucket)" : ""}`}
|
label={`Bucket ${bucket.id}${bucket.id === 1 ? " (Data Explorer Query Bucket)" : ""}`}
|
||||||
styles={{ root: { flex: 2, maxWidth: 400 } }}
|
styles={{
|
||||||
|
root: { flex: 2, maxWidth: 400 },
|
||||||
|
titleLabel: {
|
||||||
|
color:
|
||||||
|
bucket.maxThroughputPercentage === 100
|
||||||
|
? "var(--colorNeutralForeground4)"
|
||||||
|
: "var(--colorNeutralForeground1)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
disabled={bucket.maxThroughputPercentage === 100}
|
disabled={bucket.maxThroughputPercentage === 100}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
@@ -95,7 +103,10 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
|
|||||||
offText="Inactive"
|
offText="Inactive"
|
||||||
checked={bucket.maxThroughputPercentage !== 100}
|
checked={bucket.maxThroughputPercentage !== 100}
|
||||||
onChange={(event, checked) => onToggle(bucket.id, checked)}
|
onChange={(event, checked) => onToggle(bucket.id, checked)}
|
||||||
styles={{ root: { marginBottom: 0 }, text: { fontSize: 12 } }}
|
styles={{
|
||||||
|
root: { marginBottom: 0 },
|
||||||
|
text: { fontSize: 12, color: "var(--colorNeutralForeground1)" },
|
||||||
|
}}
|
||||||
></Toggle>
|
></Toggle>
|
||||||
</Stack>
|
</Stack>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user