Introduce Tenant hierarchy in Azure resource tree for multi-tenant accounts (#23311)
@@ -136,15 +136,15 @@
|
||||
{
|
||||
"id": "microsoft",
|
||||
"icon": {
|
||||
"light": "./resources/light/microsoft_account_light.svg",
|
||||
"dark": "./resources/dark/microsoft_account_dark.svg"
|
||||
"light": "./resources/users.svg",
|
||||
"dark": "./resources/users.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "work_school",
|
||||
"icon": {
|
||||
"light": "./resources/light/work_school_account_light.svg",
|
||||
"dark": "./resources/dark/work_school_account_dark.svg"
|
||||
"light": "./resources/users.svg",
|
||||
"dark": "./resources/users.svg"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -174,6 +174,11 @@
|
||||
"title": "%azure.resource.refresh.title%",
|
||||
"icon": "$(refresh)"
|
||||
},
|
||||
{
|
||||
"command": "azure.resource.selecttenants",
|
||||
"title": "%azure.resource.selecttenants.title%",
|
||||
"icon": "$(filter)"
|
||||
},
|
||||
{
|
||||
"command": "azure.resource.selectsubscriptions",
|
||||
"title": "%azure.resource.selectsubscriptions.title%",
|
||||
@@ -237,6 +242,10 @@
|
||||
"command": "azure.resource.selectsubscriptions",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "azure.resource.selecttenants",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "azure.resource.azureview.refresh",
|
||||
"when": "false"
|
||||
@@ -267,24 +276,44 @@
|
||||
}
|
||||
],
|
||||
"view/item/context": [
|
||||
{
|
||||
"command": "azure.resource.selecttenants",
|
||||
"when": "viewItem == azure.resource.itemType.multipleTenantAccount",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "azure.resource.selecttenants",
|
||||
"when": "viewItem == azure.resource.itemType.multipleTenantAccount",
|
||||
"group": "azurecore"
|
||||
},
|
||||
{
|
||||
"command": "azure.resource.selectsubscriptions",
|
||||
"when": "viewItem == azure.resource.itemType.account",
|
||||
"when": "viewItem == azure.resource.itemType.singleTenantAccount",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "azure.resource.selectsubscriptions",
|
||||
"when": "viewItem == azure.resource.itemType.account",
|
||||
"when": "viewItem == azure.resource.itemType.singleTenantAccount",
|
||||
"group": "azurecore"
|
||||
},
|
||||
{
|
||||
"command": "azure.resource.selectsubscriptions",
|
||||
"when": "viewItem == azure.resource.itemType.tenant",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "azure.resource.selectsubscriptions",
|
||||
"when": "viewItem == azure.resource.itemType.tenant",
|
||||
"group": "azurecore"
|
||||
},
|
||||
{
|
||||
"command": "azure.resource.azureview.refresh",
|
||||
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer|synapseSqlPoolContainer|synapseWorkspaceContainer)$/",
|
||||
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:singleTenantAccount|multipleTenantAccount|subscription|tenant|databaseContainer|databaseServerContainer|synapseSqlPoolContainer|synapseWorkspaceContainer)$/",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "azure.resource.azureview.refresh",
|
||||
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer|synapseSqlPoolContainer|synapseWorkspaceContainer)$/",
|
||||
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:singleTenantAccount|multipleTenantAccount|subscription|tenant|databaseContainer|databaseServerContainer|synapseSqlPoolContainer|synapseWorkspaceContainer)$/",
|
||||
"group": "azurecore"
|
||||
},
|
||||
{
|
||||
@@ -299,7 +328,7 @@
|
||||
},
|
||||
{
|
||||
"command": "azure.resource.startterminal",
|
||||
"when": "viewItem == azure.resource.itemType.account",
|
||||
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:singleTenantAccount|multipleTenantAccount)$/",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
@@ -314,19 +343,29 @@
|
||||
},
|
||||
{
|
||||
"command": "azure.resource.startterminal",
|
||||
"when": "viewItem == azure.resource.itemType.account",
|
||||
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:singleTenantAccount|multipleTenantAccount)$/",
|
||||
"group": "azurecore"
|
||||
}
|
||||
],
|
||||
"connectionDialog/browseTree": [
|
||||
{
|
||||
"command": "azure.resource.selectsubscriptions",
|
||||
"when": "contextValue == azure.resource.itemType.account",
|
||||
"when": "contextValue == azure.resource.itemType.tenant",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "azure.resource.selecttenants",
|
||||
"when": "contextValue == azure.resource.itemType.multipleTenantAccount",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "azure.resource.selectsubscriptions",
|
||||
"when": "contextValue == azure.resource.itemType.singleTenantAccount",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "azure.resource.connectiondialog.refresh",
|
||||
"when": "contextValue == azure.resource.itemType.account",
|
||||
"when": "contextValue =~ /^azure\\.resource\\.itemType\\.(?:singleTenantAccount|multipleTenantAccount|tenant)$/",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"azure.resource.refresh.title": "Refresh",
|
||||
"azure.resource.signin.title": "Azure: Sign In",
|
||||
"azure.resource.selectsubscriptions.title": "Select Subscriptions",
|
||||
"azure.resource.selecttenants.title": "Select Tenants",
|
||||
"azure.resource.startterminal.title": "Start Cloud Shell",
|
||||
"azure.resource.connectsqlserver.title": "Connect",
|
||||
"azure.resource.connectsqldb.title": "Add to Servers",
|
||||
|
||||
26
extensions/azurecore/resources/azureSynapseAnalytics.svg
Normal file
@@ -0,0 +1,26 @@
|
||||
<svg id="f2f5701e-cb3b-4d6f-b407-5866ec5b7784" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||
<defs>
|
||||
<linearGradient id="a891901b-79ae-490a-8568-9c4334417d35" x1="9" y1="5.38" x2="9" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.199" stop-color="#005ba1" />
|
||||
<stop offset="1" stop-color="#0078d4" />
|
||||
</linearGradient>
|
||||
<linearGradient id="bbdaa009-2281-4da8-9e89-6f41689e91a7" x1="9" y1="12.713" x2="9" y2="5.287" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#198ab3" />
|
||||
<stop offset="0.172" stop-color="#32bedd" />
|
||||
<stop offset="0.5" stop-color="#50e6ff" />
|
||||
<stop offset="0.5" stop-color="#4fe4fd" />
|
||||
<stop offset="0.5" stop-color="#4bddf8" />
|
||||
<stop offset="0.5" stop-color="#44d2ee" />
|
||||
<stop offset="0.5" stop-color="#3ac1e0" />
|
||||
<stop offset="0.5" stop-color="#2dabce" />
|
||||
<stop offset="0.5" stop-color="#1d90b8" />
|
||||
<stop offset="0.5" stop-color="#198ab3" />
|
||||
<stop offset="0.662" stop-color="#32bedd" />
|
||||
<stop offset="0.975" stop-color="#50e6ff" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M9,0,1.15,4.49v8.97L9,18l7.85-4.49v-9Zm6.4,12.57L9,16.27,2.6,12.609V5.38L9,1.68l6.4,3.71Z" fill="#0078d4" />
|
||||
<polygon points="9 0 9 0 1.15 4.49 2.6 5.38 9 1.68 9 1.68 15.4 5.38 16.85 4.49 9 0" fill="url(#a891901b-79ae-490a-8568-9c4334417d35)" />
|
||||
<path d="M12.74,10.475a.73.73,0,0,0-.323-.286A5.835,5.835,0,0,0,7.939,6.843L14.416,3.1,12.91,2.236,5.534,6.5A.75.75,0,0,0,5.91,7.9.684.684,0,0,0,6,7.877l.125.523a4.319,4.319,0,0,1,4.837,2.238L3.613,14.885l1.5.866L12.466,11.5a.729.729,0,0,0,.242-.236l.075-.018c-.007-.029-.018-.055-.025-.084A.735.735,0,0,0,12.74,10.475Z" fill="#50e6ff" />
|
||||
<path d="M12.091,9.013a1.85,1.85,0,1,0,1.85,1.85A1.85,1.85,0,0,0,12.091,9.013ZM5.909,5.267a1.85,1.85,0,1,0,1.85,1.85A1.85,1.85,0,0,0,5.909,5.267Z" fill="url(#bbdaa009-2281-4da8-9e89-6f41689e91a7)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
22
extensions/azurecore/resources/cosmosDb.svg
Normal file
@@ -0,0 +1,22 @@
|
||||
<svg id="b089cfca-0de1-451c-a1ca-6680ea50cb4f" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||
<defs>
|
||||
<radialGradient id="b25d0836-964a-4c84-8c20-855f66e8345e" cx="-105.006" cy="-10.409" r="5.954" gradientTransform="translate(117.739 19.644) scale(1.036 1.027)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.183" stop-color="#5ea0ef" />
|
||||
<stop offset="1" stop-color="#0078d4" />
|
||||
</radialGradient>
|
||||
<clipPath id="b36c7f5d-2ef1-4760-8a25-eeb9661f4e47">
|
||||
<path d="M14.969,7.53A6.137,6.137,0,1,1,7.574,2.987,6.137,6.137,0,0,1,14.969,7.53Z" fill="none" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<title>Icon-databases-121</title>
|
||||
<path d="M2.954,5.266a.175.175,0,0,1-.176-.176h0A2.012,2.012,0,0,0,.769,3.081a.176.176,0,0,1-.176-.175h0a.176.176,0,0,1,.176-.176A2.012,2.012,0,0,0,2.778.72.175.175,0,0,1,2.954.544h0A.175.175,0,0,1,3.13.72h0A2.012,2.012,0,0,0,5.139,2.729a.175.175,0,0,1,.176.176h0a.175.175,0,0,1-.176.176h0A2.011,2.011,0,0,0,3.13,5.09.177.177,0,0,1,2.954,5.266Z" fill="#50e6ff" />
|
||||
<path d="M15.611,17.456a.141.141,0,0,1-.141-.141h0a1.609,1.609,0,0,0-1.607-1.607.141.141,0,0,1-.141-.14h0a.141.141,0,0,1,.141-.141h0a1.608,1.608,0,0,0,1.607-1.607.141.141,0,0,1,.141-.141h0a.141.141,0,0,1,.141.141h0a1.608,1.608,0,0,0,1.607,1.607.141.141,0,1,1,0,.282h0a1.609,1.609,0,0,0-1.607,1.607A.141.141,0,0,1,15.611,17.456Z" fill="#50e6ff" />
|
||||
<g>
|
||||
<path d="M14.969,7.53A6.137,6.137,0,1,1,7.574,2.987,6.137,6.137,0,0,1,14.969,7.53Z" fill="url(#b25d0836-964a-4c84-8c20-855f66e8345e)" />
|
||||
<g clip-path="url(#b36c7f5d-2ef1-4760-8a25-eeb9661f4e47)">
|
||||
<path d="M5.709,13.115A1.638,1.638,0,1,0,5.714,9.84,1.307,1.307,0,0,0,5.721,9.7,1.651,1.651,0,0,0,4.06,8.064H2.832a6.251,6.251,0,0,0,1.595,5.051Z" fill="#f2f2f2" />
|
||||
<path d="M15.045,7.815c0-.015,0-.03-.007-.044a5.978,5.978,0,0,0-1.406-2.88,1.825,1.825,0,0,0-.289-.09,1.806,1.806,0,0,0-2.3,1.663,2,2,0,0,0-.2-.013,1.737,1.737,0,0,0-.581,3.374,1.451,1.451,0,0,0,.541.1h2.03A13.453,13.453,0,0,0,15.045,7.815Z" fill="#f2f2f2" />
|
||||
</g>
|
||||
</g>
|
||||
<path d="M17.191,3.832c-.629-1.047-2.1-1.455-4.155-1.149a14.606,14.606,0,0,0-2.082.452,6.456,6.456,0,0,1,1.528.767c.241-.053.483-.116.715-.151A7.49,7.49,0,0,1,14.3,3.662a2.188,2.188,0,0,1,1.959.725h0c.383.638.06,1.729-.886,3a16.723,16.723,0,0,1-4.749,4.051A16.758,16.758,0,0,1,4.8,13.7c-1.564.234-2.682,0-3.065-.636s-.06-1.73.886-2.995c.117-.157.146-.234.279-.392a6.252,6.252,0,0,1,.026-1.63A11.552,11.552,0,0,0,1.756,9.419C.517,11.076.181,12.566.809,13.613a3.165,3.165,0,0,0,2.9,1.249,8.434,8.434,0,0,0,1.251-.1,17.855,17.855,0,0,0,6.219-2.4,17.808,17.808,0,0,0,5.061-4.332C17.483,6.369,17.819,4.88,17.191,3.832Z" fill="#50e6ff" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
32
extensions/azurecore/resources/dataExplorerClusterDb.svg
Normal file
@@ -0,0 +1,32 @@
|
||||
<svg id="b6c98345-0c39-4f6f-b933-5283deff5690" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||
<defs>
|
||||
<linearGradient id="ea6f59fb-306a-4655-8ef5-c9678451765c" x1="1.3" y1="8.6" x2="17.5" y2="8.6" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.1" stop-color="#54aef0" />
|
||||
<stop offset="1" stop-color="#1988d9" />
|
||||
</linearGradient>
|
||||
<linearGradient id="a331b263-1cab-4091-811f-6e285c35c115" x1="0.5" y1="13.7" x2="8.11" y2="13.7" gradientTransform="translate(10.95 0.97) rotate(45)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#b3b2b3" />
|
||||
<stop offset="1" stop-color="#979797" />
|
||||
</linearGradient>
|
||||
<linearGradient id="ef7d2642-0773-456f-ac58-3a1abd750919" x1="0.5" y1="10.08" x2="5.72" y2="10.08" gradientTransform="translate(8.04 0.75) rotate(45)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#b3b2b3" />
|
||||
<stop offset="1" stop-color="#979797" />
|
||||
</linearGradient>
|
||||
<linearGradient id="a156353f-4d63-4962-9d1c-839eceb7590b" x1="5.28" y1="14.89" x2="10.49" y2="14.89" gradientTransform="translate(12.84 -1.21) rotate(45)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#b3b2b3" />
|
||||
<stop offset="1" stop-color="#979797" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<title>Icon-analytics-145</title>
|
||||
<g>
|
||||
<path d="M1.47,1.47,16.53,16.53a.57.57,0,0,0,1-.4V1.07A.57.57,0,0,0,16.93.5H1.87A.57.57,0,0,0,1.47,1.47Z" fill="url(#ea6f59fb-306a-4655-8ef5-c9678451765c)" />
|
||||
<path id="f582129e-1fd7-440c-a5c4-799b0a032e7c" d="M5.53,5.53l6.94,6.94,5-5V1.07A.57.57,0,0,0,16.93.5H10.56Z" fill="#50e6ff" />
|
||||
<rect id="b55df55b-2ac6-4f92-ba84-9d8becd854da" x="-0.38" y="12.85" width="9.37" height="1.7" rx="0.27" transform="translate(-8.43 7.06) rotate(-45)" fill="url(#a331b263-1cab-4091-811f-6e285c35c115)" />
|
||||
<rect id="a1d88672-156d-4d85-8ddb-d3d546338b0e" x="0.12" y="9.23" width="5.99" height="1.7" rx="0.27" transform="translate(-6.22 5.15) rotate(-45)" fill="url(#ef7d2642-0773-456f-ac58-3a1abd750919)" />
|
||||
<rect id="a50d2abb-ca8f-44bb-a92d-5a79f17e98d0" x="4.89" y="14.04" width="5.99" height="1.7" rx="0.27" transform="translate(-8.22 9.93) rotate(-45)" fill="url(#a156353f-4d63-4962-9d1c-839eceb7590b)" />
|
||||
<rect id="abef2dbd-e94f-4bb4-9503-d2b3cf2041a9" x="9.19" y="4.75" width="1.7" height="1.7" rx="0.38" transform="translate(-1.02 8.74) rotate(-45)" fill="#fff" />
|
||||
<rect id="a157044f-57ca-43a1-b77d-760bafa34854" x="11.59" y="2.35" width="1.7" height="1.7" rx="0.38" transform="translate(1.38 9.73) rotate(-45)" fill="#fff" />
|
||||
<rect id="b637d7ff-a1d5-4533-b34f-12a87620ff68" x="11.59" y="7.15" width="1.7" height="1.7" rx="0.38" transform="translate(-2.01 11.14) rotate(-45)" fill="#fff" />
|
||||
<path id="f613147f-9647-4c5d-a52a-663abb19234f" d="M13.91,5.33l.66-.66a.38.38,0,0,1,.54,0l.66.66a.38.38,0,0,1,0,.54l-.66.66a.38.38,0,0,1-.54,0l-.66-.66a.38.38,0,0,1,0-.54" fill="#fff" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
21
extensions/azurecore/resources/logAnalyticsWorkspaces.svg
Normal file
@@ -0,0 +1,21 @@
|
||||
<svg id="b388e944-5509-49c9-84d7-f38bffa995b2" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||
<defs>
|
||||
<linearGradient id="a997e203-1d7d-4cb9-b131-5dc44e029b46" x1="5.23" y1="17.45" x2="5.23" y2="8.09" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#1988d9" />
|
||||
<stop offset="0.9" stop-color="#54aef0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="e122e9d8-aee5-4f1a-a60e-b1eae355daa5" x1="11.5" y1="12.45" x2="11.5" y2="0.55" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#198ab3" />
|
||||
<stop offset="0.01" stop-color="#198bb4" />
|
||||
<stop offset="0.44" stop-color="#27b2d7" />
|
||||
<stop offset="0.79" stop-color="#2fcbed" />
|
||||
<stop offset="1" stop-color="#32d4f5" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<title>Icon-manage-307</title>
|
||||
<path d="M5.59,13.13H9.91v4.32H5.59ZM1.15,17.45H4.87V13.13H.55v3.72A.6.6,0,0,0,1.15,17.45Zm-.6-5H4.87V8.09H.55Z" fill="url(#a997e203-1d7d-4cb9-b131-5dc44e029b46)" />
|
||||
<path d="M11.5.55a5.89,5.89,0,0,1,5.95,6,5.89,5.89,0,0,1-5.95,5.95H5.55V6.5A5.89,5.89,0,0,1,11.5.55Z" fill-rule="evenodd" fill="url(#e122e9d8-aee5-4f1a-a60e-b1eae355daa5)" />
|
||||
<rect x="7.59" y="5.65" width="1.36" height="4.08" fill="#fff" />
|
||||
<rect x="10.31" y="7.01" width="1.36" height="2.72" fill="#fff" />
|
||||
<rect x="13.03" y="4.29" width="1.36" height="5.44" fill="#fff" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
20
extensions/azurecore/resources/mysqlDatabase.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<svg id="e05c9575-4c38-4bcd-90eb-276caf26e3d0" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||
<defs>
|
||||
<linearGradient id="e291aba6-8038-4db1-a04d-c7be74f5a3e6" x1="2.59" y1="10.16" x2="15.41" y2="10.16" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#005ba1" />
|
||||
<stop offset="0.07" stop-color="#0060a9" />
|
||||
<stop offset="0.36" stop-color="#0071c8" />
|
||||
<stop offset="0.52" stop-color="#0078d4" />
|
||||
<stop offset="0.64" stop-color="#0074cd" />
|
||||
<stop offset="0.82" stop-color="#006abb" />
|
||||
<stop offset="1" stop-color="#005ba1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<title>Icon-databases-122</title>
|
||||
<path d="M9,5.14c-3.54,0-6.41-1-6.41-2.32V15.18c0,1.27,2.82,2.3,6.32,2.32H9c3.54,0,6.41-1,6.41-2.32V2.82C15.41,4.11,12.54,5.14,9,5.14Z" fill="url(#e291aba6-8038-4db1-a04d-c7be74f5a3e6)" />
|
||||
<path d="M15.41,2.82c0,1.29-2.87,2.32-6.41,2.32s-6.41-1-6.41-2.32S5.46.5,9,.5s6.41,1,6.41,2.32" fill="#e8e8e8" />
|
||||
<path d="M13.92,2.63c0,.82-2.21,1.48-4.92,1.48S4.08,3.45,4.08,2.63,6.29,1.16,9,1.16s4.92.66,4.92,1.47" fill="#50e6ff" />
|
||||
<path d="M9,3a11.55,11.55,0,0,0-3.89.57A11.42,11.42,0,0,0,9,4.11a11.15,11.15,0,0,0,3.89-.58A11.84,11.84,0,0,0,9,3Z" fill="#198ab3" />
|
||||
<path d="M12.64,9v1.63h-1a.39.39,0,0,1-.29-.14V9H10v1.78a.92.92,0,0,0,1,.89h1.49l.26-.13s-.11.41-.26.43H10.11v1h2.66A1.21,1.21,0,0,0,14,11.7V9Z" fill="#f2f2f2" />
|
||||
<path d="M9.53,9s0,0,0,0V8.51a.7.7,0,0,0-.48-.77,1.74,1.74,0,0,0-.5-.08.94.94,0,0,0-.91.58l-.78,1.9-1-1.9A.93.93,0,0,0,5,7.66a1.44,1.44,0,0,0-.51.09c-.35.11-.43.34-.43.73v3.31H5.23V9.56l.63,1.57a1.08,1.08,0,0,0,1,.66c.44,0,.62-.26.8-.66l.67-1.51v2.15H9.51V9h0Z" fill="#f2f2f2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
28
extensions/azurecore/resources/sqlPools.svg
Normal file
@@ -0,0 +1,28 @@
|
||||
<svg id="b2f464cb-b92f-4bc9-9d8a-b83d938321a8" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||
<defs>
|
||||
<linearGradient id="fbb0c6d7-8dd3-4464-9217-32033b9dbe40" x1="7.37" y1="0.5" x2="7.37" y2="13.23" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#b3b2b3" />
|
||||
<stop offset="0.38" stop-color="#afaeaf" />
|
||||
<stop offset="0.76" stop-color="#a2a2a2" />
|
||||
<stop offset="1" stop-color="#979797" />
|
||||
</linearGradient>
|
||||
<linearGradient id="f4df9409-8db7-49ba-98de-c39ce5949175" x1="8.96" y1="13.29" x2="17.32" y2="13.29" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#005ba1" />
|
||||
<stop offset="0.07" stop-color="#0060a9" />
|
||||
<stop offset="0.36" stop-color="#0071c8" />
|
||||
<stop offset="0.52" stop-color="#0078d4" />
|
||||
<stop offset="0.64" stop-color="#0074cd" />
|
||||
<stop offset="0.82" stop-color="#006abb" />
|
||||
<stop offset="1" stop-color="#005ba1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<title>Icon-databases-125</title>
|
||||
<g>
|
||||
<polygon points="7.37 0.5 0.68 4.38 0.68 5.34 1.91 5.34 1.91 12.62 3.12 12.62 3.12 5.34 11.62 5.34 11.62 13.23 12.83 13.23 12.83 5.34 14.06 5.34 14.06 4.38 7.37 0.5" fill="url(#fbb0c6d7-8dd3-4464-9217-32033b9dbe40)" />
|
||||
<path d="M4.33,12.62H6.76V10.2H4.33Zm3.64,0H10.4V10.2H8ZM4.33,9H6.76V6.55H4.33ZM8,6.55V9H10.4V6.55Z" fill="#50e6ff" />
|
||||
<path d="M13.14,10.41C10.83,10.41,9,9.81,9,9.08v7.09c0,.73,1.84,1.32,4.12,1.33h.06c2.31,0,4.18-.6,4.18-1.33V9.08C17.32,9.81,15.45,10.41,13.14,10.41Z" fill="url(#f4df9409-8db7-49ba-98de-c39ce5949175)" />
|
||||
<path d="M17.32,9.08c0,.73-1.87,1.33-4.18,1.33S9,9.81,9,9.08s1.87-1.33,4.18-1.33,4.18.59,4.18,1.33" fill="#e8e8e8" />
|
||||
<path d="M16.35,9c0,.47-1.44.85-3.21.85S9.94,9.44,9.94,9s1.43-.85,3.2-.85,3.21.38,3.21.85" fill="#50e6ff" />
|
||||
<path d="M13.14,9.17a8.59,8.59,0,0,0-2.54.32,8.5,8.5,0,0,0,2.54.33,8.5,8.5,0,0,0,2.54-.33A8.59,8.59,0,0,0,13.14,9.17Z" fill="#32bedd" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
15
extensions/azurecore/resources/subscriptions.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg id="bc18bade-5481-447e-a959-659d72346474" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||
<defs>
|
||||
<radialGradient id="a445c717-9d75-44c7-ba6b-0d8f2383e560" cx="-36.63" cy="17.12" r="11.18" gradientTransform="translate(41.88 -7.4) scale(0.94 0.94)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.27" stop-color="#ffd70f" />
|
||||
<stop offset="0.49" stop-color="#ffcb12" />
|
||||
<stop offset="0.88" stop-color="#feac19" />
|
||||
<stop offset="1" stop-color="#fea11b" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<title>Icon-general-2</title>
|
||||
<path id="e3d1e58c-f78e-4fb5-9857-0c9331da9979" d="M13.56,7.19a2.07,2.07,0,0,0,0-2.93h0L10,.69a2.06,2.06,0,0,0-2.92,0h0L3.52,4.26a2.09,2.09,0,0,0,0,2.93l3,3a.61.61,0,0,1,.17.41v5.52a.7.7,0,0,0,.2.5l1.35,1.35a.45.45,0,0,0,.66,0l1.31-1.31h0l.77-.77a.26.26,0,0,0,0-.38l-.55-.56a.29.29,0,0,1,0-.42l.55-.56a.26.26,0,0,0,0-.38L10.4,13a.28.28,0,0,1,0-.41L11,12a.26.26,0,0,0,0-.38l-.77-.78v-.28Zm-5-5.64A1.18,1.18,0,1,1,7.37,2.73,1.17,1.17,0,0,1,8.54,1.55Z" fill="url(#a445c717-9d75-44c7-ba6b-0d8f2383e560)" />
|
||||
<path id="a21a8f7a-61cc-4035-8449-e5c8fe4d4d5e" d="M7.62,16.21h0A.25.25,0,0,0,8,16V11.55a.27.27,0,0,0-.11-.22h0a.25.25,0,0,0-.39.22V16A.27.27,0,0,0,7.62,16.21Z" fill="#ff9300" opacity="0.75" />
|
||||
<rect id="ecd3189c-fb1e-4a0e-a2b6-ba2f11dda484" x="5.69" y="5.45" width="5.86" height="0.69" rx="0.32" fill="#ff9300" opacity="0.75" />
|
||||
<rect id="a1949a3c-4818-4bd1-b236-0d970b92fc62" x="5.69" y="6.57" width="5.86" height="0.69" rx="0.32" fill="#ff9300" opacity="0.75" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
23
extensions/azurecore/resources/tenant.svg
Normal file
@@ -0,0 +1,23 @@
|
||||
<svg id="bdb56329-4717-4410-aa13-4505ecaa4e46" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||
<defs>
|
||||
<linearGradient id="ba2610c3-a45a-4e7e-a0c0-285cfd7e005d" x1="13.25" y1="13.02" x2="8.62" y2="4.25" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#1988d9" />
|
||||
<stop offset="0.9" stop-color="#54aef0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="bd8f618b-4f2f-4cb7-aff0-2fd2d211326d" x1="11.26" y1="10.47" x2="14.46" y2="15.99" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.1" stop-color="#54aef0" />
|
||||
<stop offset="0.29" stop-color="#4fabee" />
|
||||
<stop offset="0.51" stop-color="#41a2e9" />
|
||||
<stop offset="0.74" stop-color="#2a93e0" />
|
||||
<stop offset="0.88" stop-color="#1988d9" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<title>Icon-identity-221</title>
|
||||
<polygon points="1.01 10.19 8.93 15.33 16.99 10.17 18 11.35 8.93 17.19 0 11.35 1.01 10.19" fill="#50e6ff" />
|
||||
<polygon points="1.61 9.53 8.93 0.81 16.4 9.54 8.93 14.26 1.61 9.53" fill="#fff" />
|
||||
<polygon points="8.93 0.81 8.93 14.26 1.61 9.53 8.93 0.81" fill="#50e6ff" />
|
||||
<polygon points="8.93 0.81 8.93 14.26 16.4 9.54 8.93 0.81" fill="url(#ba2610c3-a45a-4e7e-a0c0-285cfd7e005d)" />
|
||||
<polygon points="8.93 7.76 16.4 9.54 8.93 14.26 8.93 7.76" fill="#53b1e0" />
|
||||
<polygon points="8.93 14.26 1.61 9.53 8.93 7.76 8.93 14.26" fill="#9cebff" />
|
||||
<polygon points="8.93 17.19 18 11.35 16.99 10.17 8.93 15.33 8.93 17.19" fill="url(#bd8f618b-4f2f-4cb7-aff0-2fd2d211326d)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
16
extensions/azurecore/resources/users.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg id="e24671f6-f501-4952-a2db-8b0b1d329c17" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="-1 0 21 21">
|
||||
<defs>
|
||||
<linearGradient id="be92901b-ec33-4c65-adf1-9b0eed06d677" x1="9" y1="6.88" x2="9" y2="20.45" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.22" stop-color="#32d4f5" />
|
||||
<stop offset="1" stop-color="#198ab3" />
|
||||
</linearGradient>
|
||||
<linearGradient id="b46fc246-25d8-4398-8779-1042e8cacae7" x1="8.61" y1="-0.4" x2="9.6" y2="11.92" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.22" stop-color="#32d4f5" />
|
||||
<stop offset="1" stop-color="#198ab3" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<title>Icon-identity-230</title>
|
||||
<path d="M15.72,18a1.45,1.45,0,0,0,1.45-1.45.47.47,0,0,0,0-.17C16.59,11.81,14,8.09,9,8.09S1.34,11.24.83,16.39A1.46,1.46,0,0,0,2.14,18H15.72Z" fill="url(#be92901b-ec33-4c65-adf1-9b0eed06d677)" />
|
||||
<path d="M9,9.17a4.59,4.59,0,0,1-2.48-.73L9,14.86l2.44-6.38A4.53,4.53,0,0,1,9,9.17Z" fill="#fff" opacity="0.8" />
|
||||
<circle cx="9.01" cy="4.58" r="4.58" fill="url(#b46fc246-25d8-4398-8779-1042e8cacae7)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -194,7 +194,7 @@ export abstract class AzureAuth implements vscode.Disposable {
|
||||
public async hydrateAccount(token: Token | AccessToken, tokenClaims: TokenClaims): Promise<AzureAccount> {
|
||||
let account: azdata.Account;
|
||||
if (this._authLibrary === Constants.AuthLibrary.MSAL) {
|
||||
const tenants = await this.getTenantsMsal(token.token);
|
||||
const tenants = await this.getTenantsMsal(token.token, tokenClaims);
|
||||
account = this.createAccount(tokenClaims, token.key, tenants);
|
||||
} else { // fallback to ADAL as default
|
||||
const tenants = await this.getTenantsAdal({ ...token });
|
||||
@@ -471,7 +471,7 @@ export abstract class AzureAuth implements vscode.Disposable {
|
||||
return result;
|
||||
}
|
||||
|
||||
public async getTenantsMsal(token: string): Promise<Tenant[]> {
|
||||
public async getTenantsMsal(token: string, tokenClaims: TokenClaims): Promise<Tenant[]> {
|
||||
const tenantUri = url.resolve(this.metadata.settings.armResource.endpoint, 'tenants?api-version=2019-11-01');
|
||||
try {
|
||||
Logger.verbose(`Fetching tenants with uri: ${tenantUri}`);
|
||||
@@ -499,7 +499,7 @@ export abstract class AzureAuth implements vscode.Disposable {
|
||||
return {
|
||||
id: tenantInfo.tenantId,
|
||||
displayName: tenantInfo.displayName ? tenantInfo.displayName : tenantInfo.tenantId,
|
||||
userId: token,
|
||||
userId: tokenClaims.oid,
|
||||
tenantCategory: tenantInfo.tenantCategory
|
||||
} as Tenant;
|
||||
});
|
||||
|
||||
@@ -12,12 +12,14 @@ import { AppContext } from '../appContext';
|
||||
import { TreeNode } from './treeNode';
|
||||
import { AzureResourceTreeProvider } from './tree/treeProvider';
|
||||
import { AzureResourceAccountTreeNode } from './tree/accountTreeNode';
|
||||
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService, IAzureTerminalService } from '../azureResource/interfaces';
|
||||
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService, IAzureTerminalService, IAzureResourceTenantFilterService } from '../azureResource/interfaces';
|
||||
import { AzureResourceServiceNames } from './constants';
|
||||
import { AzureAccount, Tenant, azureResource } from 'azurecore';
|
||||
import { FlatAccountTreeNode } from './tree/flatAccountTreeNode';
|
||||
import { FlatTenantTreeNode } from './tree/flatTenantTreeNode';
|
||||
import { ConnectionDialogTreeProvider } from './tree/connectionDialogTreeProvider';
|
||||
import { AzureResourceErrorMessageUtil, filterAccounts } from './utils';
|
||||
import { AzureResourceTenantTreeNode } from './tree/tenantTreeNode';
|
||||
import { FlatAccountTreeNode } from './tree/flatAccountTreeNode';
|
||||
|
||||
export function registerAzureResourceCommands(appContext: AppContext, azureViewTree: AzureResourceTreeProvider, connectionDialogTree: ConnectionDialogTreeProvider, authLibrary: string): void {
|
||||
const trees = [azureViewTree, connectionDialogTree];
|
||||
@@ -95,14 +97,21 @@ export function registerAzureResourceCommands(appContext: AppContext, azureViewT
|
||||
});
|
||||
|
||||
// Resource Tree commands
|
||||
|
||||
// Supports selecting subscriptions from single tenant account tree nodes or tenant tree node.
|
||||
vscode.commands.registerCommand('azure.resource.selectsubscriptions', async (node?: TreeNode) => {
|
||||
if (!(node instanceof AzureResourceAccountTreeNode) && !(node instanceof FlatAccountTreeNode)) {
|
||||
if (!(node instanceof AzureResourceAccountTreeNode) && !(node instanceof FlatAccountTreeNode)
|
||||
&& !(node instanceof AzureResourceTenantTreeNode) && !(node instanceof FlatTenantTreeNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const account = node.account;
|
||||
if (!account) {
|
||||
|
||||
// Select first tenant from single tenant accounts
|
||||
let tenant = node.account.properties.tenants[0];
|
||||
if (node instanceof AzureResourceTenantTreeNode || node instanceof FlatTenantTreeNode) {
|
||||
tenant = node.tenant;
|
||||
}
|
||||
if (!account || !tenant) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -112,7 +121,8 @@ export function registerAzureResourceCommands(appContext: AppContext, azureViewT
|
||||
let subscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||
if (subscriptions.length === 0) {
|
||||
try {
|
||||
subscriptions = await subscriptionService.getSubscriptions(account);
|
||||
let tenantIds = tenant ? [tenant.id] : account.properties.tenants.flatMap(t => t.id);
|
||||
subscriptions = await subscriptionService.getSubscriptions(account, tenantIds);
|
||||
} catch (error) {
|
||||
account.isStale = true;
|
||||
void vscode.window.showErrorMessage(AzureResourceErrorMessageUtil.getErrorMessage(error));
|
||||
@@ -120,7 +130,7 @@ export function registerAzureResourceCommands(appContext: AppContext, azureViewT
|
||||
}
|
||||
}
|
||||
|
||||
let selectedSubscriptions = await subscriptionFilterService.getSelectedSubscriptions(account);
|
||||
let selectedSubscriptions = await subscriptionFilterService.getSelectedSubscriptions(account, tenant);
|
||||
if (!selectedSubscriptions) {
|
||||
selectedSubscriptions = [];
|
||||
}
|
||||
@@ -152,7 +162,57 @@ export function registerAzureResourceCommands(appContext: AppContext, azureViewT
|
||||
}
|
||||
|
||||
selectedSubscriptions = selectedSubscriptionQuickPickItems.map((subscriptionItem) => subscriptionItem.subscription);
|
||||
await subscriptionFilterService.saveSelectedSubscriptions(account, selectedSubscriptions);
|
||||
await subscriptionFilterService.saveSelectedSubscriptions(account, tenant, selectedSubscriptions);
|
||||
}
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand('azure.resource.selecttenants', async (node?: TreeNode) => {
|
||||
if (!(node instanceof AzureResourceAccountTreeNode) && !(node instanceof FlatAccountTreeNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const account = node.account;
|
||||
if (!account) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tenantFilterService = appContext.getService<IAzureResourceTenantFilterService>(AzureResourceServiceNames.tenantFilterService);
|
||||
|
||||
let tenants = account.properties.tenants;
|
||||
|
||||
let selectedTenants = await tenantFilterService.getSelectedTenants(account);
|
||||
if (!selectedTenants) {
|
||||
selectedTenants = [];
|
||||
}
|
||||
|
||||
const selectedTenantIds: string[] = [];
|
||||
if (selectedTenants.length > 0) {
|
||||
selectedTenantIds.push(...selectedTenants.map((tenant) => tenant.id));
|
||||
} else {
|
||||
// ALL tenants are selected by default
|
||||
selectedTenantIds.push(...tenants.map((tenant) => tenant.id));
|
||||
}
|
||||
|
||||
interface AzureResourceTenantQuickPickItem extends vscode.QuickPickItem {
|
||||
tenant: Tenant;
|
||||
}
|
||||
|
||||
const tenantQuickPickItems: AzureResourceTenantQuickPickItem[] = tenants.map(tenant => {
|
||||
return {
|
||||
label: tenant.displayName,
|
||||
picked: selectedTenantIds.indexOf(tenant.id) !== -1,
|
||||
tenant: tenant
|
||||
};
|
||||
}).sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
const selectedtenantQuickPickItems = await vscode.window.showQuickPick(tenantQuickPickItems, { canPickMany: true });
|
||||
if (selectedtenantQuickPickItems && selectedtenantQuickPickItems.length > 0) {
|
||||
for (const tree of trees) {
|
||||
await tree.refresh(undefined, false);
|
||||
}
|
||||
|
||||
selectedTenants = selectedtenantQuickPickItems.map((item) => item.tenant);
|
||||
await tenantFilterService.saveSelectedTenants(account, selectedTenants);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -167,7 +227,8 @@ export function registerAzureResourceCommands(appContext: AppContext, azureViewT
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand('azure.resource.connectiondialog.refresh', async (node?: TreeNode) => {
|
||||
return connectionDialogTree.refresh(node, true);
|
||||
await connectionDialogTree.refresh(node, true); // clear cache first
|
||||
return connectionDialogTree.refresh(node, false);
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand('azure.resource.signin', async (node?: TreeNode) => {
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
|
||||
export enum AzureResourceItemType {
|
||||
account = 'azure.resource.itemType.account',
|
||||
singleTenantAccount = 'azure.resource.itemType.singleTenantAccount',
|
||||
multipleTenantAccount = 'azure.resource.itemType.multipleTenantAccount',
|
||||
subscription = 'azure.resource.itemType.subscription',
|
||||
tenant = 'azure.resource.itemType.tenant',
|
||||
databaseContainer = 'azure.resource.itemType.databaseContainer',
|
||||
database = 'azure.resource.itemType.database',
|
||||
databaseServerContainer = 'azure.resource.itemType.databaseServerContainer',
|
||||
@@ -32,6 +35,7 @@ export enum AzureResourceServiceNames {
|
||||
subscriptionService = 'AzureResourceSubscriptionService',
|
||||
subscriptionFilterService = 'AzureResourceSubscriptionFilterService',
|
||||
tenantService = 'AzureResourceTenantService',
|
||||
tenantFilterService = 'AzureResourceTenantFilterService',
|
||||
terminalService = 'AzureTerminalService',
|
||||
}
|
||||
|
||||
|
||||
@@ -135,9 +135,14 @@ export interface IAzureResourceSubscriptionService {
|
||||
getSubscriptions(account: AzureAccount, tenantIds?: string[] | undefined): Promise<azureResource.AzureResourceSubscription[]>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceTenantFilterService {
|
||||
getSelectedTenants(account: AzureAccount): Promise<Tenant[]>;
|
||||
saveSelectedTenants(account: AzureAccount, selectedTenants: Tenant[]): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceSubscriptionFilterService {
|
||||
getSelectedSubscriptions(account: AzureAccount): Promise<azureResource.AzureResourceSubscription[]>;
|
||||
saveSelectedSubscriptions(account: AzureAccount, selectedSubscriptions: azureResource.AzureResourceSubscription[]): Promise<void>;
|
||||
getSelectedSubscriptions(account: AzureAccount, tenant: Tenant): Promise<azureResource.AzureResourceSubscription[]>;
|
||||
saveSelectedSubscriptions(account: AzureAccount, tenant: Tenant, selectedSubscriptions: azureResource.AzureResourceSubscription[]): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IAzureTerminalService {
|
||||
|
||||
@@ -16,7 +16,7 @@ import { AzureAccount, azureResource } from 'azurecore';
|
||||
|
||||
export class AzureMonitorTreeDataProvider extends ResourceTreeDataProviderBase<GraphData, AzureMonitorGraphData> {
|
||||
private static readonly containerId = 'azure.resource.providers.AzureMonitorContainer';
|
||||
private static readonly containerLabel = localize('azure.resource.providers.AzureMonitorContainerLabel', "Log Analytics workspace");
|
||||
private static readonly containerLabel = localize('azure.resource.providers.AzureMonitorContainerLabel', "Log Analytics workspaces");
|
||||
|
||||
public constructor(
|
||||
databaseServerService: azureResource.IAzureResourceService,
|
||||
@@ -27,12 +27,9 @@ export class AzureMonitorTreeDataProvider extends ResourceTreeDataProviderBase<G
|
||||
|
||||
public getTreeItemForResource(databaseServer: azureResource.AzureResourceDatabaseServer, account: AzureAccount): TreeItem {
|
||||
return {
|
||||
id: `${AzureResourcePrefixes.logAnalytics}${account.key.accountId}${databaseServer.id ? databaseServer.id : databaseServer.name}`,
|
||||
id: `${AzureResourcePrefixes.logAnalytics}${account.key.accountId}${databaseServer.tenant}${databaseServer.id ? databaseServer.id : databaseServer.name}`,
|
||||
label: this.browseConnectionMode ? `${databaseServer.name} (${AzureMonitorTreeDataProvider.containerLabel}, ${databaseServer.subscription.name})` : databaseServer.name,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/azure_monitor_dark.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/azure_monitor_light.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/logAnalyticsWorkspaces.svg'),
|
||||
collapsibleState: this.browseConnectionMode ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.azureMonitor,
|
||||
payload: {
|
||||
@@ -63,10 +60,7 @@ export class AzureMonitorTreeDataProvider extends ResourceTreeDataProviderBase<G
|
||||
return [{
|
||||
id: AzureMonitorTreeDataProvider.containerId,
|
||||
label: AzureMonitorTreeDataProvider.containerLabel,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/logAnalyticsWorkspaces.svg'),
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseServerContainer
|
||||
}];
|
||||
|
||||
@@ -17,7 +17,7 @@ import * as azdata from 'azdata';
|
||||
|
||||
export class CosmosDbMongoTreeDataProvider extends ResourceTreeDataProviderBase<GraphData, DbServerGraphData> {
|
||||
private static readonly CONTAINER_ID = 'azure.resource.providers.databaseServer.treeDataProvider.cosmosDbMongoContainer';
|
||||
private static readonly CONTAINER_LABEL = localize('azure.resource.providers.databaseServer.treeDataProvider.cosmosDbMongoContainerLabel', "CosmosDB for Mongo");
|
||||
private static readonly CONTAINER_LABEL = localize('azure.resource.providers.databaseServer.treeDataProvider.cosmosDbMongoContainerLabel', "Azure CosmosDB for MongoDB");
|
||||
|
||||
public constructor(
|
||||
databaseServerService: azureResource.IAzureResourceService,
|
||||
@@ -28,12 +28,9 @@ export class CosmosDbMongoTreeDataProvider extends ResourceTreeDataProviderBase<
|
||||
|
||||
public getTreeItemForResource(databaseServer: AzureResourceMongoDatabaseServer, account: azdata.Account): azdata.TreeItem {
|
||||
return {
|
||||
id: `${AzureResourcePrefixes.cosmosdb}${account.key.accountId}${databaseServer.id ?? databaseServer.name}`,
|
||||
id: `${AzureResourcePrefixes.cosmosdb}${account.key.accountId}${databaseServer.tenant}${databaseServer.id ?? databaseServer.name}`,
|
||||
label: `${databaseServer.name} (CosmosDB Mongo API)`,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/cosmosdb_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/cosmosdb.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/cosmosDb.svg'),
|
||||
collapsibleState: TreeItemCollapsibleState.None,
|
||||
contextValue: AzureResourceItemType.cosmosDBMongoAccount,
|
||||
payload: {
|
||||
@@ -65,10 +62,7 @@ export class CosmosDbMongoTreeDataProvider extends ResourceTreeDataProviderBase<
|
||||
return [{
|
||||
id: CosmosDbMongoTreeDataProvider.CONTAINER_ID,
|
||||
label: CosmosDbMongoTreeDataProvider.CONTAINER_LABEL,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/cosmosDb.svg'),
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseServerContainer
|
||||
}];
|
||||
|
||||
@@ -17,7 +17,7 @@ import { AzureAccount, azureResource } from 'azurecore';
|
||||
export class AzureResourceDatabaseTreeDataProvider extends ResourceTreeDataProviderBase<DbServerGraphData, DatabaseGraphData> {
|
||||
|
||||
private static readonly containerId = 'azure.resource.providers.database.treeDataProvider.databaseContainer';
|
||||
private static readonly containerLabel = localize('azure.resource.providers.database.treeDataProvider.databaseContainerLabel', "SQL database");
|
||||
private static readonly containerLabel = localize('azure.resource.providers.database.treeDataProvider.databaseContainerLabel', "SQL databases");
|
||||
|
||||
public constructor(
|
||||
databaseService: azureResource.IAzureResourceService,
|
||||
@@ -28,12 +28,9 @@ export class AzureResourceDatabaseTreeDataProvider extends ResourceTreeDataProvi
|
||||
|
||||
public getTreeItemForResource(database: azureResource.AzureResourceDatabase, account: AzureAccount): TreeItem {
|
||||
return {
|
||||
id: `${AzureResourcePrefixes.databaseServer}${account.key.accountId}${database.serverFullName}.${AzureResourcePrefixes.database}${database.id ?? database.name}`,
|
||||
id: `${AzureResourcePrefixes.database}${account.key.accountId}${database.tenant}${database.serverFullName}.${AzureResourcePrefixes.database}${database.id ?? database.name}`,
|
||||
label: this.browseConnectionMode ? `${database.serverName}/${database.name} (${AzureResourceDatabaseTreeDataProvider.containerLabel}, ${database.subscription.name})` : `${database.name} (${database.serverName})`,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_database_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/sql_database.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/sqlDatabase.svg'),
|
||||
collapsibleState: this.browseConnectionMode ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.database,
|
||||
payload: {
|
||||
@@ -64,10 +61,7 @@ export class AzureResourceDatabaseTreeDataProvider extends ResourceTreeDataProvi
|
||||
return [{
|
||||
id: AzureResourceDatabaseTreeDataProvider.containerId,
|
||||
label: AzureResourceDatabaseTreeDataProvider.containerLabel,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/sqlDatabase.svg'),
|
||||
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseContainer
|
||||
}];
|
||||
|
||||
@@ -16,7 +16,7 @@ import { AzureAccount, azureResource } from 'azurecore';
|
||||
|
||||
export class AzureResourceDatabaseServerTreeDataProvider extends ResourceTreeDataProviderBase<GraphData, DbServerGraphData> {
|
||||
private static readonly containerId = 'azure.resource.providers.databaseServer.treeDataProvider.databaseServerContainer';
|
||||
private static readonly containerLabel = localize('azure.resource.providers.databaseServer.treeDataProvider.databaseServerContainerLabel', "SQL server");
|
||||
private static readonly containerLabel = localize('azure.resource.providers.databaseServer.treeDataProvider.databaseServerContainerLabel', "SQL servers");
|
||||
|
||||
public constructor(
|
||||
databaseServerService: azureResource.IAzureResourceService,
|
||||
@@ -27,12 +27,9 @@ export class AzureResourceDatabaseServerTreeDataProvider extends ResourceTreeDat
|
||||
|
||||
public getTreeItemForResource(databaseServer: azureResource.AzureResourceDatabaseServer, account: AzureAccount): TreeItem {
|
||||
return {
|
||||
id: `${AzureResourcePrefixes.databaseServer}${account.key.accountId}${databaseServer.id ?? databaseServer.name}`,
|
||||
id: `${AzureResourcePrefixes.databaseServer}${account.key.accountId}${databaseServer.tenant}${databaseServer.id ?? databaseServer.name}`,
|
||||
label: this.browseConnectionMode ? `${databaseServer.name} (${AzureResourceDatabaseServerTreeDataProvider.containerLabel}, ${databaseServer.subscription.name})` : databaseServer.name,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_server_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/sql_server.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/sqlServer.svg'),
|
||||
collapsibleState: this.browseConnectionMode ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseServer,
|
||||
payload: {
|
||||
@@ -63,10 +60,7 @@ export class AzureResourceDatabaseServerTreeDataProvider extends ResourceTreeDat
|
||||
return [{
|
||||
id: AzureResourceDatabaseServerTreeDataProvider.containerId,
|
||||
label: AzureResourceDatabaseServerTreeDataProvider.containerLabel,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/sqlServer.svg'),
|
||||
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseServerContainer
|
||||
}];
|
||||
|
||||
@@ -16,7 +16,7 @@ import { AzureAccount, azureResource } from 'azurecore';
|
||||
|
||||
export class KustoTreeDataProvider extends ResourceTreeDataProviderBase<GraphData, KustoGraphData> {
|
||||
private static readonly containerId = 'azure.resource.providers.KustoContainer';
|
||||
private static readonly containerLabel = localize('azure.resource.providers.KustoContainerLabel', "Azure Data Explorer Cluster");
|
||||
private static readonly containerLabel = localize('azure.resource.providers.KustoContainerLabel', "Azure Data Explorer Clusters");
|
||||
|
||||
public constructor(
|
||||
databaseServerService: azureResource.IAzureResourceService,
|
||||
@@ -27,12 +27,9 @@ export class KustoTreeDataProvider extends ResourceTreeDataProviderBase<GraphDat
|
||||
|
||||
public getTreeItemForResource(databaseServer: azureResource.AzureResourceDatabaseServer, account: AzureAccount): TreeItem {
|
||||
return {
|
||||
id: `${AzureResourcePrefixes.kusto}${account.key.accountId}${databaseServer.id ?? databaseServer.name}`,
|
||||
id: `${AzureResourcePrefixes.kusto}${account.key.accountId}${databaseServer.tenant}${databaseServer.id ?? databaseServer.name}`,
|
||||
label: this.browseConnectionMode ? `${databaseServer.name} (${KustoTreeDataProvider.containerLabel}, ${databaseServer.subscription.name})` : databaseServer.name,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/azureDE_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/azureDE.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/dataExplorerClusterDb.svg'),
|
||||
collapsibleState: this.browseConnectionMode ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.azureDataExplorer,
|
||||
payload: {
|
||||
@@ -63,10 +60,7 @@ export class KustoTreeDataProvider extends ResourceTreeDataProviderBase<GraphDat
|
||||
return [{
|
||||
id: KustoTreeDataProvider.containerId,
|
||||
label: KustoTreeDataProvider.containerLabel,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/dataExplorerClusterDb.svg'),
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseServerContainer
|
||||
}];
|
||||
|
||||
@@ -27,12 +27,9 @@ export class MysqlFlexibleServerTreeDataProvider extends ResourceTreeDataProvide
|
||||
|
||||
public getTreeItemForResource(databaseServer: azureResource.AzureResourceDatabaseServer, account: Account): TreeItem {
|
||||
return {
|
||||
id: `${AzureResourcePrefixes.mySqlFlexibleServer}${account.key.accountId}${databaseServer.id ?? databaseServer.name}`,
|
||||
id: `${AzureResourcePrefixes.mySqlFlexibleServer}${account.key.accountId}${databaseServer.tenant}${databaseServer.id ?? databaseServer.name}`,
|
||||
label: this.browseConnectionMode ? `${databaseServer.name} (${MysqlFlexibleServerTreeDataProvider.CONTAINER_LABEL}, ${databaseServer.subscription.name})` : databaseServer.name,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/mysql_server_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/mysql_server.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/mysqlDatabase.svg'),
|
||||
collapsibleState: this.browseConnectionMode ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseServer,
|
||||
payload: {
|
||||
@@ -64,10 +61,7 @@ export class MysqlFlexibleServerTreeDataProvider extends ResourceTreeDataProvide
|
||||
return [{
|
||||
id: MysqlFlexibleServerTreeDataProvider.CONTAINER_ID,
|
||||
label: MysqlFlexibleServerTreeDataProvider.CONTAINER_LABEL,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/mysqlDatabase.svg'),
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseServerContainer
|
||||
}];
|
||||
|
||||
@@ -17,7 +17,7 @@ import { GraphData, PostgresArcServerGraphData } from '../../interfaces';
|
||||
export class PostgresServerArcTreeDataProvider extends ResourceTreeDataProviderBase<GraphData, PostgresArcServerGraphData> {
|
||||
private static readonly containerId = 'azure.resource.providers.postgresArcServer.treeDataProvider.postgresServerContainer';
|
||||
// allow-any-unicode-next-line
|
||||
private static readonly containerLabel = localize('azure.resource.providers.postgresArcServer.treeDataProvider.postgresServerContainerLabel', "PostgreSQL Hyperscale – Azure Arc");
|
||||
private static readonly containerLabel = localize('azure.resource.providers.postgresArcServer.treeDataProvider.postgresServerContainerLabel', "PostgreSQL servers – Azure Arc");
|
||||
|
||||
public constructor(
|
||||
databaseServerService: azureResource.IAzureResourceService,
|
||||
@@ -28,12 +28,9 @@ export class PostgresServerArcTreeDataProvider extends ResourceTreeDataProviderB
|
||||
|
||||
public getTreeItemForResource(databaseServer: azureResource.AzureResourceDatabaseServer, account: AzureAccount): TreeItem {
|
||||
return {
|
||||
id: `${AzureResourcePrefixes.postgresServerArc}${account.key.accountId}${databaseServer.id ?? databaseServer.name}`,
|
||||
id: `${AzureResourcePrefixes.postgresServerArc}${account.key.accountId}${databaseServer.tenant}${databaseServer.id ?? databaseServer.name}`,
|
||||
label: this.browseConnectionMode ? `${databaseServer.name} (${PostgresServerArcTreeDataProvider.containerLabel}, ${databaseServer.subscription.name})` : databaseServer.name,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_server_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/sql_server.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/azureArcPostgresServer.svg'),
|
||||
collapsibleState: this.browseConnectionMode ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseServer,
|
||||
payload: {
|
||||
@@ -67,10 +64,7 @@ export class PostgresServerArcTreeDataProvider extends ResourceTreeDataProviderB
|
||||
return [{
|
||||
id: PostgresServerArcTreeDataProvider.containerId,
|
||||
label: PostgresServerArcTreeDataProvider.containerLabel,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/azureArcPostgresServer.svg'),
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseServerContainer
|
||||
}];
|
||||
|
||||
@@ -27,12 +27,9 @@ export class PostgresFlexibleServerTreeDataProvider extends ResourceTreeDataProv
|
||||
|
||||
public getTreeItemForResource(databaseServer: azureResource.AzureResourceDatabaseServer, account: AzureAccount): TreeItem {
|
||||
return {
|
||||
id: `${AzureResourcePrefixes.postgresFlexibleServer}${account.key.accountId}${databaseServer.id ?? databaseServer.name}`,
|
||||
id: `${AzureResourcePrefixes.postgresFlexibleServer}${account.key.accountId}${databaseServer.tenant}${databaseServer.id ?? databaseServer.name}`,
|
||||
label: this.browseConnectionMode ? `${databaseServer.name} (${PostgresFlexibleServerTreeDataProvider.containerLabel}, ${databaseServer.subscription.name})` : databaseServer.name,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_server_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/sql_server.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/postgresServer.svg'),
|
||||
collapsibleState: this.browseConnectionMode ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseServer,
|
||||
payload: {
|
||||
@@ -66,10 +63,7 @@ export class PostgresFlexibleServerTreeDataProvider extends ResourceTreeDataProv
|
||||
return [{
|
||||
id: PostgresFlexibleServerTreeDataProvider.containerId,
|
||||
label: PostgresFlexibleServerTreeDataProvider.containerLabel,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/postgresServer.svg'),
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseServerContainer
|
||||
}];
|
||||
|
||||
@@ -27,11 +27,11 @@ export class PostgresServerTreeDataProvider extends ResourceTreeDataProviderBase
|
||||
|
||||
public getTreeItemForResource(databaseServer: azureResource.AzureResourceDatabaseServer, account: AzureAccount): TreeItem {
|
||||
return {
|
||||
id: `${AzureResourcePrefixes.postgresServer}${account.key.accountId}${databaseServer.id ?? databaseServer.name}`,
|
||||
id: `${AzureResourcePrefixes.postgresServer}${account.key.accountId}${databaseServer.tenant}${databaseServer.id ?? databaseServer.name}`,
|
||||
label: this.browseConnectionMode ? `${databaseServer.name} (${PostgresServerTreeDataProvider.containerLabel}, ${databaseServer.subscription.name})` : databaseServer.name,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_server_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/sql_server.svg')
|
||||
dark: this._extensionContext.asAbsolutePath('resources/postgresServer.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/postgresServer.svg')
|
||||
},
|
||||
collapsibleState: this.browseConnectionMode ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseServer,
|
||||
@@ -67,8 +67,8 @@ export class PostgresServerTreeDataProvider extends ResourceTreeDataProviderBase
|
||||
id: PostgresServerTreeDataProvider.containerId,
|
||||
label: PostgresServerTreeDataProvider.containerLabel,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
|
||||
dark: this._extensionContext.asAbsolutePath('resources/postgresServer.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/postgresServer.svg')
|
||||
},
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseServerContainer
|
||||
|
||||
@@ -16,7 +16,7 @@ import { AzureAccount, azureResource } from 'azurecore';
|
||||
|
||||
export class SqlInstanceTreeDataProvider extends ResourceTreeDataProviderBase<GraphData, SqlInstanceGraphData> {
|
||||
private static readonly containerId = 'azure.resource.providers.sqlInstanceContainer';
|
||||
private static readonly containerLabel = localize('azure.resource.providers.sqlInstanceContainerLabel', "Azure SQL DB managed instance");
|
||||
private static readonly containerLabel = localize('azure.resource.providers.sqlInstanceContainerLabel', "SQL managed instances");
|
||||
|
||||
public constructor(
|
||||
databaseServerService: azureResource.IAzureResourceService,
|
||||
@@ -27,12 +27,9 @@ export class SqlInstanceTreeDataProvider extends ResourceTreeDataProviderBase<Gr
|
||||
|
||||
public getTreeItemForResource(databaseServer: azureResource.AzureResourceDatabaseServer, account: AzureAccount): TreeItem {
|
||||
return {
|
||||
id: `${AzureResourcePrefixes.sqlInstance}${account.key.accountId}${account.key.accountId}${databaseServer.id ?? databaseServer.name}`,
|
||||
id: `${AzureResourcePrefixes.sqlInstance}${account.key.accountId}${databaseServer.tenant}${account.key.accountId}${databaseServer.id ?? databaseServer.name}`,
|
||||
label: this.browseConnectionMode ? `${databaseServer.name} (${SqlInstanceTreeDataProvider.containerLabel}, ${databaseServer.subscription.name})` : databaseServer.name,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_instance_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/sql_instance.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/sqlManagedInstance.svg'),
|
||||
collapsibleState: this.browseConnectionMode ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseServer,
|
||||
payload: {
|
||||
@@ -63,10 +60,7 @@ export class SqlInstanceTreeDataProvider extends ResourceTreeDataProviderBase<Gr
|
||||
return [{
|
||||
id: SqlInstanceTreeDataProvider.containerId,
|
||||
label: SqlInstanceTreeDataProvider.containerLabel,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/sqlManagedInstance.svg'),
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseServerContainer
|
||||
}];
|
||||
|
||||
@@ -17,7 +17,7 @@ import { AzureAccount, azureResource } from 'azurecore';
|
||||
export class SqlInstanceArcTreeDataProvider extends ResourceTreeDataProviderBase<GraphData, SqlInstanceArcGraphData> {
|
||||
private static readonly containerId = 'azure.resource.providers.sqlInstanceArcContainer';
|
||||
// allow-any-unicode-next-line
|
||||
private static readonly containerLabel = localize('azure.resource.providers.sqlInstanceArcContainerLabel', "SQL managed instance – Azure Arc");
|
||||
private static readonly containerLabel = localize('azure.resource.providers.sqlInstanceArcContainerLabel', "SQL managed instances - Azure Arc");
|
||||
|
||||
public constructor(
|
||||
databaseServerService: azureResource.IAzureResourceService,
|
||||
@@ -28,12 +28,9 @@ export class SqlInstanceArcTreeDataProvider extends ResourceTreeDataProviderBase
|
||||
|
||||
public getTreeItemForResource(databaseServer: azureResource.AzureResourceDatabaseServer, account: AzureAccount): TreeItem {
|
||||
return {
|
||||
id: `${AzureResourcePrefixes.sqlInstanceArc}${account.key.accountId}${databaseServer.id ?? databaseServer.name}`,
|
||||
id: `${AzureResourcePrefixes.sqlInstanceArc}${account.key.accountId}${databaseServer.tenant}${databaseServer.id ?? databaseServer.name}`,
|
||||
label: this.browseConnectionMode ? `${databaseServer.name} (${SqlInstanceArcTreeDataProvider.containerLabel}, ${databaseServer.subscription.name})` : databaseServer.name,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_instance_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/sql_instance.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/azureArcSqlManagedInstance.svg'),
|
||||
collapsibleState: this.browseConnectionMode ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseServer,
|
||||
payload: {
|
||||
@@ -64,10 +61,7 @@ export class SqlInstanceArcTreeDataProvider extends ResourceTreeDataProviderBase
|
||||
return [{
|
||||
id: SqlInstanceArcTreeDataProvider.containerId,
|
||||
label: SqlInstanceArcTreeDataProvider.containerLabel,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/azureArcSqlManagedInstance.svg'),
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseServerContainer
|
||||
}];
|
||||
|
||||
@@ -28,12 +28,9 @@ export class AzureResourceSynapseSqlPoolTreeDataProvider extends ResourceTreeDat
|
||||
|
||||
public getTreeItemForResource(synapse: azureResource.AzureResourceDatabase, account: AzureAccount): TreeItem {
|
||||
return {
|
||||
id: `${AzureResourcePrefixes.synapseWorkspace}${account.key.accountId}${synapse.serverFullName}.${AzureResourcePrefixes.synapseSqlPool}${synapse.id ?? synapse.name}`,
|
||||
id: `${AzureResourcePrefixes.synapseWorkspace}${account.key.accountId}${synapse.tenant}${synapse.serverFullName}.${AzureResourcePrefixes.synapseSqlPool}${synapse.id ?? synapse.name}`,
|
||||
label: this.browseConnectionMode ? `${synapse.serverName}/${synapse.name} (${AzureResourceSynapseSqlPoolTreeDataProvider.containerLabel}, ${synapse.subscription.name})` : `${synapse.name} (${synapse.serverName})`,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_database_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/sql_database.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/sqlPools.svg'),
|
||||
collapsibleState: this.browseConnectionMode ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.synapseSqlPool,
|
||||
payload: {
|
||||
@@ -64,10 +61,7 @@ export class AzureResourceSynapseSqlPoolTreeDataProvider extends ResourceTreeDat
|
||||
return [{
|
||||
id: AzureResourceSynapseSqlPoolTreeDataProvider.containerId,
|
||||
label: AzureResourceSynapseSqlPoolTreeDataProvider.containerLabel,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/sqlPools.svg'),
|
||||
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.synapseSqlPoolContainer
|
||||
}];
|
||||
|
||||
@@ -27,12 +27,9 @@ export class AzureResourceSynapseWorkspaceTreeDataProvider extends ResourceTreeD
|
||||
|
||||
public getTreeItemForResource(synapseWorkspace: azureResource.AzureResourceDatabaseServer, account: AzureAccount): TreeItem {
|
||||
return {
|
||||
id: `${AzureResourcePrefixes.synapseWorkspace}${account.key.accountId}${synapseWorkspace.id ?? synapseWorkspace.name}`,
|
||||
id: `${AzureResourcePrefixes.synapseWorkspace}${account.key.accountId}${synapseWorkspace.tenant}${synapseWorkspace.id ?? synapseWorkspace.name}`,
|
||||
label: this.browseConnectionMode ? `${synapseWorkspace.name} (${AzureResourceSynapseWorkspaceTreeDataProvider.containerLabel}, ${synapseWorkspace.subscription.name})` : synapseWorkspace.name,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_server_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/sql_server.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/azureSynapseAnalytics.svg'),
|
||||
collapsibleState: this.browseConnectionMode ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.synapseWorkspace,
|
||||
payload: {
|
||||
@@ -63,10 +60,7 @@ export class AzureResourceSynapseWorkspaceTreeDataProvider extends ResourceTreeD
|
||||
return [{
|
||||
id: AzureResourceSynapseWorkspaceTreeDataProvider.containerId,
|
||||
label: AzureResourceSynapseWorkspaceTreeDataProvider.containerLabel,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
|
||||
},
|
||||
iconPath: this._extensionContext.asAbsolutePath('resources/azureSynapseAnalytics.svg'),
|
||||
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.synapseWorkspaceContainer
|
||||
}];
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AzureAccount, azureResource } from 'azurecore';
|
||||
import { AzureAccount, Tenant, azureResource } from 'azurecore';
|
||||
import { IAzureResourceSubscriptionFilterService, IAzureResourceCacheService } from '../interfaces';
|
||||
|
||||
interface AzureResourceSelectedSubscriptionsCache {
|
||||
selectedSubscriptions: { [accountId: string]: azureResource.AzureResourceSubscription[] };
|
||||
selectedSubscriptions: { [accountTenantId: string]: azureResource.AzureResourceSubscription[] };
|
||||
}
|
||||
|
||||
export class AzureResourceSubscriptionFilterService implements IAzureResourceSubscriptionFilterService {
|
||||
@@ -19,36 +19,44 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
||||
this._cacheKey = this._cacheService.generateKey('selectedSubscriptions');
|
||||
}
|
||||
|
||||
public async getSelectedSubscriptions(account: AzureAccount): Promise<azureResource.AzureResourceSubscription[]> {
|
||||
public async getSelectedSubscriptions(account: AzureAccount, tenant: Tenant): Promise<azureResource.AzureResourceSubscription[]> {
|
||||
let selectedSubscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||
|
||||
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(this._cacheKey);
|
||||
if (cache) {
|
||||
selectedSubscriptions = cache.selectedSubscriptions[account.key.accountId];
|
||||
selectedSubscriptions = cache.selectedSubscriptions[account.key.accountId + '/' + tenant.id];
|
||||
if (!selectedSubscriptions) {
|
||||
let oldTenantCache = cache.selectedSubscriptions[account.key.accountId]?.filter(sub => sub.tenant === tenant.id);
|
||||
if (oldTenantCache) {
|
||||
await this.saveSelectedSubscriptions(account, tenant, oldTenantCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return selectedSubscriptions;
|
||||
}
|
||||
|
||||
public async saveSelectedSubscriptions(account: AzureAccount, selectedSubscriptions: azureResource.AzureResourceSubscription[]): Promise<void> {
|
||||
let selectedSubscriptionsCache: { [accountId: string]: azureResource.AzureResourceSubscription[] } = {};
|
||||
public async saveSelectedSubscriptions(account: AzureAccount, tenant: Tenant, selectedSubscriptions: azureResource.AzureResourceSubscription[]): Promise<void> {
|
||||
let selections: { [accountTenantId: string]: azureResource.AzureResourceSubscription[] } = {};
|
||||
|
||||
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(this._cacheKey);
|
||||
if (cache) {
|
||||
selectedSubscriptionsCache = cache.selectedSubscriptions;
|
||||
selections = cache.selectedSubscriptions;
|
||||
}
|
||||
|
||||
if (!selectedSubscriptionsCache) {
|
||||
selectedSubscriptionsCache = {};
|
||||
if (!selections) {
|
||||
selections = {};
|
||||
}
|
||||
|
||||
selectedSubscriptionsCache[account.key.accountId] = selectedSubscriptions;
|
||||
let accountTenantId = account.key.accountId;
|
||||
if (tenant) {
|
||||
accountTenantId += '/' + tenant.id;
|
||||
}
|
||||
|
||||
await this._cacheService.update<AzureResourceSelectedSubscriptionsCache>(this._cacheKey, { selectedSubscriptions: selectedSubscriptionsCache });
|
||||
selections[accountTenantId] = selectedSubscriptions;
|
||||
|
||||
await this._cacheService.update<AzureResourceSelectedSubscriptionsCache>(this._cacheKey, { selectedSubscriptions: selections });
|
||||
|
||||
const filters: string[] = [];
|
||||
for (const accountId in selectedSubscriptionsCache) {
|
||||
filters.push(...selectedSubscriptionsCache[accountId].map((subscription) => `${accountId}/${subscription.id}/${subscription.name}`));
|
||||
}
|
||||
filters.push(...selections[accountTenantId].map((subscription) => `${accountTenantId}/${subscription.id}`));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AzureAccount, Tenant } from 'azurecore';
|
||||
import { IAzureResourceTenantFilterService, IAzureResourceCacheService } from '../interfaces';
|
||||
|
||||
interface AzureResourceSelectedTenantsCache {
|
||||
selectedTenants: { [accountId: string]: Tenant[] };
|
||||
}
|
||||
|
||||
export class AzureResourceTenantFilterService implements IAzureResourceTenantFilterService {
|
||||
private _cacheKey: string;
|
||||
|
||||
public constructor(
|
||||
private _cacheService: IAzureResourceCacheService
|
||||
) {
|
||||
this._cacheKey = this._cacheService.generateKey('selectedTenants');
|
||||
}
|
||||
|
||||
public async getSelectedTenants(account: AzureAccount): Promise<Tenant[]> {
|
||||
let selectedTenants: Tenant[] = [];
|
||||
|
||||
const cache = this._cacheService.get<AzureResourceSelectedTenantsCache>(this._cacheKey);
|
||||
if (cache) {
|
||||
selectedTenants = cache.selectedTenants[account.key.accountId];
|
||||
}
|
||||
|
||||
return selectedTenants;
|
||||
}
|
||||
|
||||
public async saveSelectedTenants(account: AzureAccount, selectedTenants: Tenant[]): Promise<void> {
|
||||
let selectedTenantsCache: { [accountId: string]: Tenant[] } = {};
|
||||
|
||||
const cache = this._cacheService.get<AzureResourceSelectedTenantsCache>(this._cacheKey);
|
||||
if (cache) {
|
||||
selectedTenantsCache = cache.selectedTenants;
|
||||
}
|
||||
|
||||
if (!selectedTenantsCache) {
|
||||
selectedTenantsCache = {};
|
||||
}
|
||||
|
||||
selectedTenantsCache[account.key.accountId] = selectedTenants;
|
||||
|
||||
await this._cacheService.update<AzureResourceSelectedTenantsCache>(this._cacheKey, { selectedTenants: selectedTenantsCache });
|
||||
|
||||
const filters: string[] = [];
|
||||
for (const accountId in selectedTenantsCache) {
|
||||
filters.push(...selectedTenantsCache[accountId].map((tenant) => `${accountId}/${tenant.id}/${tenant.displayName}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,16 +11,13 @@ const localize = nls.loadMessageBundle();
|
||||
|
||||
import { AppContext } from '../../appContext';
|
||||
import { TreeNode } from '../treeNode';
|
||||
import { AzureSubscriptionError } from '../errors';
|
||||
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
||||
import { AzureResourceItemType, AzureResourceServiceNames } from '../constants';
|
||||
import { AzureResourceSubscriptionTreeNode } from './subscriptionTreeNode';
|
||||
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
||||
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService } from '../../azureResource/interfaces';
|
||||
import { AzureAccount, azureResource } from 'azurecore';
|
||||
import { TenantIgnoredError } from '../../utils/TenantIgnoredError';
|
||||
import { AzureAccount, Tenant } from 'azurecore';
|
||||
import { AzureResourceTenantTreeNode } from './tenantTreeNode';
|
||||
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
||||
import { IAzureResourceTenantFilterService } from '../interfaces';
|
||||
|
||||
export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNodeBase {
|
||||
public constructor(
|
||||
@@ -29,92 +26,51 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
||||
treeChangeHandler: IAzureResourceTreeChangeHandler
|
||||
) {
|
||||
super(appContext, treeChangeHandler, undefined);
|
||||
this._tenantFilterService = this.appContext.getService<IAzureResourceTenantFilterService>(AzureResourceServiceNames.tenantFilterService);
|
||||
|
||||
this._subscriptionService = this.appContext.getService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService);
|
||||
this._subscriptionFilterService = this.appContext.getService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService);
|
||||
|
||||
if (this.account.properties.tenants.length === 1) {
|
||||
this._singleTenantTreeNode = new AzureResourceTenantTreeNode(this.account, this.account.properties.tenants[0], this, this.appContext, this.treeChangeHandler);
|
||||
}
|
||||
this._id = `account_${this.account.key.accountId}`;
|
||||
this.setCacheKey(`${this._id}.subscriptions`);
|
||||
this.setCacheKey(`${this._id}.tenants`);
|
||||
this._label = this.generateLabel();
|
||||
}
|
||||
|
||||
public async getChildren(): Promise<TreeNode[]> {
|
||||
try {
|
||||
let subscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||
let tenants = this.account.properties.tenants;
|
||||
this._totalTenantsCount = tenants.length;
|
||||
|
||||
if (this._isClearingCache) {
|
||||
subscriptions = await this._subscriptionService.getSubscriptions(this.account);
|
||||
await this.updateCache<azureResource.AzureResourceSubscription[]>(subscriptions);
|
||||
this._isClearingCache = false;
|
||||
} else {
|
||||
subscriptions = await this.getCachedSubscriptions();
|
||||
}
|
||||
const selectedTenants = await this._tenantFilterService.getSelectedTenants(this.account);
|
||||
const selectedTenantIds = (selectedTenants || <Tenant[]>[]).map((Tenant) => Tenant.id);
|
||||
|
||||
this._totalSubscriptionCount = subscriptions.length;
|
||||
|
||||
const selectedSubscriptions = await this._subscriptionFilterService.getSelectedSubscriptions(this.account);
|
||||
const selectedSubscriptionIds = (selectedSubscriptions || <azureResource.AzureResourceSubscription[]>[]).map((subscription) => subscription.id);
|
||||
if (selectedSubscriptionIds.length > 0) {
|
||||
subscriptions = subscriptions.filter((subscription) => selectedSubscriptionIds.indexOf(subscription.id) !== -1);
|
||||
this._selectedSubscriptionCount = selectedSubscriptionIds.length;
|
||||
} else {
|
||||
// ALL subscriptions are listed by default
|
||||
this._selectedSubscriptionCount = this._totalSubscriptionCount;
|
||||
}
|
||||
|
||||
this.refreshLabel();
|
||||
|
||||
if (subscriptions.length === 0) {
|
||||
return [AzureResourceMessageTreeNode.create(AzureResourceAccountTreeNode.noSubscriptionsLabel, this)];
|
||||
} else {
|
||||
const authLibrary = vscode.workspace.getConfiguration('azure').get('authenticationLibrary');
|
||||
if (authLibrary === 'ADAL') {
|
||||
// Filter out everything that we can't authenticate to.
|
||||
const hasTokenResults = await Promise.all(subscriptions.map(async s => {
|
||||
let token: azdata.accounts.AccountSecurityToken | undefined = undefined;
|
||||
let errMsg = '';
|
||||
try {
|
||||
token = await azdata.accounts.getAccountSecurityToken(this.account, s.tenant!, azdata.AzureResource.ResourceManagement);
|
||||
} catch (err) {
|
||||
if (!(err instanceof TenantIgnoredError)) {
|
||||
errMsg = AzureResourceErrorMessageUtil.getErrorMessage(err);
|
||||
}
|
||||
}
|
||||
if (!token) {
|
||||
if (errMsg !== '') {
|
||||
void vscode.window.showWarningMessage(localize('azure.unableToAccessSubscription', "Unable to access subscription {0} ({1}). Please [refresh the account](command:azure.resource.signin) to try again. {2}", s.name, s.id, errMsg));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}));
|
||||
subscriptions = subscriptions.filter((_s, i) => hasTokenResults[i]);
|
||||
}
|
||||
let subTreeNodes = await Promise.all(subscriptions.map(async (subscription) => {
|
||||
return new AzureResourceSubscriptionTreeNode(this.account, subscription, subscription.tenant!, this.appContext, this.treeChangeHandler, this);
|
||||
}));
|
||||
return subTreeNodes.sort((a, b) => a.subscription.name.localeCompare(b.subscription.name));
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof AzureSubscriptionError) {
|
||||
void vscode.commands.executeCommand('azure.resource.signin');
|
||||
}
|
||||
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
|
||||
if (selectedTenantIds.length > 0) {
|
||||
tenants = tenants.filter((tenant) => selectedTenantIds.indexOf(tenant.id) !== -1);
|
||||
this._selectedTenantsCount = selectedTenantIds.length;
|
||||
} else {
|
||||
// ALL Tenants are listed by default
|
||||
this._selectedTenantsCount = this._totalTenantsCount;
|
||||
}
|
||||
}
|
||||
|
||||
public async getCachedSubscriptions(): Promise<azureResource.AzureResourceSubscription[]> {
|
||||
return this.getCache<azureResource.AzureResourceSubscription[]>() ?? [];
|
||||
this.refreshLabel();
|
||||
|
||||
if (this.totalTenantsCount === 1) {
|
||||
return await this._singleTenantTreeNode?.getChildren() ?? [];
|
||||
} else if (tenants.length === 0) {
|
||||
return [AzureResourceMessageTreeNode.create(AzureResourceAccountTreeNode.noTenantsLabel, this)];
|
||||
} else {
|
||||
let subTreeNodes = await Promise.all(tenants.map(async (tenant) => {
|
||||
return new AzureResourceTenantTreeNode(this.account, tenant, this, this.appContext, this.treeChangeHandler);
|
||||
}));
|
||||
return subTreeNodes.sort((a, b) => a.tenant.displayName.localeCompare(b.tenant.displayName));
|
||||
}
|
||||
}
|
||||
|
||||
public getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem> {
|
||||
const item = new vscode.TreeItem(this._label, vscode.TreeItemCollapsibleState.Collapsed);
|
||||
item.id = this._id;
|
||||
item.contextValue = AzureResourceItemType.account;
|
||||
item.iconPath = {
|
||||
dark: this.appContext.extensionContext.asAbsolutePath('resources/dark/account_inverse.svg'),
|
||||
light: this.appContext.extensionContext.asAbsolutePath('resources/light/account.svg')
|
||||
};
|
||||
item.contextValue = this.account.properties.tenants.length > 1 ?
|
||||
AzureResourceItemType.multipleTenantAccount : AzureResourceItemType.singleTenantAccount;
|
||||
item.iconPath = this.appContext.extensionContext.asAbsolutePath('resources/users.svg');
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -137,12 +93,12 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public get totalSubscriptionCount(): number {
|
||||
return this._totalSubscriptionCount;
|
||||
public get totalTenantsCount(): number {
|
||||
return this._totalTenantsCount;
|
||||
}
|
||||
|
||||
public get selectedSubscriptionCount(): number {
|
||||
return this._selectedSubscriptionCount;
|
||||
public get selectedTenantCount(): number {
|
||||
return this._selectedTenantsCount;
|
||||
}
|
||||
|
||||
protected refreshLabel(): void {
|
||||
@@ -156,25 +112,23 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
||||
private generateLabel(): string {
|
||||
let label = this.account.displayInfo.displayName;
|
||||
|
||||
if (this._totalSubscriptionCount !== 0) {
|
||||
label += ` (${this._selectedSubscriptionCount} / ${this._totalSubscriptionCount} subscriptions)`;
|
||||
if (this._totalTenantsCount === 1 && this._singleTenantTreeNode) {
|
||||
label += ` (${this._singleTenantTreeNode.selectedSubscriptionCount} / ${this._singleTenantTreeNode.totalSubscriptionCount} subscriptions)`;
|
||||
} else if (this._totalTenantsCount > 0) {
|
||||
label += ` (${this._selectedTenantsCount} / ${this._totalTenantsCount} tenants)`;
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
private _subscriptionService: IAzureResourceSubscriptionService;
|
||||
private _subscriptionFilterService: IAzureResourceSubscriptionFilterService;
|
||||
private _tenantFilterService: IAzureResourceTenantFilterService;
|
||||
|
||||
private _id: string;
|
||||
private _label: string;
|
||||
private _totalSubscriptionCount = 0;
|
||||
private _selectedSubscriptionCount = 0;
|
||||
private _totalTenantsCount = 0;
|
||||
private _selectedTenantsCount = 0;
|
||||
private _singleTenantTreeNode: AzureResourceTenantTreeNode | undefined;
|
||||
|
||||
private static readonly noSubscriptionsLabel = localize('azure.resource.tree.accountTreeNode.noSubscriptionsLabel', "No Subscriptions found.");
|
||||
|
||||
sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
private static readonly noTenantsLabel = localize('azure.resource.tree.accountTreeNode.noTenantsLabel', "No Tenants found.");
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ abstract class AzureResourceTreeNodeBase extends TreeNode {
|
||||
parent: TreeNode | undefined
|
||||
) {
|
||||
super();
|
||||
|
||||
this.parent = parent;
|
||||
}
|
||||
}
|
||||
@@ -29,7 +28,6 @@ export abstract class AzureResourceContainerTreeNodeBase extends AzureResourceTr
|
||||
parent: TreeNode | undefined
|
||||
) {
|
||||
super(appContext, treeChangeHandler, parent);
|
||||
|
||||
this._cacheService = this.appContext.getService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
||||
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
||||
import { AzureResourceErrorMessageUtil, equals, filterAccounts } from '../utils';
|
||||
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||
import { FlatAccountTreeNode } from './flatAccountTreeNode';
|
||||
import { Logger } from '../../utils/Logger';
|
||||
import { AzureAccount } from 'azurecore';
|
||||
import { FlatAccountTreeNode } from './flatAccountTreeNode';
|
||||
|
||||
export class ConnectionDialogTreeProvider implements vscode.TreeDataProvider<TreeNode>, IAzureResourceTreeChangeHandler {
|
||||
public isSystemInitialized: boolean = false;
|
||||
@@ -45,7 +45,7 @@ export class ConnectionDialogTreeProvider implements vscode.TreeDataProvider<Tre
|
||||
|
||||
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
|
||||
if (element) {
|
||||
return element.getChildren(true);
|
||||
return element.getChildren();
|
||||
}
|
||||
|
||||
if (!this.isSystemInitialized) {
|
||||
@@ -63,6 +63,7 @@ export class ConnectionDialogTreeProvider implements vscode.TreeDataProvider<Tre
|
||||
for (const account of accounts) {
|
||||
try {
|
||||
const accountNode = new FlatAccountTreeNode(account, this.appContext, this);
|
||||
accountNode.refreshLabel();
|
||||
accountNodes.push(accountNode);
|
||||
}
|
||||
catch (error) {
|
||||
|
||||
@@ -11,17 +11,13 @@ const localize = nls.loadMessageBundle();
|
||||
|
||||
import { AppContext } from '../../appContext';
|
||||
import { TreeNode } from '../treeNode';
|
||||
import { AzureSubscriptionError } from '../errors';
|
||||
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
||||
import { AzureResourceItemType, AzureResourceServiceNames } from '../constants';
|
||||
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService } from '../../azureResource/interfaces';
|
||||
import { AzureAccount, azureResource } from 'azurecore';
|
||||
import { AzureResourceService } from '../resourceService';
|
||||
import { AzureResourceResourceTreeNode } from '../resourceTreeNode';
|
||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
||||
import { Logger } from '../../utils/Logger';
|
||||
import { IAzureResourceTenantFilterService } from '../interfaces';
|
||||
import { AzureAccount, Tenant } from 'azurecore';
|
||||
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
||||
import { FlatTenantTreeNode } from './flatTenantTreeNode';
|
||||
|
||||
export class FlatAccountTreeNode extends AzureResourceContainerTreeNodeBase {
|
||||
public constructor(
|
||||
@@ -31,60 +27,69 @@ export class FlatAccountTreeNode extends AzureResourceContainerTreeNodeBase {
|
||||
) {
|
||||
super(appContext, treeChangeHandler, undefined);
|
||||
|
||||
this._subscriptionService = this.appContext.getService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService);
|
||||
this._subscriptionFilterService = this.appContext.getService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService);
|
||||
this._resourceService = this.appContext.getService<AzureResourceService>(AzureResourceServiceNames.resourceService);
|
||||
this._tenantFilterService = this.appContext.getService<IAzureResourceTenantFilterService>(AzureResourceServiceNames.tenantFilterService);
|
||||
|
||||
this._id = `account_${this.account.key.accountId}`;
|
||||
this.setCacheKey(`${this._id}.dataresources`);
|
||||
this._label = account.displayInfo.displayName;
|
||||
this._loader = new FlatAccountTreeNodeLoader(appContext, this._resourceService, this._subscriptionService, this._subscriptionFilterService, this.account, this);
|
||||
this._loader.onNewResourcesAvailable(() => {
|
||||
this.treeChangeHandler.notifyNodeChanged(this);
|
||||
});
|
||||
|
||||
this._loader.onLoadingStatusChanged(() => {
|
||||
this.treeChangeHandler.notifyNodeChanged(this);
|
||||
});
|
||||
}
|
||||
|
||||
public async updateLabel(): Promise<void> {
|
||||
const subscriptionInfo = await getSubscriptionInfo(this.account, this._subscriptionService, this._subscriptionFilterService);
|
||||
if (this._loader.isLoading) {
|
||||
this._label = localize('azure.resource.tree.accountTreeNode.titleLoading', "{0} - Loading...", this.account.displayInfo.displayName);
|
||||
} else if (subscriptionInfo.total !== 0) {
|
||||
this._label = localize({
|
||||
key: 'azure.resource.tree.accountTreeNode.title',
|
||||
comment: [
|
||||
'{0} is the display name of the azure account',
|
||||
'{1} is the number of selected subscriptions in this account',
|
||||
'{2} is the number of total subscriptions in this account'
|
||||
]
|
||||
}, "{0} ({1}/{2} subscriptions)", this.account.displayInfo.displayName, subscriptionInfo.selected, subscriptionInfo.total);
|
||||
} else {
|
||||
this._label = this.account.displayInfo.displayName;
|
||||
this.setCacheKey(`${this._id}.tenants`);
|
||||
if (this.account.properties.tenants.length === 1) {
|
||||
this._singleTenantTreeNode = new FlatTenantTreeNode(this.account, this.account.properties.tenants[0], this, this.appContext, this.treeChangeHandler);
|
||||
}
|
||||
this._label = this.generateLabel();
|
||||
}
|
||||
|
||||
public async getChildren(): Promise<TreeNode[]> {
|
||||
let nodesResult: TreeNode[] = [];
|
||||
let tenants = this.account.properties.tenants;
|
||||
this._totalTenantsCount = tenants.length;
|
||||
|
||||
const selectedTenants = await this._tenantFilterService.getSelectedTenants(this.account);
|
||||
const selectedTenantIds = (selectedTenants || <Tenant[]>[]).map((Tenant) => Tenant.id);
|
||||
|
||||
if (selectedTenantIds.length > 0) {
|
||||
tenants = tenants.filter((tenant) => selectedTenantIds.indexOf(tenant.id) !== -1);
|
||||
this._selectedTenantsCount = selectedTenantIds.length;
|
||||
} else {
|
||||
// ALL Tenants are listed by default
|
||||
this._selectedTenantsCount = this._totalTenantsCount;
|
||||
}
|
||||
|
||||
this.refreshLabel();
|
||||
|
||||
if (this._totalTenantsCount === 1) {
|
||||
if (this._isClearingCache) {
|
||||
await this._singleTenantTreeNode?.getChildren();
|
||||
} else {
|
||||
nodesResult = await this._singleTenantTreeNode?.getChildren() ?? [];
|
||||
}
|
||||
} else if (tenants.length === 0) {
|
||||
nodesResult = [AzureResourceMessageTreeNode.create(FlatAccountTreeNode.noTenantsLabel, this)];
|
||||
} else {
|
||||
if (!this._multiTenantTreeNodes) {
|
||||
this._multiTenantTreeNodes = await Promise.all(tenants.map(async (tenant) => {
|
||||
let node = new FlatTenantTreeNode(this.account, tenant, undefined, this.appContext, this.treeChangeHandler);
|
||||
return node;
|
||||
}));
|
||||
}
|
||||
nodesResult = this._multiTenantTreeNodes.sort((a, b) => a.tenant.displayName.localeCompare(b.tenant.displayName));
|
||||
}
|
||||
|
||||
if (this._isClearingCache) {
|
||||
await this.updateLabel();
|
||||
this._loader.start().catch(err => console.error('Error loading Azure FlatAccountTreeNodes ', err));
|
||||
this._multiTenantTreeNodes?.forEach(node => {
|
||||
node.clearCache();
|
||||
});
|
||||
this._isClearingCache = false;
|
||||
return [];
|
||||
} else {
|
||||
return this._loader.nodes;
|
||||
return nodesResult;
|
||||
}
|
||||
}
|
||||
|
||||
public getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem> {
|
||||
const item = new vscode.TreeItem(this._label, vscode.TreeItemCollapsibleState.Collapsed);
|
||||
item.id = this._id;
|
||||
item.contextValue = AzureResourceItemType.account;
|
||||
item.iconPath = {
|
||||
dark: this.appContext.extensionContext.asAbsolutePath('resources/dark/account_inverse.svg'),
|
||||
light: this.appContext.extensionContext.asAbsolutePath('resources/light/account.svg')
|
||||
};
|
||||
item.contextValue = this.account.properties.tenants.length > 1 ?
|
||||
AzureResourceItemType.multipleTenantAccount : AzureResourceItemType.singleTenantAccount;
|
||||
item.iconPath = this.appContext.extensionContext.asAbsolutePath('resources/users.svg');
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -107,130 +112,34 @@ export class FlatAccountTreeNode extends AzureResourceContainerTreeNodeBase {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
private _subscriptionService: IAzureResourceSubscriptionService;
|
||||
private _subscriptionFilterService: IAzureResourceSubscriptionFilterService;
|
||||
private _resourceService: AzureResourceService;
|
||||
private _loader: FlatAccountTreeNodeLoader;
|
||||
public refreshLabel(): void {
|
||||
const newLabel = this.generateLabel();
|
||||
if (this._label !== newLabel) {
|
||||
this._label = newLabel;
|
||||
this.treeChangeHandler.notifyNodeChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
private generateLabel(): string {
|
||||
let label = this.account.displayInfo.displayName;
|
||||
|
||||
if (this._totalTenantsCount === 1 && this._singleTenantTreeNode) {
|
||||
label += ` (${this._singleTenantTreeNode._selectedSubscriptionCount} / ${this._singleTenantTreeNode._totalSubscriptionCount} subscriptions)`;
|
||||
} else if (this._totalTenantsCount > 0) {
|
||||
label += ` (${this._selectedTenantsCount} / ${this._totalTenantsCount} tenants)`;
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
private _tenantFilterService: IAzureResourceTenantFilterService;
|
||||
|
||||
private _id: string;
|
||||
private _label: string;
|
||||
}
|
||||
|
||||
async function getSubscriptionInfo(account: AzureAccount, subscriptionService: IAzureResourceSubscriptionService, subscriptionFilterService: IAzureResourceSubscriptionFilterService): Promise<{
|
||||
subscriptions: azureResource.AzureResourceSubscription[],
|
||||
total: number,
|
||||
selected: number
|
||||
}> {
|
||||
let subscriptions = await subscriptionService.getSubscriptions(account);
|
||||
const total = subscriptions.length;
|
||||
let selected = total;
|
||||
|
||||
const selectedSubscriptions = await subscriptionFilterService.getSelectedSubscriptions(account);
|
||||
const selectedSubscriptionIds = (selectedSubscriptions || <azureResource.AzureResourceSubscription[]>[]).map((subscription) => subscription.id);
|
||||
if (selectedSubscriptionIds.length > 0) {
|
||||
subscriptions = subscriptions.filter((subscription) => selectedSubscriptionIds.indexOf(subscription.id) !== -1);
|
||||
selected = selectedSubscriptionIds.length;
|
||||
}
|
||||
return {
|
||||
subscriptions,
|
||||
total,
|
||||
selected
|
||||
};
|
||||
}
|
||||
class FlatAccountTreeNodeLoader {
|
||||
|
||||
private _isLoading: boolean = false;
|
||||
private _nodes: TreeNode[] = [];
|
||||
private readonly _onNewResourcesAvailable = new vscode.EventEmitter<void>();
|
||||
public readonly onNewResourcesAvailable = this._onNewResourcesAvailable.event;
|
||||
private readonly _onLoadingStatusChanged = new vscode.EventEmitter<void>();
|
||||
public readonly onLoadingStatusChanged = this._onLoadingStatusChanged.event;
|
||||
|
||||
constructor(private readonly appContext: AppContext,
|
||||
private readonly _resourceService: AzureResourceService,
|
||||
private readonly _subscriptionService: IAzureResourceSubscriptionService,
|
||||
private readonly _subscriptionFilterService: IAzureResourceSubscriptionFilterService,
|
||||
private readonly _account: AzureAccount,
|
||||
private readonly _accountNode: TreeNode) {
|
||||
}
|
||||
|
||||
public get isLoading(): boolean {
|
||||
return this._isLoading;
|
||||
}
|
||||
|
||||
public get nodes(): TreeNode[] {
|
||||
return this._nodes;
|
||||
}
|
||||
|
||||
public async start(): Promise<void> {
|
||||
if (this._isLoading) {
|
||||
return;
|
||||
}
|
||||
this._isLoading = true;
|
||||
this._nodes = [];
|
||||
this._onLoadingStatusChanged.fire();
|
||||
let newNodesAvailable = false;
|
||||
|
||||
// Throttle the refresh events to at most once per 500ms
|
||||
const refreshHandle = setInterval(() => {
|
||||
if (newNodesAvailable) {
|
||||
this._onNewResourcesAvailable.fire();
|
||||
newNodesAvailable = false;
|
||||
}
|
||||
if (!this.isLoading) {
|
||||
clearInterval(refreshHandle);
|
||||
}
|
||||
}, 500);
|
||||
try {
|
||||
|
||||
// Authenticate to tenants to filter out subscriptions that are not accessible.
|
||||
let tenants = this._account.properties.tenants;
|
||||
// Filter out tenants that we can't authenticate to.
|
||||
tenants = tenants.filter(async tenant => {
|
||||
try {
|
||||
const token = await azdata.accounts.getAccountSecurityToken(this._account, tenant.id, azdata.AzureResource.ResourceManagement);
|
||||
return token !== undefined;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
let subscriptions: azureResource.AzureResourceSubscription[] = (await getSubscriptionInfo(this._account, this._subscriptionService, this._subscriptionFilterService)).subscriptions;
|
||||
|
||||
if (subscriptions.length !== 0) {
|
||||
// Filter out subscriptions that don't belong to the tenants we filtered above.
|
||||
subscriptions = subscriptions.filter(async s => {
|
||||
const tenant = tenants.find(t => t.id === s.tenant);
|
||||
if (!tenant) {
|
||||
Logger.info(`Account does not have permissions to view subscription ${JSON.stringify(s)}.`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
const resources = await this._resourceService.getAllChildren(this._account, subscriptions, true);
|
||||
if (resources?.length > 0) {
|
||||
this._nodes.push(...resources.map(dr => new AzureResourceResourceTreeNode(dr, this._accountNode, this.appContext)));
|
||||
this._nodes = this.nodes.sort((a, b) => {
|
||||
return a.getNodeInfo().label.localeCompare(b.getNodeInfo().label);
|
||||
});
|
||||
newNodesAvailable = true;
|
||||
}
|
||||
// Create "No Resources Found" message node if no resources found under azure account.
|
||||
if (this._nodes.length === 0) {
|
||||
this._nodes.push(AzureResourceMessageTreeNode.create(localize('azure.resource.flatAccountTreeNode.noResourcesLabel', "No Resources found."), this._accountNode))
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof AzureSubscriptionError) {
|
||||
void vscode.commands.executeCommand('azure.resource.signin');
|
||||
}
|
||||
// http status code 429 means "too many requests"
|
||||
// use a custom error message for azure resource graph api throttling error to make it more actionable for users.
|
||||
const errorMessage = error?.statusCode === 429 ? localize('azure.resource.throttleerror', "Requests from this account have been throttled. To retry, please select a smaller number of subscriptions.") : AzureResourceErrorMessageUtil.getErrorMessage(error);
|
||||
void vscode.window.showErrorMessage(localize('azure.resource.tree.loadresourceerror', "An error occurred while loading Azure resources: {0}", errorMessage));
|
||||
}
|
||||
|
||||
this._isLoading = false;
|
||||
this._onLoadingStatusChanged.fire();
|
||||
}
|
||||
private _totalTenantsCount = 0;
|
||||
private _selectedTenantsCount = 0;
|
||||
private _singleTenantTreeNode: FlatTenantTreeNode | undefined;
|
||||
private _multiTenantTreeNodes: FlatTenantTreeNode[] | undefined;
|
||||
|
||||
private static readonly noTenantsLabel = localize('azure.resource.tree.accountTreeNode.noTenantsLabel', "No Tenants found.");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { AppContext } from '../../appContext';
|
||||
import { TreeNode } from '../treeNode';
|
||||
import { AzureSubscriptionError } from '../errors';
|
||||
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
||||
import { AzureResourceItemType, AzureResourceServiceNames } from '../constants';
|
||||
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService } from '../interfaces';
|
||||
import { AzureAccount, Tenant, azureResource } from 'azurecore';
|
||||
import { AzureResourceService } from '../resourceService';
|
||||
import { AzureResourceResourceTreeNode } from '../resourceTreeNode';
|
||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
||||
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
||||
import { FlatAccountTreeNode } from './flatAccountTreeNode';
|
||||
|
||||
export class FlatTenantTreeNode extends AzureResourceContainerTreeNodeBase {
|
||||
public constructor(
|
||||
public readonly account: AzureAccount,
|
||||
public readonly tenant: Tenant,
|
||||
private readonly parentNode: FlatAccountTreeNode | undefined,
|
||||
appContext: AppContext,
|
||||
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
||||
) {
|
||||
super(appContext, treeChangeHandler, parentNode);
|
||||
|
||||
this._subscriptionService = this.appContext.getService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService);
|
||||
this._subscriptionFilterService = this.appContext.getService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService);
|
||||
this._resourceService = this.appContext.getService<AzureResourceService>(AzureResourceServiceNames.resourceService);
|
||||
|
||||
this._id = `account_${this.account.key.accountId}.tenant_${tenant.id}`;
|
||||
this.setCacheKey(`${this._id}.dataresources`);
|
||||
this._label = this.generateLabel();
|
||||
}
|
||||
|
||||
public async getChildren(): Promise<TreeNode[]> {
|
||||
try {
|
||||
let nodesResult: TreeNode[] = [];
|
||||
let subscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||
if (this._isClearingCache) {
|
||||
subscriptions = await this._subscriptionService.getSubscriptions(this.account, [this.tenant.id]);
|
||||
await this.updateCache<azureResource.AzureResourceSubscription[]>(subscriptions);
|
||||
} else {
|
||||
subscriptions = await this.getCachedSubscriptions();
|
||||
}
|
||||
|
||||
this._totalSubscriptionCount = subscriptions.length;
|
||||
|
||||
const allSubscriptions = await this._subscriptionFilterService.getSelectedSubscriptions(this.account, this.tenant);
|
||||
const selectedSubscriptions = allSubscriptions?.filter(subscription => subscription.tenant === this.tenant.id);
|
||||
const selectedSubscriptionIds = (selectedSubscriptions || <azureResource.AzureResourceSubscription[]>[]).map((subscription) => subscription.id);
|
||||
if (selectedSubscriptionIds.length > 0) {
|
||||
subscriptions = subscriptions.filter((subscription) => selectedSubscriptionIds.indexOf(subscription.id) !== -1);
|
||||
this._selectedSubscriptionCount = selectedSubscriptionIds.length;
|
||||
} else {
|
||||
// ALL subscriptions are listed by default
|
||||
this._selectedSubscriptionCount = this._totalSubscriptionCount;
|
||||
}
|
||||
|
||||
this.refreshLabel();
|
||||
|
||||
if (this._isClearingCache) {
|
||||
this._isClearingCache = false;
|
||||
return [];
|
||||
}
|
||||
|
||||
if (subscriptions.length === 0) {
|
||||
nodesResult = [AzureResourceMessageTreeNode.create(FlatTenantTreeNode.noSubscriptionsLabel, this)];
|
||||
} else {
|
||||
let _nodes: AzureResourceResourceTreeNode[] = [];
|
||||
const resources = await this._resourceService.getAllChildren(this.account, subscriptions, true);
|
||||
if (resources?.length > 0) {
|
||||
_nodes.push(...resources.map(dr => new AzureResourceResourceTreeNode(dr, this.parentNode ?? this, this.appContext)));
|
||||
_nodes = _nodes.sort((a, b) => {
|
||||
return a.getNodeInfo().label.localeCompare(b.getNodeInfo().label);
|
||||
});
|
||||
}
|
||||
nodesResult = _nodes;
|
||||
}
|
||||
|
||||
return nodesResult;
|
||||
} catch (error) {
|
||||
if (error instanceof AzureSubscriptionError) {
|
||||
void vscode.commands.executeCommand('azure.resource.signin');
|
||||
}
|
||||
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
|
||||
}
|
||||
}
|
||||
|
||||
public async getCachedSubscriptions(): Promise<azureResource.AzureResourceSubscription[]> {
|
||||
return this.getCache<azureResource.AzureResourceSubscription[]>() ?? [];
|
||||
}
|
||||
|
||||
public getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem> {
|
||||
const item = new vscode.TreeItem(this._label, vscode.TreeItemCollapsibleState.Collapsed);
|
||||
item.id = this._id;
|
||||
item.contextValue = AzureResourceItemType.tenant;
|
||||
item.iconPath = this.appContext.extensionContext.asAbsolutePath('resources/tenant.svg');
|
||||
return item;
|
||||
}
|
||||
|
||||
protected refreshLabel(): void {
|
||||
const newLabel = this.generateLabel();
|
||||
if (this._label !== newLabel) {
|
||||
this._label = newLabel;
|
||||
this.treeChangeHandler.notifyNodeChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
private generateLabel(): string {
|
||||
let label = this.tenant.displayName;
|
||||
|
||||
if (this._totalSubscriptionCount !== 0) {
|
||||
label += ` (${this._selectedSubscriptionCount} / ${this._totalSubscriptionCount} subscriptions)`;
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
public getNodeInfo(): azdata.NodeInfo {
|
||||
return {
|
||||
label: this._label,
|
||||
isLeaf: false,
|
||||
errorMessage: undefined,
|
||||
metadata: undefined,
|
||||
nodePath: this.generateNodePath(),
|
||||
parentNodePath: this.parent?.generateNodePath() ?? '',
|
||||
nodeStatus: undefined,
|
||||
nodeType: AzureResourceItemType.tenant,
|
||||
nodeSubType: undefined,
|
||||
iconType: AzureResourceItemType.tenant
|
||||
};
|
||||
}
|
||||
|
||||
public get nodePathValue(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
private _subscriptionService: IAzureResourceSubscriptionService;
|
||||
private _subscriptionFilterService: IAzureResourceSubscriptionFilterService;
|
||||
private _resourceService: AzureResourceService;
|
||||
|
||||
private _id: string;
|
||||
private _label: string;
|
||||
public _totalSubscriptionCount = 0;
|
||||
public _selectedSubscriptionCount = 0;
|
||||
|
||||
private static readonly noSubscriptionsLabel = localize('azure.resource.tree.accountTreeNode.noSubscriptionsLabel', "No Subscriptions found.");
|
||||
}
|
||||
|
||||
export async function getSubscriptionInfo(account: AzureAccount, tenant: Tenant, subscriptionService: IAzureResourceSubscriptionService, subscriptionFilterService: IAzureResourceSubscriptionFilterService): Promise<{
|
||||
subscriptions: azureResource.AzureResourceSubscription[],
|
||||
total: number,
|
||||
selected: number
|
||||
}> {
|
||||
let subscriptions = await subscriptionService.getSubscriptions(account, [tenant.id]);
|
||||
const total = subscriptions.length;
|
||||
let selected = total;
|
||||
|
||||
const selectedSubscriptions = await subscriptionFilterService.getSelectedSubscriptions(account, tenant);
|
||||
const selectedSubscriptionIds = (selectedSubscriptions || <azureResource.AzureResourceSubscription[]>[]).map((subscription) => subscription.id);
|
||||
if (selectedSubscriptionIds.length > 0) {
|
||||
subscriptions = subscriptions.filter((subscription) => selectedSubscriptionIds.indexOf(subscription.id) !== -1);
|
||||
selected = selectedSubscriptionIds.length;
|
||||
}
|
||||
return {
|
||||
subscriptions,
|
||||
total,
|
||||
selected
|
||||
};
|
||||
}
|
||||
@@ -18,20 +18,20 @@ import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
||||
import { AzureResourceService } from '../resourceService';
|
||||
import { AzureResourceResourceTreeNode } from '../resourceTreeNode';
|
||||
import { AzureAccount, azureResource } from 'azurecore';
|
||||
import { AzureAccount, Tenant, azureResource } from 'azurecore';
|
||||
|
||||
export class AzureResourceSubscriptionTreeNode extends AzureResourceContainerTreeNodeBase {
|
||||
public constructor(
|
||||
public readonly account: AzureAccount,
|
||||
public readonly subscription: azureResource.AzureResourceSubscription,
|
||||
public readonly tenantId: string,
|
||||
public readonly tenant: Tenant,
|
||||
appContext: AppContext,
|
||||
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
||||
parent: TreeNode
|
||||
) {
|
||||
super(appContext, treeChangeHandler, parent);
|
||||
|
||||
this._id = `account_${this.account.key.accountId}.subscription_${this.subscription.id}.tenant_${this.tenantId}`;
|
||||
this._id = `account_${this.account.key.accountId}.tenant_${this.tenant.id}.subscription_${this.subscription.id}`;
|
||||
this.setCacheKey(`${this._id}.resources`);
|
||||
}
|
||||
|
||||
@@ -62,10 +62,7 @@ export class AzureResourceSubscriptionTreeNode extends AzureResourceContainerTre
|
||||
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
||||
const item = new TreeItem(this.subscription.name, TreeItemCollapsibleState.Collapsed);
|
||||
item.contextValue = AzureResourceItemType.subscription;
|
||||
item.iconPath = {
|
||||
dark: this.appContext.extensionContext.asAbsolutePath('resources/dark/subscription_inverse.svg'),
|
||||
light: this.appContext.extensionContext.asAbsolutePath('resources/light/subscription.svg')
|
||||
};
|
||||
item.iconPath = this.appContext.extensionContext.asAbsolutePath('resources/subscriptions.svg');
|
||||
return item;
|
||||
}
|
||||
|
||||
|
||||
156
extensions/azurecore/src/azureResource/tree/tenantTreeNode.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { AppContext } from '../../appContext';
|
||||
import { TreeNode } from '../treeNode';
|
||||
import { AzureSubscriptionError } from '../errors';
|
||||
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
||||
import { AzureResourceItemType, AzureResourceServiceNames } from '../constants';
|
||||
import { AzureResourceSubscriptionTreeNode } from './subscriptionTreeNode';
|
||||
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
||||
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService } from '../../azureResource/interfaces';
|
||||
import { AzureAccount, Tenant, azureResource } from 'azurecore';
|
||||
import { AzureResourceAccountTreeNode } from './accountTreeNode';
|
||||
|
||||
export class AzureResourceTenantTreeNode extends AzureResourceContainerTreeNodeBase {
|
||||
public constructor(
|
||||
public readonly account: AzureAccount,
|
||||
public readonly tenant: Tenant,
|
||||
parentNode: AzureResourceAccountTreeNode,
|
||||
appContext: AppContext,
|
||||
treeChangeHandler: IAzureResourceTreeChangeHandler
|
||||
) {
|
||||
super(appContext, treeChangeHandler, parentNode);
|
||||
|
||||
this._subscriptionService = this.appContext.getService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService);
|
||||
this._subscriptionFilterService = this.appContext.getService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService);
|
||||
|
||||
this._id = `account_${this.account.key.accountId}.tenant_${tenant.id}`;
|
||||
this.setCacheKey(`${this._id}.subscriptions`);
|
||||
this._label = this.generateLabel();
|
||||
}
|
||||
|
||||
public async getChildren(): Promise<TreeNode[]> {
|
||||
try {
|
||||
let subscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||
|
||||
if (this._isClearingCache) {
|
||||
subscriptions = await this._subscriptionService.getSubscriptions(this.account, [this.tenant.id]);
|
||||
await this.updateCache<azureResource.AzureResourceSubscription[]>(subscriptions);
|
||||
this._isClearingCache = false;
|
||||
} else {
|
||||
subscriptions = await this.getCachedSubscriptions();
|
||||
}
|
||||
|
||||
this._totalSubscriptionCount = subscriptions.length;
|
||||
|
||||
const allSubscriptions = await this._subscriptionFilterService.getSelectedSubscriptions(this.account, this.tenant);
|
||||
const selectedSubscriptions = allSubscriptions?.filter(subscription => subscription.tenant === this.tenant.id);
|
||||
const selectedSubscriptionIds = (selectedSubscriptions || <azureResource.AzureResourceSubscription[]>[]).map((subscription) => subscription.id);
|
||||
if (selectedSubscriptionIds.length > 0) {
|
||||
subscriptions = subscriptions.filter((subscription) => selectedSubscriptionIds.indexOf(subscription.id) !== -1);
|
||||
this._selectedSubscriptionCount = selectedSubscriptionIds.length;
|
||||
} else {
|
||||
// ALL subscriptions are listed by default
|
||||
this._selectedSubscriptionCount = this._totalSubscriptionCount;
|
||||
}
|
||||
|
||||
this.refreshLabel();
|
||||
|
||||
if (subscriptions.length === 0) {
|
||||
return [AzureResourceMessageTreeNode.create(AzureResourceTenantTreeNode.noSubscriptionsLabel, this)];
|
||||
} else {
|
||||
let subTreeNodes = await Promise.all(subscriptions.map(async (subscription) => {
|
||||
return new AzureResourceSubscriptionTreeNode(this.account, subscription, this.tenant, this.appContext, this.treeChangeHandler, this);
|
||||
}));
|
||||
return subTreeNodes.sort((a, b) => a.subscription.name.localeCompare(b.subscription.name));
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof AzureSubscriptionError) {
|
||||
void vscode.commands.executeCommand('azure.resource.signin');
|
||||
}
|
||||
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
|
||||
}
|
||||
}
|
||||
|
||||
public async getCachedSubscriptions(): Promise<azureResource.AzureResourceSubscription[]> {
|
||||
return this.getCache<azureResource.AzureResourceSubscription[]>() ?? [];
|
||||
}
|
||||
|
||||
public getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem> {
|
||||
const item = new vscode.TreeItem(this._label, vscode.TreeItemCollapsibleState.Collapsed);
|
||||
item.id = this._id;
|
||||
item.contextValue = AzureResourceItemType.tenant;
|
||||
item.iconPath = this.appContext.extensionContext.asAbsolutePath('resources/tenant.svg');
|
||||
return item;
|
||||
}
|
||||
|
||||
public getNodeInfo(): azdata.NodeInfo {
|
||||
return {
|
||||
label: this._label,
|
||||
isLeaf: false,
|
||||
errorMessage: undefined,
|
||||
metadata: undefined,
|
||||
nodePath: this.generateNodePath(),
|
||||
parentNodePath: this.parent?.generateNodePath() ?? '',
|
||||
nodeStatus: undefined,
|
||||
nodeType: AzureResourceItemType.tenant,
|
||||
nodeSubType: undefined,
|
||||
iconType: AzureResourceItemType.tenant
|
||||
};
|
||||
}
|
||||
|
||||
public get nodePathValue(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public get totalSubscriptionCount(): number {
|
||||
return this._totalSubscriptionCount;
|
||||
}
|
||||
|
||||
public get selectedSubscriptionCount(): number {
|
||||
return this._selectedSubscriptionCount;
|
||||
}
|
||||
|
||||
protected refreshLabel(): void {
|
||||
const newLabel = this.generateLabel();
|
||||
if (this._label !== newLabel) {
|
||||
this._label = newLabel;
|
||||
this.treeChangeHandler.notifyNodeChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
private generateLabel(): string {
|
||||
let label = this.tenant.displayName;
|
||||
|
||||
if (this._totalSubscriptionCount !== 0) {
|
||||
label += ` (${this._selectedSubscriptionCount} / ${this._totalSubscriptionCount} subscriptions)`;
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
private _subscriptionService: IAzureResourceSubscriptionService;
|
||||
private _subscriptionFilterService: IAzureResourceSubscriptionFilterService;
|
||||
|
||||
private _id: string;
|
||||
private _label: string;
|
||||
private _totalSubscriptionCount = 0;
|
||||
private _selectedSubscriptionCount = 0;
|
||||
|
||||
private static readonly noSubscriptionsLabel = localize('azure.resource.tree.accountTreeNode.noSubscriptionsLabel', "No Subscriptions found.");
|
||||
|
||||
sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ export class AzureResourceTreeProvider implements vscode.TreeDataProvider<TreeNo
|
||||
|
||||
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
|
||||
if (element) {
|
||||
return element.getChildren(true);
|
||||
return element.getChildren();
|
||||
}
|
||||
|
||||
if (!this.isSystemInitialized) {
|
||||
@@ -55,11 +55,15 @@ export class AzureResourceTreeProvider implements vscode.TreeDataProvider<TreeNo
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.accounts && this.accounts.length > 0) {
|
||||
this.accounts = filterAccounts(this.accounts, this.authLibrary);
|
||||
return this.accounts.map((account) => new AzureResourceAccountTreeNode(account, this.appContext, this));
|
||||
if (this.accounts) {
|
||||
if (this.accounts.length === 0) {
|
||||
return [new AzureResourceAccountNotSignedInTreeNode()];
|
||||
} else {
|
||||
this.accounts = filterAccounts(this.accounts, this.authLibrary);
|
||||
return this.accounts.map((account) => new AzureResourceAccountTreeNode(account, this.appContext, this));
|
||||
}
|
||||
} else {
|
||||
return [new AzureResourceAccountNotSignedInTreeNode()];
|
||||
return [AzureResourceMessageTreeNode.create(localize('azure.resource.tree.treeProvider.loadingLabel', "Loading ..."), undefined)];
|
||||
}
|
||||
} catch (error) {
|
||||
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), undefined)];
|
||||
@@ -68,7 +72,10 @@ export class AzureResourceTreeProvider implements vscode.TreeDataProvider<TreeNo
|
||||
|
||||
private async loadAccounts(): Promise<void> {
|
||||
try {
|
||||
this.accounts = filterAccounts(await azdata.accounts.getAllAccounts(), this.authLibrary);
|
||||
let accounts = await azdata.accounts.getAllAccounts();
|
||||
if (accounts) {
|
||||
this.accounts = filterAccounts(accounts, this.authLibrary);
|
||||
}
|
||||
// System has been initialized
|
||||
this.setSystemInitialized();
|
||||
this._onDidChangeTreeData.fire(undefined);
|
||||
|
||||
@@ -39,7 +39,7 @@ export abstract class TreeNode {
|
||||
}
|
||||
|
||||
// TODO support filtering by already expanded / not yet expanded
|
||||
let children = await node.getChildren(false);
|
||||
let children = await node.getChildren();
|
||||
if (children) {
|
||||
for (let child of children) {
|
||||
if (filter && filter(child)) {
|
||||
@@ -55,7 +55,7 @@ export abstract class TreeNode {
|
||||
|
||||
public parent: TreeNode | undefined = undefined;
|
||||
|
||||
public abstract getChildren(refreshChildren: boolean): TreeNode[] | Promise<TreeNode[]>;
|
||||
public abstract getChildren(): TreeNode[] | Promise<TreeNode[]>;
|
||||
public abstract getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem>;
|
||||
|
||||
public abstract getNodeInfo(): azdata.NodeInfo;
|
||||
|
||||
@@ -14,7 +14,7 @@ import { EOL } from 'os';
|
||||
import { AppContext } from '../appContext';
|
||||
import { invalidAzureAccount, invalidTenant, unableToFetchTokenError } from '../localizedConstants';
|
||||
import { AzureResourceServiceNames } from './constants';
|
||||
import { IAzureResourceSubscriptionFilterService, IAzureResourceSubscriptionService } from './interfaces';
|
||||
import { IAzureResourceSubscriptionFilterService, IAzureResourceSubscriptionService, IAzureResourceTenantFilterService } from './interfaces';
|
||||
import { AzureResourceGroupService } from './providers/resourceGroup/resourceGroupService';
|
||||
import { BlobServiceClient, StorageSharedKeyCredential } from '@azure/storage-blob';
|
||||
import providerSettings from '../account-provider/providerSettings';
|
||||
@@ -418,8 +418,12 @@ export async function getSelectedSubscriptions(appContext: AppContext, account?:
|
||||
}
|
||||
|
||||
const subscriptionFilterService = appContext.getService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService);
|
||||
const tenantFilterService = appContext.getService<IAzureResourceTenantFilterService>(AzureResourceServiceNames.tenantFilterService);
|
||||
try {
|
||||
result.subscriptions.push(...await subscriptionFilterService.getSelectedSubscriptions(account));
|
||||
const tenants = await tenantFilterService.getSelectedTenants(account);
|
||||
for (const tenant of tenants) {
|
||||
result.subscriptions.push(...await subscriptionFilterService.getSelectedSubscriptions(account, tenant));
|
||||
}
|
||||
} catch (err) {
|
||||
const error = new Error(localize('azure.accounts.getSelectedSubscriptions.queryError', "Error fetching subscriptions for account {0} : {1}",
|
||||
account.displayInfo.displayName,
|
||||
|
||||
@@ -12,7 +12,7 @@ import * as os from 'os';
|
||||
import { AppContext } from './appContext';
|
||||
import { AzureAccountProviderService } from './account-provider/azureAccountProviderService';
|
||||
import { AzureResourceService } from './azureResource/resourceService';
|
||||
import { IAzureResourceCacheService, IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService, IAzureTerminalService } from './azureResource/interfaces';
|
||||
import { IAzureResourceCacheService, IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService, IAzureTerminalService, IAzureResourceTenantFilterService } from './azureResource/interfaces';
|
||||
import { AzureResourceServiceNames } from './azureResource/constants';
|
||||
import { AzureResourceSubscriptionService } from './azureResource/services/subscriptionService';
|
||||
import { AzureResourceSubscriptionFilterService } from './azureResource/services/subscriptionFilterService';
|
||||
@@ -29,11 +29,10 @@ import { AzureResourceGroupService } from './azureResource/providers/resourceGro
|
||||
import { Logger } from './utils/Logger';
|
||||
import { ConnectionDialogTreeProvider } from './azureResource/tree/connectionDialogTreeProvider';
|
||||
import { AzureDataGridProvider } from './azureDataGridProvider';
|
||||
// import { AzureResourceUniversalService } from './azureResource/providers/universal/universalService';
|
||||
import { AzureResourceUniversalService } from './azureResource/providers/universal/universalService';
|
||||
import { AzureResourceUniversalTreeDataProvider } from './azureResource/providers/universal/universalTreeDataProvider';
|
||||
import { AzureResourceUniversalResourceProvider } from './azureResource/providers/universal/universalProvider';
|
||||
// import { AzureResourceUniversalTreeDataProvider } from './azureResource/providers/universal/universalTreeDataProvider';
|
||||
import { AzureResourceTenantFilterService } from './azureResource/services/tenantFilterService';
|
||||
|
||||
let extensionContext: vscode.ExtensionContext;
|
||||
|
||||
@@ -275,6 +274,7 @@ function registerAzureServices(appContext: AppContext): void {
|
||||
appContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, new AzureResourceCacheService(extensionContext));
|
||||
appContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, new AzureResourceSubscriptionService());
|
||||
appContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, new AzureResourceSubscriptionFilterService(new AzureResourceCacheService(extensionContext)));
|
||||
appContext.registerService<IAzureResourceTenantFilterService>(AzureResourceServiceNames.tenantFilterService, new AzureResourceTenantFilterService(new AzureResourceCacheService(extensionContext)));
|
||||
appContext.registerService<IAzureTerminalService>(AzureResourceServiceNames.terminalService, new AzureTerminalService(extensionContext));
|
||||
}
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ const mockDatabases: azureResource.AzureResourceDatabase[] = [
|
||||
name: 'mock database 1',
|
||||
id: 'mock-id-1',
|
||||
provider: DATABASE_PROVIDER_ID,
|
||||
tenant: 'mockTenantId',
|
||||
serverName: 'mock database server 1',
|
||||
serverFullName: 'mock database server full name 1',
|
||||
loginName: 'mock login',
|
||||
@@ -90,6 +91,7 @@ const mockDatabases: azureResource.AzureResourceDatabase[] = [
|
||||
name: 'mock database 2',
|
||||
id: 'mock-id-2',
|
||||
provider: DATABASE_PROVIDER_ID,
|
||||
tenant: 'mockTenantId',
|
||||
serverName: 'mock database server 2',
|
||||
serverFullName: 'mock database server full name 2',
|
||||
loginName: 'mock login',
|
||||
@@ -140,7 +142,7 @@ describe('AzureResourceDatabaseTreeDataProvider.getChildren', function (): void
|
||||
|
||||
const child = children[0];
|
||||
should(child.id).equal('azure.resource.providers.database.treeDataProvider.databaseContainer');
|
||||
should(child.label).equal('SQL database');
|
||||
should(child.label).equal('SQL databases');
|
||||
should(child.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
|
||||
should(child.contextValue).equal('azure.resource.itemType.databaseContainer');
|
||||
});
|
||||
@@ -160,7 +162,7 @@ describe('AzureResourceDatabaseTreeDataProvider.getChildren', function (): void
|
||||
should(child.account).equal(mockAccount);
|
||||
should(child.subscription).equal(mockSubscription);
|
||||
should(child.tenantId).equal(mockTenantId);
|
||||
should(child.treeItem.id).equal(`databaseServer_${mockAccount.key.accountId}${database.serverFullName}.database_${database.id}`);
|
||||
should(child.treeItem.id).equal(`database_${mockAccount.key.accountId}${database.tenant}${database.serverFullName}.database_${database.id}`);
|
||||
should(child.treeItem.label).equal(`${database.name} (${database.serverName})`);
|
||||
should(child.treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
|
||||
should(child.treeItem.contextValue).equal(AzureResourceItemType.database);
|
||||
|
||||
@@ -60,7 +60,7 @@ const mockResourceRootNode: azureResource.IAzureResourceNode = {
|
||||
treeItem: {
|
||||
id: 'mock_resource_root_node',
|
||||
label: 'mock resource root node',
|
||||
iconPath: undefined,
|
||||
iconPath: '',
|
||||
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: 'mock_resource_root_node'
|
||||
}
|
||||
@@ -76,6 +76,7 @@ const mockDatabaseServers: azureResource.AzureResourceDatabaseServer[] = [
|
||||
name: 'mock database server 1',
|
||||
id: 'mock-id-1',
|
||||
provider: DATABASE_SERVER_PROVIDER_ID,
|
||||
tenant: 'mockTenantId',
|
||||
fullName: 'mock database server full name 1',
|
||||
loginName: 'mock login',
|
||||
defaultDatabaseName: 'master',
|
||||
@@ -89,6 +90,7 @@ const mockDatabaseServers: azureResource.AzureResourceDatabaseServer[] = [
|
||||
name: 'mock database server 2',
|
||||
id: 'mock-id-2',
|
||||
provider: DATABASE_SERVER_PROVIDER_ID,
|
||||
tenant: 'mockTenantId',
|
||||
fullName: 'mock database server full name 2',
|
||||
loginName: 'mock login',
|
||||
defaultDatabaseName: 'master',
|
||||
@@ -139,7 +141,7 @@ describe('AzureResourceDatabaseServerTreeDataProvider.getChildren', function ():
|
||||
|
||||
const child = children[0];
|
||||
should(child.id).equal('azure.resource.providers.databaseServer.treeDataProvider.databaseServerContainer');
|
||||
should(child.label).equal('SQL server');
|
||||
should(child.label).equal('SQL servers');
|
||||
should(child.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
|
||||
should(child.contextValue).equal('azure.resource.itemType.databaseServerContainer');
|
||||
});
|
||||
@@ -159,7 +161,7 @@ describe('AzureResourceDatabaseServerTreeDataProvider.getChildren', function ():
|
||||
should(child.account).equal(mockAccount);
|
||||
should(child.subscription).equal(mockSubscription);
|
||||
should(child.tenantId).equal(mockTenantId);
|
||||
should(child.treeItem.id).equal(`databaseServer_${mockAccount.key.accountId}${databaseServer.id}`);
|
||||
should(child.treeItem.id).equal(`databaseServer_${mockAccount.key.accountId}${databaseServer.tenant}${databaseServer.id}`);
|
||||
should(child.treeItem.label).equal(databaseServer.name);
|
||||
should(child.treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
|
||||
should(child.treeItem.contextValue).equal(AzureResourceItemType.databaseServer);
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
IAzureResourceCacheService,
|
||||
IAzureResourceSubscriptionService,
|
||||
IAzureResourceSubscriptionFilterService,
|
||||
IAzureResourceTenantFilterService,
|
||||
} from '../../../azureResource/interfaces';
|
||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
||||
import { AzureResourceAccountTreeNode } from '../../../azureResource/tree/accountTreeNode';
|
||||
@@ -24,15 +25,15 @@ import { AzureResourceMessageTreeNode } from '../../../azureResource/messageTree
|
||||
import { generateGuid } from '../../../azureResource/utils';
|
||||
import { AzureAccount, azureResource } from 'azurecore';
|
||||
import allSettings from '../../../account-provider/providerSettings';
|
||||
import { AzureResourceTenantTreeNode } from '../../../azureResource/tree/tenantTreeNode';
|
||||
|
||||
// Mock services
|
||||
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
|
||||
let mockSubscriptionServiceADAL: TypeMoq.IMock<IAzureResourceSubscriptionService>;
|
||||
let mockSubscriptionServiceMSAL: TypeMoq.IMock<IAzureResourceSubscriptionService>;
|
||||
let mockSubscriptionService: TypeMoq.IMock<IAzureResourceSubscriptionService>;
|
||||
let mockSubscriptionFilterService: TypeMoq.IMock<IAzureResourceSubscriptionFilterService>;
|
||||
let mockAppContextADAL: AppContext;
|
||||
let mockAppContextMSAL: AppContext;
|
||||
let mockTenantFilterService: TypeMoq.IMock<IAzureResourceTenantFilterService>;
|
||||
let mockAppContext: AppContext;
|
||||
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
||||
|
||||
// Mock test data
|
||||
@@ -41,6 +42,13 @@ const mockTenant = {
|
||||
id: mockTenantId,
|
||||
displayName: 'Mock Tenant'
|
||||
};
|
||||
const mockTenantAlternative = {
|
||||
id: 'mock_tenant_id_alt',
|
||||
displayName: 'Mock Tenant Alternative'
|
||||
};
|
||||
|
||||
const mockTenants = [mockTenant, mockTenantAlternative];
|
||||
|
||||
const mockAccount: AzureAccount = {
|
||||
key: {
|
||||
accountId: '97915f6d-84fa-4926-b60c-38db64327ad7',
|
||||
@@ -54,9 +62,7 @@ const mockAccount: AzureAccount = {
|
||||
email: '97915f6d-84fa-4926-b60c-38db64327ad7'
|
||||
},
|
||||
properties: {
|
||||
tenants: [
|
||||
mockTenant
|
||||
],
|
||||
tenants: mockTenants,
|
||||
owningTenant: mockTenant,
|
||||
providerSettings: {
|
||||
settings: allSettings[0].metadata.settings,
|
||||
@@ -68,6 +74,8 @@ const mockAccount: AzureAccount = {
|
||||
isStale: false
|
||||
};
|
||||
|
||||
const mockFilteredTenants = [mockTenant];
|
||||
|
||||
const mock_subscription_id_1 = 'mock_subscription_1';
|
||||
const mockSubscription1: azureResource.AzureResourceSubscription = {
|
||||
id: mock_subscription_id_1,
|
||||
@@ -97,25 +105,20 @@ describe('AzureResourceAccountTreeNode.info', function (): void {
|
||||
beforeEach(() => {
|
||||
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||
mockSubscriptionServiceADAL = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
||||
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionServiceMSAL = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
||||
mockSubscriptionServiceMSAL.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
||||
mockTenantFilterService = TypeMoq.Mock.ofType<IAzureResourceTenantFilterService>();
|
||||
|
||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||
|
||||
mockSubscriptionCache = [];
|
||||
|
||||
mockAppContextADAL = new AppContext(mockExtensionContext.object);
|
||||
mockAppContextADAL.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||
mockAppContextADAL.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionServiceADAL.object);
|
||||
mockAppContextADAL.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
|
||||
|
||||
mockAppContextMSAL = new AppContext(mockExtensionContext.object);
|
||||
mockAppContextMSAL.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||
mockAppContextMSAL.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionServiceMSAL.object);
|
||||
mockAppContextMSAL.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
|
||||
mockAppContext = new AppContext(mockExtensionContext.object);
|
||||
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
|
||||
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
|
||||
mockAppContext.registerService<IAzureResourceTenantFilterService>(AzureResourceServiceNames.tenantFilterService, mockTenantFilterService.object);
|
||||
|
||||
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
|
||||
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockSubscriptionCache);
|
||||
@@ -129,8 +132,8 @@ describe('AzureResourceAccountTreeNode.info', function (): void {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('Should be correct when created for ADAL.', async function (): Promise<void> {
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
|
||||
it('Should be correct when created.', async function (): Promise<void> {
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
|
||||
const accountTreeNodeId = `account_${mockAccount.key.accountId}`;
|
||||
|
||||
@@ -139,7 +142,7 @@ describe('AzureResourceAccountTreeNode.info', function (): void {
|
||||
const treeItem = await accountTreeNode.getTreeItem();
|
||||
should(treeItem.id).equal(accountTreeNodeId);
|
||||
should(treeItem.label).equal(mockAccount.displayInfo.displayName);
|
||||
should(treeItem.contextValue).equal(AzureResourceItemType.account);
|
||||
should(treeItem.contextValue).equal(AzureResourceItemType.multipleTenantAccount);
|
||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
|
||||
|
||||
const nodeInfo = accountTreeNode.getNodeInfo();
|
||||
@@ -149,146 +152,126 @@ describe('AzureResourceAccountTreeNode.info', function (): void {
|
||||
should(nodeInfo.iconType).equal(AzureResourceItemType.account);
|
||||
});
|
||||
|
||||
it('Should be correct when created for MSAL.', async function (): Promise<void> {
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextMSAL, mockTreeChangeHandler.object);
|
||||
|
||||
const accountTreeNodeId = `account_${mockAccount.key.accountId}`;
|
||||
|
||||
should(accountTreeNode.nodePathValue).equal(accountTreeNodeId);
|
||||
|
||||
const treeItem = await accountTreeNode.getTreeItem();
|
||||
should(treeItem.id).equal(accountTreeNodeId);
|
||||
should(treeItem.label).equal(mockAccount.displayInfo.displayName);
|
||||
should(treeItem.contextValue).equal(AzureResourceItemType.account);
|
||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
|
||||
|
||||
const nodeInfo = accountTreeNode.getNodeInfo();
|
||||
should(nodeInfo.label).equal(mockAccount.displayInfo.displayName);
|
||||
should(nodeInfo.isLeaf).false();
|
||||
should(nodeInfo.nodeType).equal(AzureResourceItemType.account);
|
||||
should(nodeInfo.iconType).equal(AzureResourceItemType.account);
|
||||
});
|
||||
|
||||
it('Should be correct when there are subscriptions listed for ADAL.', async function (): Promise<void> {
|
||||
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve([]));
|
||||
it('Should be correct when there are tenants available.', async function (): Promise<void> {
|
||||
mockTenantFilterService.setup((o) => o.getSelectedTenants(mockAccount)).returns(() => Promise.resolve(mockFilteredTenants));
|
||||
sinon.stub(azdata.accounts, 'getAccountSecurityToken').resolves(mockToken);
|
||||
|
||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
|
||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockFilteredTenants.length} / ${mockTenants.length} tenants)`;
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
|
||||
const subscriptionNodes = await accountTreeNode.getChildren();
|
||||
const tenantNodes = await accountTreeNode.getChildren();
|
||||
|
||||
should(tenantNodes).Array();
|
||||
should(tenantNodes.length).equal(mockFilteredTenants.length);
|
||||
|
||||
const treeItem = await accountTreeNode.getTreeItem();
|
||||
should(treeItem.label).equal(accountTreeNodeLabel);
|
||||
|
||||
const nodeInfo = accountTreeNode.getNodeInfo();
|
||||
should(nodeInfo.label).equal(accountTreeNodeLabel);
|
||||
});
|
||||
|
||||
it('Should be correct when there are subscriptions listed.', async function (): Promise<void> {
|
||||
mockTenantFilterService.setup((o) => o.getSelectedTenants(mockAccount)).returns(() => Promise.resolve(mockFilteredTenants));
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount, mockTenant)).returns(() => Promise.resolve([]));
|
||||
sinon.stub(azdata.accounts, 'getAccountSecurityToken').resolves(mockToken);
|
||||
|
||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockFilteredTenants.length} / ${mockTenants.length} tenants)`;
|
||||
const tenantTreeNodeLabel = `${mockTenant.displayName} (${mockSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
|
||||
|
||||
// Validate account tree node
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
const tenantNodes = await accountTreeNode.getChildren();
|
||||
|
||||
should(tenantNodes).Array();
|
||||
should(tenantNodes.length).equal(mockFilteredTenants.length);
|
||||
|
||||
const treeItem = await accountTreeNode.getTreeItem();
|
||||
should(treeItem.label).equal(accountTreeNodeLabel);
|
||||
|
||||
const nodeInfo = accountTreeNode.getNodeInfo();
|
||||
should(nodeInfo.label).equal(accountTreeNodeLabel);
|
||||
|
||||
// Validate tenant tree node
|
||||
const tenantTreeNode = tenantNodes[0];
|
||||
const subscriptions = await tenantTreeNode.getChildren();
|
||||
|
||||
should(subscriptions).Array();
|
||||
should(subscriptions.length).equal(mockSubscriptions.length);
|
||||
|
||||
const subTreeItem = await tenantTreeNode.getTreeItem();
|
||||
should(subTreeItem.label).equal(tenantTreeNodeLabel);
|
||||
|
||||
const subNodeInfo = tenantTreeNode.getNodeInfo();
|
||||
should(subNodeInfo.label).equal(tenantTreeNodeLabel);
|
||||
});
|
||||
|
||||
it('Should only show subscriptions with valid tokens.', async function (): Promise<void> {
|
||||
mockTenantFilterService.setup((o) => o.getSelectedTenants(mockAccount)).returns(() => Promise.resolve(mockTenants));
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount, mockTenant)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
sinon.stub(azdata.accounts, 'getAccountSecurityToken').onFirstCall().resolves(mockToken);
|
||||
|
||||
const tenantTreeNodeLabel = `${mockTenant.displayName} (${mockSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
|
||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockTenants.length} / ${mockTenants.length} tenants)`;
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
const tenantTreeNode = (await accountTreeNode.getChildren())[0];
|
||||
const subscriptionNodes = await tenantTreeNode.getChildren();
|
||||
|
||||
// Validate account tree node
|
||||
const treeItem = await accountTreeNode.getTreeItem();
|
||||
should(treeItem.label).equal(accountTreeNodeLabel);
|
||||
|
||||
const nodeInfo = accountTreeNode.getNodeInfo();
|
||||
should(nodeInfo.label).equal(accountTreeNodeLabel);
|
||||
|
||||
// Validate tenant tree node
|
||||
const tenantTreeItem = await tenantTreeNode.getTreeItem();
|
||||
should(tenantTreeItem.label).equal(tenantTreeNodeLabel);
|
||||
|
||||
const tenantNodeInfo = tenantTreeNode.getNodeInfo();
|
||||
should(tenantNodeInfo.label).equal(tenantTreeNodeLabel);
|
||||
|
||||
should(subscriptionNodes).Array();
|
||||
should(subscriptionNodes.length).equal(mockSubscriptions.length);
|
||||
|
||||
const treeItem = await accountTreeNode.getTreeItem();
|
||||
should(treeItem.label).equal(accountTreeNodeLabel);
|
||||
|
||||
const nodeInfo = accountTreeNode.getNodeInfo();
|
||||
should(nodeInfo.label).equal(accountTreeNodeLabel);
|
||||
});
|
||||
|
||||
it('Should be correct when there are subscriptions listed for MSAL.', async function (): Promise<void> {
|
||||
mockSubscriptionServiceMSAL.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve([]));
|
||||
it('Should be correct when there are subscriptions filtered.', async function (): Promise<void> {
|
||||
mockTenantFilterService.setup((o) => o.getSelectedTenants(mockAccount)).returns(() => Promise.resolve(mockFilteredTenants));
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount, mockTenant)).returns(() => Promise.resolve(mockFilteredSubscriptions));
|
||||
sinon.stub(azdata.accounts, 'getAccountSecurityToken').resolves(mockToken);
|
||||
|
||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
|
||||
const tenantTreeNodeLabel = `${mockTenant.displayName} (${mockFilteredSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
|
||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockFilteredTenants.length} / ${mockTenants.length} tenants)`;
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextMSAL, mockTreeChangeHandler.object);
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
const tenantNodes = await accountTreeNode.getChildren();
|
||||
|
||||
const subscriptionNodes = await accountTreeNode.getChildren();
|
||||
should(tenantNodes).Array();
|
||||
should(tenantNodes.length).equal(mockFilteredTenants.length);
|
||||
|
||||
should(subscriptionNodes).Array();
|
||||
should(subscriptionNodes.length).equal(mockSubscriptions.length);
|
||||
const tenantTreeNode = tenantNodes[0];
|
||||
const subscriptionNodes = await tenantTreeNode.getChildren();
|
||||
|
||||
const treeItem = await accountTreeNode.getTreeItem();
|
||||
should(treeItem.label).equal(accountTreeNodeLabel);
|
||||
|
||||
const nodeInfo = accountTreeNode.getNodeInfo();
|
||||
should(nodeInfo.label).equal(accountTreeNodeLabel);
|
||||
});
|
||||
|
||||
it('Should only show subscriptions with valid tokens for ADAL.', async function (): Promise<void> {
|
||||
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
|
||||
sinon.stub(azdata.accounts, 'getAccountSecurityToken').onFirstCall().resolves(mockToken);
|
||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockFilteredSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
|
||||
|
||||
const subscriptionNodes = await accountTreeNode.getChildren();
|
||||
|
||||
should(subscriptionNodes).Array();
|
||||
should(subscriptionNodes.length).equal(1);
|
||||
|
||||
const treeItem = await accountTreeNode.getTreeItem();
|
||||
should(treeItem.label).equal(accountTreeNodeLabel);
|
||||
|
||||
const nodeInfo = accountTreeNode.getNodeInfo();
|
||||
should(nodeInfo.label).equal(accountTreeNodeLabel);
|
||||
});
|
||||
|
||||
it('Should only show subscriptions with valid tokens for MSAL.', async function (): Promise<void> {
|
||||
mockSubscriptionServiceMSAL.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
|
||||
sinon.stub(azdata.accounts, 'getAccountSecurityToken').onFirstCall().resolves(mockToken);
|
||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockFilteredSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextMSAL, mockTreeChangeHandler.object);
|
||||
|
||||
const subscriptionNodes = await accountTreeNode.getChildren();
|
||||
|
||||
should(subscriptionNodes).Array();
|
||||
should(subscriptionNodes.length).equal(1);
|
||||
|
||||
const treeItem = await accountTreeNode.getTreeItem();
|
||||
should(treeItem.label).equal(accountTreeNodeLabel);
|
||||
|
||||
const nodeInfo = accountTreeNode.getNodeInfo();
|
||||
should(nodeInfo.label).equal(accountTreeNodeLabel);
|
||||
});
|
||||
|
||||
it('Should be correct when there are subscriptions filtered for ADAL.', async function (): Promise<void> {
|
||||
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
|
||||
sinon.stub(azdata.accounts, 'getAccountSecurityToken').resolves(mockToken);
|
||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockFilteredSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
|
||||
|
||||
const subscriptionNodes = await accountTreeNode.getChildren();
|
||||
|
||||
should(subscriptionNodes).Array();
|
||||
should(subscriptionNodes.length).equal(mockFilteredSubscriptions.length);
|
||||
|
||||
const treeItem = await accountTreeNode.getTreeItem();
|
||||
should(treeItem.label).equal(accountTreeNodeLabel);
|
||||
const tenantTreeItem = await tenantTreeNode.getTreeItem();
|
||||
should(tenantTreeItem.label).equal(tenantTreeNodeLabel);
|
||||
|
||||
const nodeInfo = accountTreeNode.getNodeInfo();
|
||||
should(nodeInfo.label).equal(accountTreeNodeLabel);
|
||||
});
|
||||
|
||||
it('Should be correct when there are subscriptions filtered for MSAL.', async function (): Promise<void> {
|
||||
mockSubscriptionServiceMSAL.setup((o) => o.getSubscriptions(mockAccount)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
|
||||
sinon.stub(azdata.accounts, 'getAccountSecurityToken').resolves(mockToken);
|
||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockFilteredSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextMSAL, mockTreeChangeHandler.object);
|
||||
|
||||
const subscriptionNodes = await accountTreeNode.getChildren();
|
||||
|
||||
should(subscriptionNodes).Array();
|
||||
should(subscriptionNodes.length).equal(mockFilteredSubscriptions.length);
|
||||
|
||||
const treeItem = await accountTreeNode.getTreeItem();
|
||||
should(treeItem.label).equal(accountTreeNodeLabel);
|
||||
|
||||
const nodeInfo = accountTreeNode.getNodeInfo();
|
||||
should(nodeInfo.label).equal(accountTreeNodeLabel);
|
||||
const tenantNodeInfo = tenantTreeNode.getNodeInfo();
|
||||
should(tenantNodeInfo.label).equal(tenantTreeNodeLabel);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -296,23 +279,18 @@ describe('AzureResourceAccountTreeNode.getChildren', function (): void {
|
||||
beforeEach(() => {
|
||||
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||
mockSubscriptionServiceADAL = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
||||
mockSubscriptionServiceMSAL = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
||||
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
||||
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
||||
|
||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||
|
||||
mockSubscriptionCache = [];
|
||||
|
||||
mockAppContextADAL = new AppContext(mockExtensionContext.object);
|
||||
mockAppContextADAL.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||
mockAppContextADAL.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionServiceADAL.object);
|
||||
mockAppContextADAL.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
|
||||
|
||||
mockAppContextMSAL = new AppContext(mockExtensionContext.object);
|
||||
mockAppContextMSAL.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||
mockAppContextMSAL.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionServiceMSAL.object);
|
||||
mockAppContextMSAL.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
|
||||
mockAppContext = new AppContext(mockExtensionContext.object);
|
||||
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
|
||||
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
|
||||
mockAppContext.registerService<IAzureResourceTenantFilterService>(AzureResourceServiceNames.tenantFilterService, mockTenantFilterService.object);
|
||||
|
||||
sinon.stub(azdata.accounts, 'getAccountSecurityToken').resolves(mockToken);
|
||||
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
|
||||
@@ -327,28 +305,27 @@ describe('AzureResourceAccountTreeNode.getChildren', function (): void {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('Should load subscriptions from scratch and update cache when it is clearing cache for ADAL.', async function (): Promise<void> {
|
||||
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve([]));
|
||||
it('Should load subscriptions from scratch and update cache when it is clearing cache.', async function (): Promise<void> {
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount, mockTenant)).returns(() => Promise.resolve([]));
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
const tenantTreeNode = new AzureResourceTenantTreeNode(mockAccount, mockTenant, accountTreeNode, mockAppContext, mockTreeChangeHandler.object);
|
||||
const children = await tenantTreeNode.getChildren();
|
||||
|
||||
const children = await accountTreeNode.getChildren();
|
||||
|
||||
mockSubscriptionServiceADAL.verify((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(0));
|
||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.once());
|
||||
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
|
||||
mockTreeChangeHandler.verify((o) => o.notifyNodeChanged(accountTreeNode), TypeMoq.Times.once());
|
||||
mockTreeChangeHandler.verify((o) => o.notifyNodeChanged(tenantTreeNode), TypeMoq.Times.once());
|
||||
|
||||
should(accountTreeNode.totalSubscriptionCount).equal(mockSubscriptions.length);
|
||||
should(accountTreeNode.selectedSubscriptionCount).equal(mockSubscriptions.length);
|
||||
should(accountTreeNode.isClearingCache).false();
|
||||
should(tenantTreeNode.totalSubscriptionCount).equal(mockSubscriptions.length);
|
||||
should(tenantTreeNode.selectedSubscriptionCount).equal(mockSubscriptions.length);
|
||||
should(tenantTreeNode.isClearingCache).false();
|
||||
|
||||
should(children).Array();
|
||||
should(children.length).equal(mockSubscriptions.length);
|
||||
|
||||
should(mockSubscriptionCache).deepEqual(mockSubscriptions);
|
||||
|
||||
for (let ix = 0; ix < mockSubscriptions.length; ix++) {
|
||||
@@ -356,39 +333,38 @@ describe('AzureResourceAccountTreeNode.getChildren', function (): void {
|
||||
const subscription = mockSubscriptions[ix];
|
||||
|
||||
should(child).instanceof(AzureResourceSubscriptionTreeNode);
|
||||
should(child.nodePathValue).equal(`account_${mockAccount.key.accountId}.subscription_${subscription.id}.tenant_${mockTenantId}`);
|
||||
should(child.nodePathValue).equal(`account_${mockAccount.key.accountId}.tenant_${mockTenantId}.subscription_${subscription.id}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('Should load subscriptions from cache when it is not clearing cache.', async function (): Promise<void> {
|
||||
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve([]));
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount, mockTenant)).returns(() => Promise.resolve([]));
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
|
||||
await accountTreeNode.getChildren();
|
||||
const children = await accountTreeNode.getChildren();
|
||||
const tenants = await accountTreeNode.getChildren();
|
||||
await tenants[0].getChildren();
|
||||
const children = await tenants[0].getChildren();
|
||||
|
||||
|
||||
mockSubscriptionServiceADAL.verify((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
|
||||
should(children.length).equal(mockSubscriptionCache.length);
|
||||
|
||||
for (let ix = 0; ix < mockSubscriptionCache.length; ix++) {
|
||||
should(children[ix].nodePathValue).equal(`account_${mockAccount.key.accountId}.subscription_${mockSubscriptionCache[ix].id}.tenant_${mockTenantId}`);
|
||||
should(children[ix].nodePathValue).equal(`account_${mockAccount.key.accountId}.tenant_${mockTenantId}.subscription_${mockSubscriptionCache[ix].id}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('Should handle when there is no subscriptions.', async function (): Promise<void> {
|
||||
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve([]));
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve([]));
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
const tenantTreeNode = new AzureResourceTenantTreeNode(mockAccount, mockTenant, accountTreeNode, mockAppContext, mockTreeChangeHandler.object);
|
||||
const children = await tenantTreeNode.getChildren();
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
|
||||
|
||||
const children = await accountTreeNode.getChildren();
|
||||
|
||||
should(accountTreeNode.totalSubscriptionCount).equal(0);
|
||||
should(tenantTreeNode.totalSubscriptionCount).equal(0);
|
||||
|
||||
should(children).Array();
|
||||
should(children.length).equal(1);
|
||||
@@ -397,36 +373,51 @@ describe('AzureResourceAccountTreeNode.getChildren', function (): void {
|
||||
should(children[0].getNodeInfo().label).equal('No Subscriptions found.');
|
||||
});
|
||||
|
||||
it('Should honor subscription filtering.', async function (): Promise<void> {
|
||||
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
|
||||
it('Should honor tenant filtering.', async function (): Promise<void> {
|
||||
mockTenantFilterService.setup((o) => o.getSelectedTenants(mockAccount)).returns(() => Promise.resolve(mockFilteredTenants));
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
const children = await accountTreeNode.getChildren();
|
||||
|
||||
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.once());
|
||||
should(accountTreeNode.selectedTenantCount).equal(mockFilteredTenants.length);
|
||||
should(children.length).equal(mockFilteredTenants.length);
|
||||
|
||||
should(accountTreeNode.selectedSubscriptionCount).equal(mockFilteredSubscriptions.length);
|
||||
should(children.length).equal(mockFilteredSubscriptions.length);
|
||||
for (let ix = 0; ix < mockFilteredTenants.length; ix++) {
|
||||
should(children[ix].nodePathValue).equal(`account_${mockAccount.key.accountId}.tenant_${mockTenantId}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('Should honor subscription filtering.', async function (): Promise<void> {
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockFilteredSubscriptions));
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
const tenantTreeNode = new AzureResourceTenantTreeNode(mockAccount, mockTenant, accountTreeNode, mockAppContext, mockTreeChangeHandler.object);
|
||||
const subscriptions = await tenantTreeNode.getChildren();
|
||||
|
||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
|
||||
should(subscriptions.length).equal(mockFilteredSubscriptions.length);
|
||||
should(tenantTreeNode.selectedSubscriptionCount).equal(mockFilteredSubscriptions.length);
|
||||
|
||||
for (let ix = 0; ix < mockFilteredSubscriptions.length; ix++) {
|
||||
should(children[ix].nodePathValue).equal(`account_${mockAccount.key.accountId}.subscription_${mockFilteredSubscriptions[ix].id}.tenant_${mockTenantId}`);
|
||||
const subscription = mockSubscriptions[ix];
|
||||
should(subscriptions[ix].nodePathValue).equal(`account_${mockAccount.key.accountId}.tenant_${mockTenantId}.subscription_${subscription.id}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('Should handle errors.', async function (): Promise<void> {
|
||||
mockSubscriptionServiceADAL.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockSubscriptions));
|
||||
|
||||
const mockError = 'Test error';
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => { throw new Error(mockError); });
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount, mockTenant)).returns(() => { throw new Error(mockError); });
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
|
||||
const children = await accountTreeNode.getChildren();
|
||||
const tenants = await accountTreeNode.getChildren();
|
||||
const children = await tenants[0].getChildren();
|
||||
|
||||
mockSubscriptionServiceADAL.verify((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.once());
|
||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount, mockTenant), TypeMoq.Times.once());
|
||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.never());
|
||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
|
||||
@@ -442,17 +433,17 @@ describe('AzureResourceAccountTreeNode.clearCache', function (): void {
|
||||
beforeEach(() => {
|
||||
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||
mockSubscriptionServiceADAL = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
||||
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
||||
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
||||
|
||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||
|
||||
mockSubscriptionCache = [];
|
||||
|
||||
mockAppContextADAL = new AppContext(mockExtensionContext.object);
|
||||
mockAppContextADAL.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||
mockAppContextADAL.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionServiceADAL.object);
|
||||
mockAppContextADAL.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
|
||||
mockAppContext = new AppContext(mockExtensionContext.object);
|
||||
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
|
||||
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
|
||||
|
||||
sinon.stub(azdata.accounts, 'getAccountSecurityToken').returns(Promise.resolve(mockToken));
|
||||
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
|
||||
@@ -468,7 +459,7 @@ describe('AzureResourceAccountTreeNode.clearCache', function (): void {
|
||||
});
|
||||
|
||||
it('Should clear cache.', async function (): Promise<void> {
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContextADAL, mockTreeChangeHandler.object);
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
accountTreeNode.clearCache();
|
||||
should(accountTreeNode.isClearingCache).true();
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ import { AzureResourceService } from '../../../azureResource/resourceService';
|
||||
import { AzureResourceResourceTreeNode } from '../../../azureResource/resourceTreeNode';
|
||||
import { IAzureResourceCacheService } from '../../../azureResource/interfaces';
|
||||
import { generateGuid } from '../../../azureResource/utils';
|
||||
import { AzureAccount, AzureAccountProperties, azureResource } from 'azurecore';
|
||||
import { AzureAccount, AzureAccountProperties, Tenant, azureResource } from 'azurecore';
|
||||
import { TreeNode } from '../../../azureResource/treeNode';
|
||||
|
||||
// Mock services
|
||||
@@ -47,6 +47,13 @@ const mockAccount: AzureAccount = {
|
||||
const mockTenantId: string = 'mock_tenant';
|
||||
const mockSubscriptionId: string = 'mock_subscription';
|
||||
|
||||
const mockTenant: Tenant = {
|
||||
id: mockTenantId,
|
||||
displayName: 'mock_tenant',
|
||||
userId: 'test@email.com',
|
||||
tenantCategory: 'Home'
|
||||
}
|
||||
|
||||
const mockSubscription: azureResource.AzureResourceSubscription = {
|
||||
id: mockSubscriptionId,
|
||||
name: 'mock subscription',
|
||||
@@ -96,9 +103,9 @@ describe('AzureResourceSubscriptionTreeNode.info', function (): void {
|
||||
});
|
||||
|
||||
it('Should be correct when created.', async function (): Promise<void> {
|
||||
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockAccount, mockSubscription, mockTenantId, appContext, mockTreeChangeHandler.object, TypeMoq.Mock.ofType<TreeNode>().object);
|
||||
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockAccount, mockSubscription, mockTenant, appContext, mockTreeChangeHandler.object, TypeMoq.Mock.ofType<TreeNode>().object);
|
||||
|
||||
should(subscriptionTreeNode.nodePathValue).equal(`account_${mockAccount.key.accountId}.subscription_${mockSubscription.id}.tenant_${mockTenantId}`);
|
||||
should(subscriptionTreeNode.nodePathValue).equal(`account_${mockAccount.key.accountId}.tenant_${mockTenantId}.subscription_${mockSubscription.id}`);
|
||||
|
||||
const treeItem = await subscriptionTreeNode.getTreeItem();
|
||||
should(treeItem.label).equal(mockSubscription.name);
|
||||
@@ -147,7 +154,7 @@ describe('AzureResourceSubscriptionTreeNode.getChildren', function (): void {
|
||||
});
|
||||
|
||||
it('Should return resource containers.', async function (): Promise<void> {
|
||||
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockAccount, mockSubscription, mockTenantId, appContext, mockTreeChangeHandler.object, TypeMoq.Mock.ofType<TreeNode>().object);
|
||||
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockAccount, mockSubscription, mockTenant, appContext, mockTreeChangeHandler.object, TypeMoq.Mock.ofType<TreeNode>().object);
|
||||
const children = await subscriptionTreeNode.getChildren();
|
||||
|
||||
mockResourceTreeDataProvider1.verify((o) => o.getRootChildren(), TypeMoq.Times.once());
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
.list-row.account-picker-list .label,
|
||||
.list-row.tenant-picker-list .label {
|
||||
flex: 1 1 auto;
|
||||
margin-left: 12px;
|
||||
margin-left: 8px;
|
||||
margin-top: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||