diff --git a/extension.bundle.ts b/extension.bundle.ts index fdb1a8a29..3bb2aefcf 100644 --- a/extension.bundle.ts +++ b/extension.bundle.ts @@ -24,19 +24,28 @@ export { emulatorPassword, isWindows } from './src/constants'; export { ParsedDocDBConnectionString, parseDocDBConnectionString } from './src/docdb/docDBConnectionStrings'; export { getCosmosClient } from './src/docdb/getCosmosClient'; export * from './src/docdb/registerDocDBCommands'; -export { activateInternal, cosmosDBCopyConnectionString, createServer, deactivateInternal, deleteAccount } from './src/extension'; +export { + activateInternal, + cosmosDBCopyConnectionString, + createServer, + deactivateInternal, + deleteAccount, +} from './src/extension'; export { ext } from './src/extensionVariables'; export * from './src/graph/registerGraphCommands'; +export { connectToMongoClient, isCosmosEmulatorConnectionString } from './src/mongo/connectToMongoClient'; export { MongoCommand } from './src/mongo/MongoCommand'; +export { + addDatabaseToAccountConnectionString, + encodeMongoConnectionString, + getDatabaseNameFromConnectionString, +} from './src/mongo/mongoConnectionStrings'; export { findCommandAtPosition, getAllCommandsFromText } from './src/mongo/MongoScrapbook'; export { MongoShell } from './src/mongo/MongoShell'; -export { connectToMongoClient, isCosmosEmulatorConnectionString } from './src/mongo/connectToMongoClient'; -export { addDatabaseToAccountConnectionString, encodeMongoConnectionString, getDatabaseNameFromConnectionString } from './src/mongo/mongoConnectionStrings'; export * from './src/mongo/registerMongoCommands'; export { IDatabaseInfo } from './src/mongo/tree/MongoAccountTreeItem'; export { addDatabaseToConnectionString } from './src/postgres/postgresConnectionStrings'; export { AttachedAccountsTreeItem, MONGO_CONNECTION_EXPECTED } from './src/tree/AttachedAccountsTreeItem'; -export { AzureAccountTreeItemWithAttached } from './src/tree/AzureAccountTreeItemWithAttached'; export * from './src/utils/azureClients'; export { getPublicIpv4, isIpInRanges } from './src/utils/getIp'; export { improveError } from './src/utils/improveError'; diff --git a/package.json b/package.json index e5617eb72..1c538f142 100644 --- a/package.json +++ b/package.json @@ -701,474 +701,412 @@ "when": "view == azureResourceGroups && viewItem =~ /(AzureCosmosDb|PostgreSqlServers(Standard|Flexible))/i && viewItem =~ /azureResourceTypeGroup/i", "group": "1@1" }, + { - "command": "cosmosDB.deleteAccount", - "when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /mongodb.item.account(?![a-z])/i", + "command": "azureDatabases.detachDatabaseAccount", + "when": "view == azureWorkspace && viewItem == postgresServerAttached", "group": "1@2" }, { - "command": "cosmosDB.deleteAccount", - "when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /mongoclusters/i && viewItem =~ /treeitem.mongoCluster/i", + "command": "postgreSQL.deleteServer", + "when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /postgresServer(?![a-z])/i", "group": "1@2" }, { - "command": "cosmosDB.deleteAccount", - "when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /cosmosDBDocumentServer(?![a-z])/i", - "group": "1@2" + "command": "postgreSQL.showPasswordlessWiki", + "when": "view =~ /azure(ResourceGroups|azureFocusView)/ && viewItem =~ /postgresDatabase(?![a-z])/i && viewItem =~ /usesPassword/i", + "group": "inline" }, { - "command": "cosmosDB.deleteAccount", - "when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /cosmosDBGraphAccount(?![a-z])/i", - "group": "1@2" + "command": "postgreSQL.createDatabase", + "when": "view =~ /(azureResourceGroups|Workspace|azureFocusView)/ && viewItem =~ /postgresServer/i", + "group": "1@1" }, { - "command": "cosmosDB.deleteAccount", - "when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /cosmosDBTableAccount(?![a-z])/i", + "command": "postgreSQL.deleteDatabase", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /postgresDatabase(?![a-z])/i", "group": "1@2" }, { - "command": "postgreSQL.deleteServer", - "when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /postgresServer(?![a-z])/i", + "command": "postgreSQL.deleteTable", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == postgresTable", "group": "1@2" }, { - "command": "cosmosDB.createDocDBDocument", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBDocumentsGroup", + "command": "postgreSQL.deleteFunction", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == postgresFunction", "group": "1@2" }, { - "command": "cosmosDB.openNoSqlQueryEditor", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBDocumentCollection && config.cosmosDB.preview.queryEditor", - "group": "1@1" - }, - { - "command": "cosmosDB.openNoSqlQueryEditor", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBDocumentsGroup && config.cosmosDB.preview.queryEditor", - "group": "1@1" - }, - { - "command": "cosmosDB.writeNoSqlQuery", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBDocumentCollection", + "command": "postgreSQL.deleteStoredProcedure", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == postgresStoredProcedure", "group": "1@2" }, { - "command": "cosmosDB.importDocument", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBDocumentCollection", - "group": "1@3" + "command": "postgreSQL.copyConnectionString", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /postgresDatabase(?![a-z])/i", + "group": "2@1" }, { - "command": "cosmosDB.createDocDBStoredProcedure", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBStoredProceduresGroup", + "command": "postgreSQL.connectDatabase", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /postgresDatabase(?![a-z])/i", "group": "1@1" }, { - "command": "cosmosDB.createDocDBTrigger", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBTriggersGroup", + "command": "postgreSQL.createFunctionQuery", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == postgresFunctions", "group": "1@1" }, { - "command": "cosmosDB.createDocDBCollection", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBDocumentDatabase", + "command": "postgreSQL.createStoredProcedureQuery", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == postgresStoredProcedures", "group": "1@1" }, { - "command": "cosmosDB.createDocDBDatabase", - "when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /cosmosDBDocumentServer(?![a-z])/i", - "group": "1@1" + "command": "azureDatabases.refresh", + "when": "view =~ /azureWorkspace/ && viewItem =~ /postgresServer(?![a-z])/i", + "group": "2@2" }, { - "command": "cosmosDB.createDocDBDatabase", - "when": "view == azureWorkspace && viewItem == cosmosDBDocumentServerAttached", - "group": "1@1" + "command": "azureDatabases.refresh", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /postgresDatabase(?![a-z])/i", + "group": "3@1" }, { - "command": "cosmosDB.createGraphDatabase", - "when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /cosmosDBGraphAccount(?![a-z])/i", + "command": "azureDatabases.refresh", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == postgresTables", "group": "1@1" }, { - "command": "cosmosDB.createGraphDatabase", - "when": "view == azureWorkspace && viewItem == cosmosDBGraphAccountAttached", - "group": "1@1" + "command": "azureDatabases.refresh", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == postgresFunctions", + "group": "2@1" }, { - "command": "cosmosDB.createGraph", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBGraphDatabase", - "group": "1@1" + "command": "azureDatabases.refresh", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == postgresStoredProcedures", + "group": "2@1" }, { - "command": "postgreSQL.showPasswordlessWiki", - "when": "view =~ /azure(ResourceGroups|azureFocusView)/ && viewItem =~ /postgresDatabase(?![a-z])/i && viewItem =~ /usesPassword/i", - "group": "inline" + "command": "azureDatabases.refresh", + "when": "view == azureWorkspace && viewItem == postgresServerAttached", + "group": "2@1" }, + + { - "command": "postgreSQL.createDatabase", - "when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /postgresServer(?![a-z])/i", + "//": "[Account] Create Cosmos DB database", + "command": "cosmosDB.createDocDBDatabase", + "when": "view =~ /azure(ResourceGroups|FocusView|Workspace)/ && viewItem =~ /treeitem[.]account(?![a-z.\\/])/i", "group": "1@1" }, { - "command": "postgreSQL.createDatabase", - "when": "view == azureWorkspace && viewItem == postgresServerAttached", + "//": "[Account] Create Mongo DB|Cluster database", + "command": "command.mongoClusters.createDatabase", + "when": "view =~ /azure(ResourceGroups|FocusView|Workspace)/ && viewItem =~ /treeitem[.]mongoCluster(?![a-z.\\/])/i", "group": "1@1" }, { - "command": "azureDatabases.detachDatabaseAccount", - "when": "view == azureWorkspace && viewItem == cosmosDBMongoServerAttached", + "//": "[Account] Delete Cosmos DB account", + "command": "cosmosDB.deleteAccount", + "when": "view =~ /azure(ResourceGroups|FocusView)/ && viewItem =~ /treeitem[.]account(?![a-z.\\/])/i", "group": "1@2" }, { - "command": "azureDatabases.detachDatabaseAccount", - "when": "view == azureWorkspace && viewItem == cosmosDBGraphAccountAttached", + "//": "[Account] Delete Mongo DB account", + "command": "cosmosDB.deleteAccount", + "when": "view =~ /azure(ResourceGroups|FocusView)/ && viewItem =~ /treeitem[.]mongoCluster(?![a-z.\\/])/i", "group": "1@2" }, { + "//": "[Account] Detach Cosmos DB account (workspace only)", "command": "azureDatabases.detachDatabaseAccount", - "when": "view == azureWorkspace && viewItem == cosmosDBDocumentServerAttached", + "when": "view =~ /azureWorkspace/ && viewItem =~ /treeitem[.]account(?![a-z.\\/])/i", "group": "1@2" }, { + "//": "[Account] Detach Mongo DB account (workspace only)", "command": "azureDatabases.detachDatabaseAccount", - "when": "view == azureWorkspace && viewItem == cosmosDBTableAccountAttached", + "when": "view =~ /azureWorkspace/ && viewItem =~ /treeitem[.]mongoCluster(?![a-z.\\/])/i", "group": "1@2" }, { - "command": "azureDatabases.detachDatabaseAccount", - "when": "view == azureWorkspace && viewItem == postgresServerAttached", + "//": "[Account] Detach Mongo Cluster account (workspace only)", + "command": "command.mongoClusters.removeWorkspaceConnection", + "when": "view =~ /azureWorkspace/ && viewItem =~ /treeitem[.]mongoCluster(?![a-z.\\/])/i", "group": "1@2" }, { - "command": "cosmosDB.connectMongoDB", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == mongoDb", + "//": "[Account] Copy connection string to Cosmos DB account", + "command": "cosmosDB.copyConnectionString", + "when": "view =~ /azure(ResourceGroups|FocusView|Workspace)/ && viewItem =~ /treeitem[.]account(?![a-z.\\/])/i", "group": "2@1" }, { - "command": "cosmosDB.deleteDocDBCollection", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBDocumentCollection", - "group": "1@4" - }, - { - "command": "cosmosDB.viewDocDBCollectionOffer", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBDocumentCollection", - "group": "1@5" - }, - { - "command": "cosmosDB.viewDocDBDatabaseOffer", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBDocumentDatabase", - "group": "1@5" + "//": "[Account] Copy connection string to Mongo Cluster or MongoDB (RU) account", + "command": "command.mongoClusters.copyConnectionString", + "when": "view =~ /azure(ResourceGroups|FocusView|Workspace)/ && viewItem =~ /treeitem[.]mongoCluster(?![a-z.\\/])/i", + "group": "2@1" }, { - "command": "cosmosDB.deleteDocDBDocument", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBDocument", - "group": "1@2" + "//": "[Account] Mongo DB|Cluster Launch Shell", + "command": "command.mongoClusters.launchShell", + "when": "view =~ /azure(ResourceGroups|FocusView|Workspace)/ && viewItem =~ /treeitem[.]mongoCluster(?![a-z.\\/])/i", + "group": "2@2" }, { - "command": "cosmosDB.deleteDocDBStoredProcedure", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBStoredProcedure", - "group": "1@2" + "//": "[Account] Refresh", + "command": "azureDatabases.refresh", + "when": "view =~ /azureWorkspace/ && viewItem =~ /treeitem[.](mongoCluster|account)(?![a-z.\\/])/i", + "group": "3@1" }, + + { - "command": "cosmosDB.executeDocDBStoredProcedure", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBStoredProcedure", + "//": "[Database] Create Graph container", + "command": "cosmosDB.createGraph", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]database(?![a-z.\\/])/i && viewItem =~ /experience[.](graph)/i", "group": "1@1" }, { - "command": "cosmosDB.deleteDocDBTrigger", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBTrigger", - "group": "1@2" + "//": "[Database] Create NoSql container", + "command": "cosmosDB.createDocDBCollection", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]database(?![a-z.\\/])/i && viewItem =~ /experience[.](table|cassandra|core)/i", + "group": "1@1" }, { - "command": "cosmosDB.deleteDocDBDatabase", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBDocumentDatabase", - "group": "1@2" + "//": "[Database] Create Mongo DB|Cluster collection", + "command": "command.mongoClusters.createCollection", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]database(?![a-z.\\/])/i && viewItem =~ /experience[.](mongocluster|mongodb)/i", + "group": "1@1" }, { + "//": "[Database] Delete Graph database", "command": "cosmosDB.deleteGraphDatabase", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBGraphDatabase", - "group": "1@2" - }, - { - "command": "postgreSQL.deleteDatabase", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /postgresDatabase(?![a-z])/i", - "group": "1@2" - }, - { - "command": "postgreSQL.deleteTable", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == postgresTable", - "group": "1@2" - }, - { - "command": "postgreSQL.deleteFunction", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == postgresFunction", - "group": "1@2" - }, - { - "command": "postgreSQL.deleteStoredProcedure", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == postgresStoredProcedure", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]database(?![a-z.\\/])/i && viewItem =~ /experience[.](graph)/i", "group": "1@2" }, { - "command": "cosmosDB.deleteGraph", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBGraph", + "//": "[Database] Delete NoSql database", + "command": "cosmosDB.deleteDocDBDatabase", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]database(?![a-z.\\/])/i && viewItem =~ /experience[.](table|cassandra|core)/i", "group": "1@2" }, { - "command": "cosmosDB.attachDatabaseAccount", - "when": "view == azureWorkspace && viewItem =~ /cosmosDBAttachedAccounts(?![a-z])/gi", - "group": "1@1" - }, - { - "command": "cosmosDB.attachEmulator", - "when": "view == azureWorkspace && viewItem == cosmosDBAttachedAccountsWithEmulator", + "//": "[Database] Delete Mongo DB|Cluster database", + "command": "command.mongoClusters.dropDatabase", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]database(?![a-z.\\/])/i && viewItem =~ /experience[.](mongocluster|mongodb)/i", "group": "1@2" }, { - "command": "cosmosDB.copyConnectionString", - "when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /cosmosDBMongoServer(?![a-z])/i", - "group": "2@1" - }, - { - "command": "cosmosDB.copyConnectionString", - "when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /cosmosDBGraphAccount(?![a-z])/i", - "group": "2@1" - }, - { - "command": "cosmosDB.copyConnectionString", - "when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /cosmosDBDocumentServer(?![a-z])/i", - "group": "2@1" - }, - { - "command": "cosmosDB.copyConnectionString", - "when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /cosmosDBTableAccount(?![a-z])/i", - "group": "2@1" - }, - { - "command": "cosmosDB.copyConnectionString", - "when": "view == azureWorkspace && viewItem == cosmosDBMongoServerAttached", - "group": "2@1" - }, - { - "command": "cosmosDB.copyConnectionString", - "when": "view == azureWorkspace && viewItem == cosmosDBGraphAccountAttached", - "group": "2@1" - }, - { - "command": "cosmosDB.copyConnectionString", - "when": "view == azureWorkspace && viewItem == cosmosDBDocumentServerAttached", - "group": "2@1" - }, - { - "command": "cosmosDB.copyConnectionString", - "when": "view == azureWorkspace && viewItem == cosmosDBTableAccountAttached", - "group": "2@1" - }, - { - "command": "postgreSQL.copyConnectionString", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /postgresDatabase(?![a-z])/i", - "group": "2@1" - }, - { - "command": "azureDatabases.refresh", - "when": "view == azureWorkspace && viewItem == cosmosDBMongoServerAttached", - "group": "3@2" + "//": "[Database] View NoSql database offer", + "command": "cosmosDB.viewDocDBDatabaseOffer", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]database(?![a-z.\\/])/i && viewItem =~ /experience[.](table|cassandra|core)/i", + "group": "1@3" }, { - "command": "azureDatabases.refresh", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBDocumentCollection", + "//": "[Database] Connect to Mongo DB", + "command": "cosmosDB.connectMongoDB", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]database(?![a-z.\\/])/i && viewItem =~ /experience[.](mongodb)/i", "group": "2@1" }, { - "command": "azureDatabases.refresh", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBDocumentDatabase", - "group": "4@1" - }, - { - "command": "azureDatabases.refresh", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBDocumentsGroup", + "//": "[Database] Mongo Cluster Launch Shell", + "command": "command.mongoClusters.launchShell", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]database(?![a-z.\\/])/i && viewItem =~ /experience[.](mongocluster)/i", "group": "2@1" }, { + "//": "[Database] Refresh", "command": "azureDatabases.refresh", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBStoredProceduresGroup", - "group": "2@1" + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]database(?![a-z.\\/])/i", + "group": "3@1" }, + + { - "command": "azureDatabases.refresh", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBTriggersGroup", - "group": "2@1" + "//": "[Container] Open NoSql query editor", + "command": "cosmosDB.openNoSqlQueryEditor", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]container(?![a-z.\\/])/i && viewItem =~ /experience[.](table|cassandra|core)/i", + "group": "1@1" }, { - "command": "azureDatabases.refresh", - "when": "view =~ /azureWorkspace/ && viewItem =~ /cosmosDBDocumentServer(?![a-z])/i", - "group": "3@2" + "//": "[Container] Open NoSql query editor (scrapbook)", + "command": "cosmosDB.writeNoSqlQuery", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]container(?![a-z.\\/])/i && viewItem =~ /experience[.](table|cassandra|core)/i", + "group": "1@2" }, { - "command": "azureDatabases.refresh", - "when": "view =~ /azureWorkspace/ && viewItem =~ /postgresServer(?![a-z])/i", - "group": "2@2" + "//": "[Container] Import NoSql documents", + "command": "cosmosDB.importDocument", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]container(?![a-z.\\/])/i && viewItem =~ /experience[.](table|cassandra|core)/i", + "group": "1@3" }, { - "command": "azureDatabases.refresh", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /postgresDatabase(?![a-z])/i", - "group": "3@1" + "//": "[Container] Delete NoSql container", + "command": "cosmosDB.deleteDocDBCollection", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]container(?![a-z.\\/])/i && viewItem =~ /experience[.](table|cassandra|core)/i", + "group": "1@4" }, { - "command": "azureDatabases.refresh", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == postgresTables", - "group": "1@1" + "//": "[Container] Delete Graph container", + "command": "cosmosDB.deleteGraph", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]container(?![a-z.\\/])/i && viewItem =~ /experience[.](graph)/i", + "group": "1@2" }, { - "command": "azureDatabases.refresh", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == postgresFunctions", - "group": "2@1" + "//": "[Container] View NoSql container offer", + "command": "cosmosDB.viewDocDBCollectionOffer", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]container(?![a-z.\\/])/i && viewItem =~ /experience[.](table|cassandra|core)/i", + "group": "1@5" }, { + "//": "[Container] Refresh", "command": "azureDatabases.refresh", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == postgresStoredProcedures", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]container(?![a-z.\\/])/i", "group": "2@1" }, + + { - "command": "azureDatabases.refresh", - "when": "view == azureWorkspace && viewItem == cosmosDBDocumentServerAttached", - "group": "3@1" - }, - { - "command": "azureDatabases.refresh", - "when": "view =~ /azureWorkspace/ && viewItem =~ /cosmosDBGraphAccount(?![a-z])/i", - "group": "3@2" + "//": "[Collection] Open Mongo DB|Cluster collection", + "command": "command.mongoClusters.containerView.open", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]collection(?![a-z.\\/])/i && viewItem =~ /experience[.](mongocluster|mongodb)/i", + "group": "1@1" }, { - "command": "azureDatabases.refresh", - "when": "view == azureWorkspace && viewItem == cosmosDBGraphAccountAttached", - "group": "3@1" + "//": "[Collection] Create Mongo DB|Cluster document", + "command": "command.mongoClusters.createDocument", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]collection(?![a-z.\\/])/i && viewItem =~ /experience[.](mongocluster|mongodb)/i", + "group": "1@2" }, { - "command": "azureDatabases.refresh", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == cosmosDBGraphDatabase", + "//": "[Collection] Import Mongo DB|Cluster documents", + "command": "command.mongoClusters.importDocuments", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]collection(?![a-z.\\/])/i && viewItem =~ /experience[.](mongocluster|mongodb)/i", "group": "2@1" }, { - "command": "azureDatabases.refresh", - "when": "view == azureWorkspace && viewItem == postgresServerAttached", - "group": "2@1" + "//": "[Collection] Export Mongo DB|Cluster documents", + "command": "command.mongoClusters.exportDocuments", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]collection(?![a-z.\\/])/i && viewItem =~ /experience[.](mongocluster|mongodb)/i", + "group": "2@2" }, { - "command": "azureDatabases.refresh", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == mongoDb", + "//": "[Collection] Drop Mongo DB|Cluster collection", + "command": "command.mongoClusters.dropCollection", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]collection(?![a-z.\\/])/i && viewItem =~ /experience[.](mongocluster|mongodb)/i", "group": "3@1" }, { - "command": "azureDatabases.refresh", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == MongoCollection", + "//": "[Collection] Launch Mongo DB|Cluster shell", + "command": "command.mongoClusters.launchShell", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]collection(?![a-z.\\/])/i && viewItem =~ /experience[.](mongocluster|mongodb)/i", "group": "4@1" }, { + "//": "[Collection] Refresh", "command": "azureDatabases.refresh", - "when": "view == azureWorkspace && viewItem =~ /^cosmosDBAttachedAccounts(?![a-z])/gi", - "group": "2@1" - }, - { - "command": "cosmosDB.importDocument", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == MongoCollection", - "group": "1@3" - }, - { - "command": "postgreSQL.connectDatabase", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /postgresDatabase(?![a-z])/i", - "group": "1@1" - }, - { - "command": "postgreSQL.createFunctionQuery", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == postgresFunctions", - "group": "1@1" + "when" : "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]collection(?![a-z.\\/])/i", + "group": "5@1" }, + + { - "command": "postgreSQL.createStoredProcedureQuery", - "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem == postgresStoredProcedures", + "//": "[Stored Procedures] Create Stored Procedure", + "command": "cosmosDB.createDocDBStoredProcedure", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]storedProcedures(?![a-z.\\/])/i && viewItem =~ /experience[.](graph|table|cassandra|core)/i", "group": "1@1" }, { - "command": "command.mongoClusters.dropCollection", - "when": "viewItem =~ /treeitem.collection/i && viewItem =~ /(mongocluster|mongodb)/i", - "group": "3@1" + "//": "[Stored Procedures] Refresh", + "command": "azureDatabases.refresh", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]storedProcedures(?![a-z.\\/])/i && viewItem =~ /experience[.](graph|table|cassandra|core)/i", + "group": "2@1" }, + + { - "command": "command.mongoClusters.dropDatabase", - "when": "viewItem =~ /treeitem.database/i && viewItem =~ /(mongocluster|mongodb)/i", + "//": "[Stored Procedure] Execute", + "command": "cosmosDB.executeDocDBStoredProcedure", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]storedProcedure(?![a-z.\\/])/i && viewItem =~ /experience[.](graph|table|cassandra|core)/i", "group": "1@1" }, { - "command": "command.mongoClusters.removeWorkspaceConnection", - "when": "vscodeDatabases.mongoClustersSupportEnabled && view == azureWorkspace && viewItem =~ /treeitem.mongoCluster/i && viewItem =~ /(mongocluster|mongodb)/i" - }, - { - "command": "command.mongoClusters.createCollection", - "when": "viewItem =~ /treeitem.database/i && viewItem =~ /(mongocluster|mongodb)/i", - "group": "1@1" + "//": "[Stored Procedure] Delete", + "command": "cosmosDB.deleteDocDBStoredProcedure", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]storedProcedure(?![a-z.\\/])/i && viewItem =~ /experience[.](graph|table|cassandra|core)/i", + "group": "1@2" }, + + { - "command": "command.mongoClusters.createDatabase", - "when": "viewItem =~ /treeitem.mongoCluster|mongodb.item.account/i", + "//": "[Triggers] Create Trigger", + "command": "cosmosDB.createDocDBTrigger", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]triggers(?![a-z.\\/])/i && viewItem =~ /experience[.](graph|table|cassandra|core)/i", "group": "1@1" }, { - "command": "command.mongoClusters.copyConnectionString", - "when": "viewItem =~ /mongodb.item.account/i || viewItem =~ /treeitem.mongoCluster/i && viewItem =~ /(mongocluster)/i", - "group": "2@1" - }, - { - "command": "command.mongoClusters.importDocuments", - "when": "vscodeDatabases.mongoClustersSupportEnabled && viewItem =~ /treeitem.collection/i && viewItem =~ /(mongocluster|mongodb)/i", + "//": "[Triggers] Refresh", + "command": "azureDatabases.refresh", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]triggers(?![a-z.\\/])/i && viewItem =~ /experience[.](graph|table|cassandra|core)/i", "group": "2@1" }, + + { - "command": "command.mongoClusters.exportDocuments", - "when": "vscodeDatabases.mongoClustersSupportEnabled && viewItem =~ /treeitem.collection/i && viewItem =~ /(mongocluster|mongodb)/i", - "group": "2@2" + "//": "[Trigger] Delete", + "command": "cosmosDB.deleteDocDBTrigger", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]trigger(?![a-z.\\/])/i && viewItem =~ /experience[.](graph|table|cassandra|core)/i", + "group": "1@1" }, + + { - "command": "command.mongoClusters.containerView.open", - "when": "viewItem =~ /treeitem.collection/i && viewItem =~ /(mongocluster|mongodb)/i", + "//": "[Documents] Open NoSql query editor", + "command": "cosmosDB.openNoSqlQueryEditor", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]documents(?![a-z.\\/])/i && viewItem =~ /experience[.](graph|table|cassandra|core)/i", "group": "1@1" }, { - "command": "command.mongoClusters.createDocument", - "when": "viewItem =~ /treeitem.collection/i && viewItem =~ /(mongocluster|mongodb)/i", + "//": "[Documents] Create NoSql Document", + "command": "cosmosDB.createDocDBDocument", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]documents(?![a-z.\\/])/i && viewItem =~ /experience[.](graph|table|cassandra|core)/i", "group": "1@2" }, { - "command": "command.mongoClusters.launchShell", - "when": "viewItem =~ /treeitem.mongoCluster|mongodb.item.account/i", - "group": "2@1" - }, - { - "command": "command.mongoClusters.launchShell", - "when": "viewItem =~ /treeitem.database/i && viewItem =~ /(mongocluster|mongodb)/i", + "//": "[Documents] Refresh", + "command": "azureDatabases.refresh", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]documents(?![a-z.\\/])/i && viewItem =~ /experience[.](graph|table|cassandra|core)/i", "group": "2@1" }, + + { - "command": "command.mongoClusters.launchShell", - "when": "viewItem =~ /treeitem.collection/i && viewItem =~ /(mongocluster|mongodb)/i", - "group": "4@1" - }, - { - "command": "azureDatabases.refresh", - "when": "viewItem =~ /treeitem.collection/i && viewItem =~ /(mongocluster|mongodb)/i", - "group": "5@1" + "//": "[Document] Delete", + "command": "cosmosDB.deleteDocDBDocument", + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]document(?![a-z.\\/])/i && viewItem =~ /experience[.](graph|table|cassandra|core)/i", + "group": "1@1" }, { + "//": "[Document] Refresh", "command": "azureDatabases.refresh", - "when": "viewItem =~ /treeitem.database/i && viewItem =~ /(mongocluster|mongodb)/i", - "group": "3@1" + "when": "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]document(?![a-z.\\/])/i && viewItem =~ /experience[.](graph|table|cassandra|core)/i", + "group": "2@1" }, + + { + "//": "[Indexes] Refresh", "command": "azureDatabases.refresh", - "when": "viewItem =~ /treeitem.indexes/i && viewItem =~ /(mongocluster|mongodb)/i", - "group": "4@1" + "when" : "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]indexes(?![a-z.\\/])/i && viewItem =~ /experience[.](mongocluster|mongodb)/i", + "group": "1@1" }, { + "//": "[Index] Refresh", "command": "azureDatabases.refresh", - "when": "viewItem =~ /treeitem.index/i && viewItem =~ /(mongocluster|mongodb)/i", - "group": "4@1" + "when" : "view =~ /azure(ResourceGroups|Workspace|FocusView)/ && viewItem =~ /treeitem[.]index(?![a-z.\\/])/i && viewItem =~ /experience[.](mongocluster|mongodb)/i", + "group": "1@1" } ], "explorer/context": [ @@ -1325,11 +1263,6 @@ "type": "boolean", "default": true, "description": "Show warning dialog when uploading a document to the cloud." - }, - "cosmosDB.preview.queryEditor": { - "type": "boolean", - "default": true, - "description": "Enable the NoSQL Query Editor." } } } diff --git a/src/commands/deleteDatabaseAccount/deleteCosmosDBAccount.ts b/src/commands/deleteDatabaseAccount/deleteCosmosDBAccount.ts index ccb1a4848..ccd888e22 100644 --- a/src/commands/deleteDatabaseAccount/deleteCosmosDBAccount.ts +++ b/src/commands/deleteDatabaseAccount/deleteCosmosDBAccount.ts @@ -6,6 +6,7 @@ import { type CosmosDBManagementClient } from '@azure/arm-cosmosdb'; import { getResourceGroupFromId } from '@microsoft/vscode-azext-azureutils'; import { AzExtTreeItem, createSubscriptionContext } from '@microsoft/vscode-azext-utils'; +import { type AzureSubscription } from '@microsoft/vscode-azureresources-api'; import * as vscode from 'vscode'; import { ext } from '../../extensionVariables'; import { CosmosAccountResourceItemBase } from '../../tree/CosmosAccountResourceItemBase'; @@ -27,7 +28,13 @@ export async function deleteCosmosDBAccount( resourceGroup = getResourceGroupFromId(node.fullId); accountName = getDatabaseAccountNameFromId(node.fullId); } else if (node instanceof CosmosAccountResourceItemBase) { - const subscriptionContext = createSubscriptionContext(node.account.subscription); + // Not all CosmosAccountResourceItemBase instances have a subscription property (attached account does not), + // so we need to create a subscription context + if (!('subscription' in node.account)) { + throw new Error('Subscription is required to delete an account.'); + } + + const subscriptionContext = createSubscriptionContext(node.account.subscription as AzureSubscription); client = await createCosmosDBClient([context, subscriptionContext]); resourceGroup = getResourceGroupFromId(node.account.id); accountName = node.account.name; diff --git a/src/commands/deleteDatabaseAccount/deleteDatabaseAccount.ts b/src/commands/deleteDatabaseAccount/deleteDatabaseAccount.ts index 6723fe493..c4f68f5bc 100644 --- a/src/commands/deleteDatabaseAccount/deleteDatabaseAccount.ts +++ b/src/commands/deleteDatabaseAccount/deleteDatabaseAccount.ts @@ -11,7 +11,8 @@ import { type IActionContext, type ISubscriptionContext, } from '@microsoft/vscode-azext-utils'; -import { type MongoClusterResourceItem } from '../../mongoClusters/tree/MongoClusterResourceItem'; +import { type AzureSubscription } from '@microsoft/vscode-azureresources-api'; +import { MongoClusterResourceItem } from '../../mongoClusters/tree/MongoClusterResourceItem'; import { CosmosAccountResourceItemBase } from '../../tree/CosmosAccountResourceItemBase'; import { createActivityContext } from '../../utils/activityUtils'; import { localize } from '../../utils/localize'; @@ -26,10 +27,14 @@ export async function deleteDatabaseAccount( let subscription: ISubscriptionContext; if (node instanceof AzExtTreeItem) { subscription = node.subscription; - } else if (node instanceof CosmosAccountResourceItemBase) { - subscription = createSubscriptionContext(node.account.subscription); + } else if (node instanceof CosmosAccountResourceItemBase && 'subscription' in node.account) { + subscription = createSubscriptionContext(node.account.subscription as AzureSubscription); + } else if (node instanceof MongoClusterResourceItem) { + subscription = createSubscriptionContext(node.subscription); } else { - subscription = createSubscriptionContext((node as MongoClusterResourceItem).subscription); + // Not all CosmosAccountResourceItemBase instances have a subscription property (attached account does not), + // so we need to create a subscription context + throw new Error('Subscription is required to delete an account.'); } let accountName: string; diff --git a/src/extensionVariables.ts b/src/extensionVariables.ts index 9cce4a590..57f6b57ae 100644 --- a/src/extensionVariables.ts +++ b/src/extensionVariables.ts @@ -3,15 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { - type AzExtTreeDataProvider, - type AzExtTreeItem, - type IAzExtLogOutputChannel, - type TreeElementStateManager, -} from '@microsoft/vscode-azext-utils'; +import { type IAzExtLogOutputChannel, type TreeElementStateManager } from '@microsoft/vscode-azext-utils'; import { type AzureHostExtensionApi } from '@microsoft/vscode-azext-utils/hostapi'; import { type AzureResourcesExtensionApi } from '@microsoft/vscode-azureresources-api'; -import { type ExtensionContext, type SecretStorage, type TreeView } from 'vscode'; +import { type ExtensionContext, type SecretStorage } from 'vscode'; import { type DatabasesFileSystem } from './DatabasesFileSystem'; import { type NoSqlCodeLensProvider } from './docdb/NoSqlCodeLensProvider'; import { type MongoDBLanguageClient } from './mongo/languageClient'; @@ -22,8 +17,6 @@ import { type MongoClustersWorkspaceBranchDataProvider } from './mongoClusters/t import { type PostgresCodeLensProvider } from './postgres/services/PostgresCodeLensProvider'; import { type PostgresDatabaseTreeItem } from './postgres/tree/PostgresDatabaseTreeItem'; import { type AttachedAccountsTreeItem } from './tree/AttachedAccountsTreeItem'; -import { type AzureAccountTreeItemWithAttached } from './tree/AzureAccountTreeItemWithAttached'; -import { type SharedWorkspaceResourceProvider } from './tree/workspace/SharedWorkspaceResourceProvider'; /** * Namespace for common variables used throughout the extension. They must be initialized in the activate() method of extension.ts @@ -33,11 +26,8 @@ export namespace ext { export let connectedPostgresDB: PostgresDatabaseTreeItem | undefined; export let context: ExtensionContext; export let outputChannel: IAzExtLogOutputChannel; - export let tree: AzExtTreeDataProvider; - export let treeView: TreeView; export let attachedAccountsNode: AttachedAccountsTreeItem; export let isBundle: boolean | undefined; - export let azureAccountTreeItem: AzureAccountTreeItemWithAttached; export let secretStorage: SecretStorage; export let postgresCodeLensProvider: PostgresCodeLensProvider | undefined; export const prefix: string = 'azureDatabases'; @@ -53,9 +43,6 @@ export namespace ext { // used for the resources tree export let mongoClustersBranchDataProvider: MongoClustersBranchDataProvider; - // used for the workspace: this is the general provider - export let workspaceDataProvider: SharedWorkspaceResourceProvider; - // used for the workspace: these are the dedicated providers export let mongoClustersWorkspaceBranchDataProvider: MongoClustersWorkspaceBranchDataProvider; diff --git a/src/mongoClusters/tree/CollectionItem.ts b/src/mongoClusters/tree/CollectionItem.ts index 2da6162c0..bdca220d6 100644 --- a/src/mongoClusters/tree/CollectionItem.ts +++ b/src/mongoClusters/tree/CollectionItem.ts @@ -11,22 +11,26 @@ import { type TreeElementWithId, } from '@microsoft/vscode-azext-utils'; import { type Document } from 'bson'; -import { ThemeIcon, TreeItemCollapsibleState, type TreeItem } from 'vscode'; -import { type Experience } from '../../AzureDBExperiences'; +import { ThemeIcon, type TreeItem, TreeItemCollapsibleState } from 'vscode'; +import { API, type Experience } from '../../AzureDBExperiences'; import { ext } from '../../extensionVariables'; +import { type TreeElementWithContextValue } from '../../tree/TreeElementWithContextValue'; import { type TreeElementWithExperience } from '../../tree/TreeElementWithExperience'; import { - MongoClustersClient, type CollectionItemModel, type DatabaseItemModel, type InsertDocumentsResult, + MongoClustersClient, } from '../MongoClustersClient'; import { IndexesItem } from './IndexesItem'; import { type MongoClusterModel } from './MongoClusterModel'; -export class CollectionItem implements TreeElementWithId, TreeElementWithExperience { - id: string; - experience?: Experience; +export class CollectionItem implements TreeElementWithId, TreeElementWithExperience, TreeElementWithContextValue { + public readonly id: string; + public readonly experience?: Experience; + public readonly contextValue: string = 'treeItem.collection'; + + private readonly experienceContextValue: string = ''; constructor( readonly mongoCluster: MongoClusterModel, @@ -35,12 +39,14 @@ export class CollectionItem implements TreeElementWithId, TreeElementWithExperie ) { this.id = `${mongoCluster.id}/${databaseInfo.name}/${collectionInfo.name}`; this.experience = mongoCluster.dbExperience; + this.experienceContextValue = `experience.${this.experience?.api ?? API.Common}`; + this.contextValue = createContextValue([this.contextValue, this.experienceContextValue]); } async getChildren(): Promise { return [ createGenericElement({ - contextValue: createContextValue(['treeitem.documents', this.mongoCluster.dbExperience?.api ?? '']), + contextValue: createContextValue(['treeItem.documents', this.experienceContextValue]), id: `${this.id}/documents`, label: 'Documents', commandId: 'command.internal.mongoClusters.containerView.open', @@ -90,7 +96,7 @@ export class CollectionItem implements TreeElementWithId, TreeElementWithExperie getTreeItem(): TreeItem { return { id: this.id, - contextValue: createContextValue(['treeitem.collection', this.mongoCluster.dbExperience?.api ?? '']), + contextValue: this.contextValue, label: this.collectionInfo.name, iconPath: new ThemeIcon('folder-opened'), collapsibleState: TreeItemCollapsibleState.Collapsed, diff --git a/src/mongoClusters/tree/DatabaseItem.ts b/src/mongoClusters/tree/DatabaseItem.ts index 25c456274..a66b80818 100644 --- a/src/mongoClusters/tree/DatabaseItem.ts +++ b/src/mongoClusters/tree/DatabaseItem.ts @@ -12,17 +12,21 @@ import { } from '@microsoft/vscode-azext-utils'; import * as vscode from 'vscode'; import { ThemeIcon, TreeItemCollapsibleState, type TreeItem } from 'vscode'; -import { type Experience } from '../../AzureDBExperiences'; +import { API, type Experience } from '../../AzureDBExperiences'; import { ext } from '../../extensionVariables'; +import { type TreeElementWithContextValue } from '../../tree/TreeElementWithContextValue'; import { type TreeElementWithExperience } from '../../tree/TreeElementWithExperience'; import { localize } from '../../utils/localize'; import { MongoClustersClient, type DatabaseItemModel } from '../MongoClustersClient'; import { CollectionItem } from './CollectionItem'; import { type MongoClusterModel } from './MongoClusterModel'; -export class DatabaseItem implements TreeElementWithId, TreeElementWithExperience { - id: string; - experience?: Experience; +export class DatabaseItem implements TreeElementWithId, TreeElementWithExperience, TreeElementWithContextValue { + public readonly id: string; + public readonly experience?: Experience; + public readonly contextValue: string = 'treeItem.database'; + + private readonly experienceContextValue: string = ''; constructor( readonly mongoCluster: MongoClusterModel, @@ -30,6 +34,8 @@ export class DatabaseItem implements TreeElementWithId, TreeElementWithExperienc ) { this.id = `${mongoCluster.id}/${databaseInfo.name}`; this.experience = mongoCluster.dbExperience; + this.experienceContextValue = `experience.${this.experience?.api ?? API.Common}`; + this.contextValue = createContextValue([this.contextValue, this.experienceContextValue]); } async getChildren(): Promise { @@ -40,11 +46,8 @@ export class DatabaseItem implements TreeElementWithId, TreeElementWithExperienc // no databases in there: return [ createGenericElement({ - contextValue: createContextValue([ - 'treeitem.no-collections', - this.mongoCluster.dbExperience?.api ?? '', - ]), - id: `${this.id}/no-databases`, + contextValue: createContextValue(['treeItem.no-collections', this.experienceContextValue]), + id: `${this.id}/no-collections`, label: 'Create collection...', iconPath: new vscode.ThemeIcon('plus'), commandId: 'command.mongoClusters.createCollection', @@ -94,7 +97,7 @@ export class DatabaseItem implements TreeElementWithId, TreeElementWithExperienc getTreeItem(): TreeItem { return { id: this.id, - contextValue: createContextValue(['treeitem.database', this.mongoCluster.dbExperience?.api ?? '']), + contextValue: this.contextValue, label: this.databaseInfo.name, iconPath: new ThemeIcon('database'), // TODO: create our own icon here, this one's shape can change collapsibleState: TreeItemCollapsibleState.Collapsed, diff --git a/src/mongoClusters/tree/IndexItem.ts b/src/mongoClusters/tree/IndexItem.ts index c15e3486e..d4bd544c2 100644 --- a/src/mongoClusters/tree/IndexItem.ts +++ b/src/mongoClusters/tree/IndexItem.ts @@ -10,14 +10,18 @@ import { type TreeElementWithId, } from '@microsoft/vscode-azext-utils'; import { ThemeIcon, TreeItemCollapsibleState, type TreeItem } from 'vscode'; -import { type Experience } from '../../AzureDBExperiences'; +import { API, type Experience } from '../../AzureDBExperiences'; +import { type TreeElementWithContextValue } from '../../tree/TreeElementWithContextValue'; import { type TreeElementWithExperience } from '../../tree/TreeElementWithExperience'; import { type CollectionItemModel, type DatabaseItemModel, type IndexItemModel } from '../MongoClustersClient'; import { type MongoClusterModel } from './MongoClusterModel'; -export class IndexItem implements TreeElementWithId, TreeElementWithExperience { - id: string; - experience?: Experience; +export class IndexItem implements TreeElementWithId, TreeElementWithExperience, TreeElementWithContextValue { + public readonly id: string; + public readonly experience?: Experience; + public readonly contextValue: string = 'treeItem.index'; + + private readonly experienceContextValue: string = ''; constructor( readonly mongoCluster: MongoClusterModel, @@ -27,6 +31,8 @@ export class IndexItem implements TreeElementWithId, TreeElementWithExperience { ) { this.id = `${mongoCluster.id}/${databaseInfo.name}/${collectionInfo.name}/indexes/${indexInfo.name}`; this.experience = mongoCluster.dbExperience; + this.experienceContextValue = `experience.${this.experience?.api ?? API.Common}`; + this.contextValue = createContextValue([this.contextValue, this.experienceContextValue]); } async getChildren(): Promise { @@ -47,7 +53,7 @@ export class IndexItem implements TreeElementWithId, TreeElementWithExperience { getTreeItem(): TreeItem { return { id: this.id, - contextValue: createContextValue(['treeitem.index', this.mongoCluster.dbExperience?.api ?? '']), + contextValue: this.contextValue, label: this.indexInfo.name, iconPath: new ThemeIcon('combine'), // TODO: create our onw icon here, this one's shape can change collapsibleState: TreeItemCollapsibleState.Collapsed, diff --git a/src/mongoClusters/tree/IndexesItem.ts b/src/mongoClusters/tree/IndexesItem.ts index efa7575b0..303bcaf60 100644 --- a/src/mongoClusters/tree/IndexesItem.ts +++ b/src/mongoClusters/tree/IndexesItem.ts @@ -5,15 +5,19 @@ import { createContextValue, type TreeElementBase, type TreeElementWithId } from '@microsoft/vscode-azext-utils'; import { ThemeIcon, TreeItemCollapsibleState, type TreeItem } from 'vscode'; -import { type Experience } from '../../AzureDBExperiences'; +import { API, type Experience } from '../../AzureDBExperiences'; +import { type TreeElementWithContextValue } from '../../tree/TreeElementWithContextValue'; import { type TreeElementWithExperience } from '../../tree/TreeElementWithExperience'; import { MongoClustersClient, type CollectionItemModel, type DatabaseItemModel } from '../MongoClustersClient'; import { IndexItem } from './IndexItem'; import { type MongoClusterModel } from './MongoClusterModel'; -export class IndexesItem implements TreeElementWithId, TreeElementWithExperience { - id: string; - experience?: Experience; +export class IndexesItem implements TreeElementWithId, TreeElementWithExperience, TreeElementWithContextValue { + public readonly id: string; + public readonly experience?: Experience; + public readonly contextValue: string = 'treeItem.indexes'; + + private readonly experienceContextValue: string = ''; constructor( readonly mongoCluster: MongoClusterModel, @@ -22,6 +26,8 @@ export class IndexesItem implements TreeElementWithId, TreeElementWithExperience ) { this.id = `${mongoCluster.id}/${databaseInfo.name}/${collectionInfo.name}/indexes`; this.experience = mongoCluster.dbExperience; + this.experienceContextValue = `experience.${this.experience?.api ?? API.Common}`; + this.contextValue = createContextValue([this.contextValue, this.experienceContextValue]); } async getChildren(): Promise { @@ -35,7 +41,7 @@ export class IndexesItem implements TreeElementWithId, TreeElementWithExperience getTreeItem(): TreeItem { return { id: this.id, - contextValue: createContextValue(['treeitem.indexes', this.mongoCluster.dbExperience?.api ?? '']), + contextValue: this.contextValue, label: 'Indexes', iconPath: new ThemeIcon('combine'), // TODO: create our onw icon here, this one's shape can change collapsibleState: TreeItemCollapsibleState.Collapsed, diff --git a/src/mongoClusters/tree/MongoClusterItemBase.ts b/src/mongoClusters/tree/MongoClusterItemBase.ts index 48eabc8d5..24e474b3f 100644 --- a/src/mongoClusters/tree/MongoClusterItemBase.ts +++ b/src/mongoClusters/tree/MongoClusterItemBase.ts @@ -13,8 +13,9 @@ import { import { type TreeItem } from 'vscode'; import * as vscode from 'vscode'; -import { type Experience } from '../../AzureDBExperiences'; +import { API, type Experience } from '../../AzureDBExperiences'; import { ext } from '../../extensionVariables'; +import { type TreeElementWithContextValue } from '../../tree/TreeElementWithContextValue'; import { type TreeElementWithExperience } from '../../tree/TreeElementWithExperience'; import { localize } from '../../utils/localize'; import { regionToDisplayName } from '../../utils/regionToDisplayName'; @@ -24,13 +25,20 @@ import { DatabaseItem } from './DatabaseItem'; import { type MongoClusterModel } from './MongoClusterModel'; // This info will be available at every level in the tree for immediate access -export abstract class MongoClusterItemBase implements TreeElementWithId, TreeElementWithExperience { - id: string; - experience?: Experience; +export abstract class MongoClusterItemBase + implements TreeElementWithId, TreeElementWithExperience, TreeElementWithContextValue +{ + public readonly id: string; + public readonly experience?: Experience; + public readonly contextValue: string = 'treeItem.mongoCluster'; - constructor(public mongoCluster: MongoClusterModel) { + private readonly experienceContextValue: string = ''; + + protected constructor(public mongoCluster: MongoClusterModel) { this.id = mongoCluster.id ?? ''; this.experience = mongoCluster.dbExperience; + this.experienceContextValue = `experience.${this.experience?.api ?? API.Common}`; + this.contextValue = createContextValue([this.contextValue, this.experienceContextValue]); } /** @@ -97,10 +105,7 @@ export abstract class MongoClusterItemBase implements TreeElementWithId, TreeEle if (databases.length === 0) { return [ createGenericElement({ - contextValue: createContextValue([ - 'treeitem.no-databases', - this.mongoCluster.dbExperience?.api ?? '', - ]), + contextValue: createContextValue(['treeItem.no-databases', this.experienceContextValue]), id: `${this.id}/no-databases`, label: 'Create database...', iconPath: new vscode.ThemeIcon('plus'), @@ -149,7 +154,7 @@ export abstract class MongoClusterItemBase implements TreeElementWithId, TreeEle getTreeItem(): TreeItem { return { id: this.id, - contextValue: createContextValue(['treeitem.mongocluster', this.mongoCluster.dbExperience?.api ?? '']), + contextValue: this.contextValue, label: this.mongoCluster.name, description: this.mongoCluster.sku !== undefined ? `(${this.mongoCluster.sku})` : false, // iconPath: getThemeAgnosticIconPath('CosmosDBAccount.svg'), // Uncomment if icon is available diff --git a/src/mongoClusters/tree/workspace/MongoClusterWorkspaceItem.ts b/src/mongoClusters/tree/workspace/MongoClusterWorkspaceItem.ts index a0287d507..840f83b90 100644 --- a/src/mongoClusters/tree/workspace/MongoClusterWorkspaceItem.ts +++ b/src/mongoClusters/tree/workspace/MongoClusterWorkspaceItem.ts @@ -6,7 +6,6 @@ import { AzureWizard, callWithTelemetryAndErrorHandling, - createContextValue, nonNullProp, nonNullValue, UserCancelledError, @@ -159,7 +158,7 @@ export class MongoClusterWorkspaceItem extends MongoClusterItemBase { getTreeItem(): vscode.TreeItem { return { id: this.id, - contextValue: createContextValue(['treeitem.mongocluster', this.mongoCluster.dbExperience?.api ?? '']), + contextValue: this.contextValue, label: this.mongoCluster.name, description: this.mongoCluster.sku !== undefined ? `(${this.mongoCluster.sku})` : false, iconPath: new vscode.ThemeIcon('server-environment'), // Uncomment if icon is available diff --git a/src/mongoClusters/tree/workspace/MongoDBAccountsWorkspaceItem.ts b/src/mongoClusters/tree/workspace/MongoDBAccountsWorkspaceItem.ts index 8e10d2cb1..5697d0620 100644 --- a/src/mongoClusters/tree/workspace/MongoDBAccountsWorkspaceItem.ts +++ b/src/mongoClusters/tree/workspace/MongoDBAccountsWorkspaceItem.ts @@ -35,7 +35,7 @@ export class MongoDBAccountsWorkspaceItem implements TreeElementWithId, TreeElem return new MongoClusterWorkspaceItem(model); }), createGenericElement({ - contextValue: 'treeitem.newConnection', + contextValue: 'treeItem.newConnection', id: this.id + '/newConnection', label: 'New Connection...', iconPath: new ThemeIcon('plus'), diff --git a/src/tree/AttachedAccountsTreeItem.ts b/src/tree/AttachedAccountsTreeItem.ts index 894bee0bc..28c11a66e 100644 --- a/src/tree/AttachedAccountsTreeItem.ts +++ b/src/tree/AttachedAccountsTreeItem.ts @@ -49,7 +49,7 @@ const localMongoConnectionString: string = 'mongodb://127.0.0.1:27017'; export class AttachedAccountsTreeItem extends AzExtParentTreeItem { public static contextValue: string = 'cosmosDBAttachedAccounts' + (isWindows ? 'WithEmulator' : 'WithoutEmulator'); public readonly contextValue: string = AttachedAccountsTreeItem.contextValue; - public readonly label: string = 'Attached Database Accounts'; + public readonly label: string = 'Attached Database Accounts (Postgres)'; public childTypeLabel: string = 'Account'; public suppressMaskLabel = true; @@ -359,7 +359,10 @@ export class AttachedAccountsTreeItem extends AzExtParentTreeItem { await ext.secretStorage.get(getSecretStorageKey(this._serviceName, id)), 'connectionString', ); - persistedAccounts.push(await this.createTreeItem(connectionString, api, label, id, isEmulator)); + // TODO: Left only Postgres, other types are moved to new tree api v2 + if (api === API.PostgresSingle || api === API.PostgresFlexible) { + persistedAccounts.push(await this.createTreeItem(connectionString, api, label, id, isEmulator)); + } }), ); } diff --git a/src/tree/AzureAccountTreeItemWithAttached.ts b/src/tree/AzureAccountTreeItemWithAttached.ts deleted file mode 100644 index 226a1a43b..000000000 --- a/src/tree/AzureAccountTreeItemWithAttached.ts +++ /dev/null @@ -1,36 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { AzureAccountTreeItemBase } from '@microsoft/vscode-azext-azureutils'; -import { type AzExtTreeItem, type IActionContext, type ISubscriptionContext } from '@microsoft/vscode-azext-utils'; -import { ext } from '../extensionVariables'; -import { AttachedAccountsTreeItem } from './AttachedAccountsTreeItem'; -import { SubscriptionTreeItem } from './SubscriptionTreeItem'; - -export class AzureAccountTreeItemWithAttached extends AzureAccountTreeItemBase { - public constructor(testAccount?: object) { - super(undefined, testAccount); - ext.attachedAccountsNode = new AttachedAccountsTreeItem(this); - } - - public createSubscriptionTreeItem(root: ISubscriptionContext): SubscriptionTreeItem { - return new SubscriptionTreeItem(this, root); - } - - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - const children: AzExtTreeItem[] = await super.loadMoreChildrenImpl(clearCache, context); - return children.concat(ext.attachedAccountsNode); - } - - public compareChildrenImpl(item1: AzExtTreeItem, item2: AzExtTreeItem): number { - if (item1 instanceof AttachedAccountsTreeItem) { - return 1; - } else if (item2 instanceof AttachedAccountsTreeItem) { - return -1; - } else { - return super.compareChildrenImpl(item1, item2); - } - } -} diff --git a/src/tree/CosmosAccountResourceItemBase.ts b/src/tree/CosmosAccountResourceItemBase.ts index a32bb2f29..e3975ccfa 100644 --- a/src/tree/CosmosAccountResourceItemBase.ts +++ b/src/tree/CosmosAccountResourceItemBase.ts @@ -3,18 +3,28 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { createContextValue } from '@microsoft/vscode-azext-utils'; +import { type ResourceBase } from '@microsoft/vscode-azureresources-api'; +import { v4 as uuid } from 'uuid'; import * as vscode from 'vscode'; import { type TreeItem } from 'vscode'; -import { getExperienceLabel, tryGetExperience } from '../AzureDBExperiences'; -import { type CosmosAccountModel } from './CosmosAccountModel'; +import { type Experience } from '../AzureDBExperiences'; import { type CosmosDBTreeElement } from './CosmosDBTreeElement'; +import { type TreeElementWithContextValue } from './TreeElementWithContextValue'; +import { type TreeElementWithExperience } from './TreeElementWithExperience'; -export abstract class CosmosAccountResourceItemBase implements CosmosDBTreeElement { - public id: string; - public contextValue: string = 'cosmosDB.item.account'; +export abstract class CosmosAccountResourceItemBase + implements CosmosDBTreeElement, TreeElementWithExperience, TreeElementWithContextValue +{ + public readonly id: string; + public readonly contextValue: string = 'treeItem.account'; - protected constructor(readonly account: CosmosAccountModel) { - this.id = account.id ?? ''; + protected constructor( + public readonly account: ResourceBase, + public readonly experience: Experience, + ) { + this.id = account.id ?? uuid(); + this.contextValue = createContextValue([this.contextValue, `experience.${this.experience.api}`]); } /** @@ -30,22 +40,11 @@ export abstract class CosmosAccountResourceItemBase implements CosmosDBTreeEleme * @returns The TreeItem object. */ getTreeItem(): TreeItem { - const experience = tryGetExperience(this.account); - if (!experience) { - const accountKindLabel = getExperienceLabel(this.account); - const label: string = this.account.name + (accountKindLabel ? ` (${accountKindLabel})` : ``); - return { - id: this.id, - contextValue: 'cosmosDB.item.account', - label: label, - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - }; - } return { id: this.id, - contextValue: `${experience.api}.item.account`, + contextValue: this.contextValue, label: this.account.name, - description: `(${experience.shortName})`, + description: `(${this.experience.shortName})`, collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, }; } diff --git a/src/tree/CosmosDBBranchDataProvider.ts b/src/tree/CosmosDBBranchDataProvider.ts index 4b9b1cf05..ca1be4315 100644 --- a/src/tree/CosmosDBBranchDataProvider.ts +++ b/src/tree/CosmosDBBranchDataProvider.ts @@ -11,7 +11,7 @@ import { } from '@microsoft/vscode-azext-utils'; import { type BranchDataProvider } from '@microsoft/vscode-azureresources-api'; import * as vscode from 'vscode'; -import { API, tryGetExperience } from '../AzureDBExperiences'; +import { API, CoreExperience, tryGetExperience } from '../AzureDBExperiences'; import { databaseAccountType } from '../constants'; import { ext } from '../extensionVariables'; import { localize } from '../utils/localize'; @@ -22,6 +22,8 @@ import { GraphAccountResourceItem } from './graph/GraphAccountResourceItem'; import { MongoAccountResourceItem } from './mongo/MongoAccountResourceItem'; import { NoSqlAccountResourceItem } from './nosql/NoSqlAccountResourceItem'; import { TableAccountResourceItem } from './table/TableAccountResourceItem'; +import { isTreeElementWithContextValue } from './TreeElementWithContextValue'; +import { isTreeElementWithExperience } from './TreeElementWithExperience'; export class CosmosDBBranchDataProvider extends vscode.Disposable @@ -49,7 +51,20 @@ export class CosmosDBBranchDataProvider context.errorHandling.rethrow = true; context.errorHandling.forceIncludeInReportIssueCommand = true; - return (await element.getChildren?.())?.map((child) => { + if (isTreeElementWithContextValue(element)) { + context.telemetry.properties.parentContext = element.contextValue; + } + + if (isTreeElementWithExperience(element)) { + context.telemetry.properties.experience = element.experience?.api ?? API.Common; + } + + // TODO: values to mask. New TreeElements do not have valueToMask field + // I assume this array should be filled after element.getChildren() call + // And these values should be masked in the context + + const children = (await element.getChildren?.()) ?? []; + return children.map((child) => { return ext.state.wrapItemInStateHandling(child, (child: CosmosDBTreeElement) => this.refresh(child), ) as CosmosDBTreeElement; @@ -75,7 +90,7 @@ export class CosmosDBBranchDataProvider async getResourceItem(resource: CosmosDBResource): Promise { const resourceItem = await callWithTelemetryAndErrorHandling( 'CosmosDBBranchDataProvider.getResourceItem', - async (context: IActionContext) => { + (context: IActionContext) => { const id = nonNullProp(resource, 'id'); const name = nonNullProp(resource, 'name'); const type = nonNullProp(resource, 'type'); @@ -107,7 +122,8 @@ export class CosmosDBBranchDataProvider return new TableAccountResourceItem(accountModel, experience); } - // Unknown experience + // Unknown experience fallback + return new NoSqlAccountResourceItem(accountModel, CoreExperience); } else { // Unknown resource type } diff --git a/src/tree/CosmosDBWorkspaceBranchDataProvider.ts b/src/tree/CosmosDBWorkspaceBranchDataProvider.ts index 5fe67fe8c..6202cd22d 100644 --- a/src/tree/CosmosDBWorkspaceBranchDataProvider.ts +++ b/src/tree/CosmosDBWorkspaceBranchDataProvider.ts @@ -11,11 +11,14 @@ import { } from '@microsoft/vscode-azext-utils'; import { type BranchDataProvider } from '@microsoft/vscode-azureresources-api'; import * as vscode from 'vscode'; +import { API } from '../AzureDBExperiences'; import { ext } from '../extensionVariables'; import { localize } from '../utils/localize'; +import { CosmosDBAttachedAccountsResourceItem } from './attached/CosmosDBAttachedAccountsResourceItem'; import { type CosmosDBResource } from './CosmosAccountModel'; import { type CosmosDBTreeElement } from './CosmosDBTreeElement'; -import { CosmosDBAttachedAccountsResourceItem } from './attached/CosmosDBAttachedAccountsResourceItem'; +import { isTreeElementWithContextValue } from './TreeElementWithContextValue'; +import { isTreeElementWithExperience } from './TreeElementWithExperience'; export class CosmosDBWorkspaceBranchDataProvider extends vscode.Disposable @@ -40,8 +43,24 @@ export class CosmosDBWorkspaceBranchDataProvider 'CosmosDBWorkspaceBranchDataProvider.getChildren', async (context: IActionContext) => { context.telemetry.properties.view = 'workspace'; + context.errorHandling.suppressDisplay = true; + context.errorHandling.rethrow = true; + context.errorHandling.forceIncludeInReportIssueCommand = true; + + if (isTreeElementWithContextValue(element)) { + context.telemetry.properties.parentContext = element.contextValue; + } + + if (isTreeElementWithExperience(element)) { + context.telemetry.properties.experience = element.experience?.api ?? API.Common; + } + + // TODO: values to mask. New TreeElements do not have valueToMask field + // I assume this array should be filled after element.getChildren() call + // And these values should be masked in the context - return (await element.getChildren?.())?.map((child) => { + const children = (await element.getChildren?.()) ?? []; + return children.map((child) => { return ext.state.wrapItemInStateHandling(child, (child: CosmosDBTreeElement) => this.refresh(child), ) as CosmosDBTreeElement; diff --git a/src/tree/TreeElementWithContextValue.ts b/src/tree/TreeElementWithContextValue.ts new file mode 100644 index 000000000..a7bed5d25 --- /dev/null +++ b/src/tree/TreeElementWithContextValue.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export type TreeElementWithContextValue = { + readonly contextValue: string; +}; + +export function isTreeElementWithContextValue(node: unknown): node is TreeElementWithContextValue { + return typeof node === 'object' && node !== null && 'contextValue' in node; +} diff --git a/src/tree/attached/CosmosDBAttachedAccountsResourceItem.ts b/src/tree/attached/CosmosDBAttachedAccountsResourceItem.ts index 9ed64cb44..c981f0c08 100644 --- a/src/tree/attached/CosmosDBAttachedAccountsResourceItem.ts +++ b/src/tree/attached/CosmosDBAttachedAccountsResourceItem.ts @@ -5,9 +5,9 @@ import { callWithTelemetryAndErrorHandling, + createContextValue, createGenericElement, nonNullValue, - type IActionContext, } from '@microsoft/vscode-azext-utils'; import vscode, { ThemeIcon, TreeItemCollapsibleState } from 'vscode'; import { API, getExperienceFromApi } from '../../AzureDBExperiences'; @@ -18,18 +18,22 @@ import { type CosmosDBTreeElement } from '../CosmosDBTreeElement'; import { GraphAccountAttachedResourceItem } from '../graph/GraphAccountAttachedResourceItem'; import { NoSqlAccountAttachedResourceItem } from '../nosql/NoSqlAccountAttachedResourceItem'; import { TableAccountAttachedResourceItem } from '../table/TableAccountAttachedResourceItem'; +import { type TreeElementWithContextValue } from '../TreeElementWithContextValue'; import { WorkspaceResourceType } from '../workspace/SharedWorkspaceResourceProvider'; import { SharedWorkspaceStorage, type SharedWorkspaceStorageItem } from '../workspace/SharedWorkspaceStorage'; import { type CosmosDBAttachedAccountModel } from './CosmosDBAttachedAccountModel'; -export class CosmosDBAttachedAccountsResourceItem implements CosmosDBTreeElement { - public id: string = WorkspaceResourceType.AttachedAccounts; - public contextValue: string = 'cosmosDB.workspace.item.accounts'; +export class CosmosDBAttachedAccountsResourceItem implements CosmosDBTreeElement, TreeElementWithContextValue { + public readonly id: string = WorkspaceResourceType.AttachedAccounts; + public readonly contextValue: string = 'treeItem.accounts'; private readonly attachDatabaseAccount: CosmosDBTreeElement; private readonly attachEmulator: CosmosDBTreeElement; constructor() { + this.id = WorkspaceResourceType.AttachedAccounts; + this.contextValue = createContextValue([this.contextValue, `attachedAccounts`]); + this.attachDatabaseAccount = createGenericElement({ id: `${this.id}/attachAccount`, contextValue: `${this.contextValue}/attachAccount`, @@ -50,31 +54,20 @@ export class CosmosDBAttachedAccountsResourceItem implements CosmosDBTreeElement } public async getChildren(): Promise { - const items = await callWithTelemetryAndErrorHandling('getChildren', async (context: IActionContext) => { - context.telemetry.properties.view = 'workspace'; - context.telemetry.properties.parentContext = this.contextValue; - - // TODO: remove after a few releases - await this.migrateV1AccountsToV2(); // Move accounts from the old storage format to the new one - - const items = await SharedWorkspaceStorage.getItems(this.id); - - return await this.getChildrenImpl(items); - }); + // TODO: remove after a few releases + await this.pickSupportedAccounts(); // Move accounts from the old storage format to the new one + const items = await SharedWorkspaceStorage.getItems(this.id); + const children = await this.getChildrenImpl(items); const auxItems = isWindows ? [this.attachDatabaseAccount, this.attachEmulator] : [this.attachDatabaseAccount]; - const result: CosmosDBTreeElement[] = []; - result.push(...(items ?? [])); - result.push(...auxItems); - - return result; + return [...children, ...auxItems]; } public getTreeItem() { return { id: this.id, - contextValue: 'cosmosDB.workspace.item.accounts', + contextValue: this.contextValue, label: 'Attached Database Accounts', iconPath: new ThemeIcon('plug'), collapsibleState: TreeItemCollapsibleState.Collapsed, @@ -120,6 +113,62 @@ export class CosmosDBAttachedAccountsResourceItem implements CosmosDBTreeElement ); } + protected async pickSupportedAccounts(): Promise { + return callWithTelemetryAndErrorHandling( + 'CosmosDBAttachedAccountsResourceItem.pickSupportedAccounts', + async () => { + const serviceName = 'ms-azuretools.vscode-cosmosdb.connectionStrings'; + const value: string | undefined = ext.context.globalState.get(serviceName); + + if (!value) { + return; + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const accounts: (string | IPersistedAccount)[] = JSON.parse(value); + for (const account of accounts) { + let id: string; + let name: string; + let isEmulator: boolean; + let api: API; + + if (typeof account === 'string') { + // Default to Mongo if the value is a string for the sake of backwards compatibility + // (Mongo was originally the only account type that could be attached) + id = account; + name = account; + api = API.MongoDB; + isEmulator = false; + } else { + id = (account).id; + name = (account).id; + api = (account).defaultExperience; + isEmulator = (account).isEmulator ?? false; + } + + // TODO: Ignore Postgres accounts until we have a way to handle them + if (api === API.PostgresSingle || api === API.PostgresFlexible) { + continue; + } + + const connectionString: string = nonNullValue( + await ext.secretStorage.get(`${serviceName}.${id}`), + 'connectionString', + ); + + const storageItem: SharedWorkspaceStorageItem = { + id, + name, + properties: { isEmulator, api }, + secrets: [connectionString], + }; + + await SharedWorkspaceStorage.push(WorkspaceResourceType.AttachedAccounts, storageItem, true); + } + }, + ); + } + protected async migrateV1AccountsToV2(): Promise { const serviceName = 'ms-azuretools.vscode-cosmosdb.connectionStrings'; const value: string | undefined = ext.context.globalState.get(serviceName); diff --git a/src/tree/docdb/DocumentDBAccountAttachedResourceItem.ts b/src/tree/docdb/DocumentDBAccountAttachedResourceItem.ts index 3a62d66b0..93691ae1f 100644 --- a/src/tree/docdb/DocumentDBAccountAttachedResourceItem.ts +++ b/src/tree/docdb/DocumentDBAccountAttachedResourceItem.ts @@ -14,48 +14,30 @@ import { getSignedInPrincipalIdForAccountEndpoint } from '../../docdb/utils/azur import { isRbacException, showRbacPermissionError } from '../../docdb/utils/rbacUtils'; import { localize } from '../../utils/localize'; import { type CosmosDBAttachedAccountModel } from '../attached/CosmosDBAttachedAccountModel'; +import { CosmosAccountResourceItemBase } from '../CosmosAccountResourceItemBase'; import { type CosmosDBTreeElement } from '../CosmosDBTreeElement'; import { type AccountInfo } from './AccountInfo'; -export abstract class DocumentDBAccountAttachedResourceItem implements CosmosDBTreeElement { - public id: string; - public contextValue: string = 'cosmosDB.workspace.item.account'; +export abstract class DocumentDBAccountAttachedResourceItem extends CosmosAccountResourceItemBase { + public declare readonly account: CosmosDBAttachedAccountModel; // To prevent the RBAC notification from showing up multiple times protected hasShownRbacNotification: boolean = false; - protected constructor( - protected account: CosmosDBAttachedAccountModel, - protected experience: Experience, - ) { - this.contextValue = `${experience.api}.workspace.item.account`; + protected constructor(account: CosmosDBAttachedAccountModel, experience: Experience) { + super(account, experience); } public async getChildren(): Promise { - const result = await callWithTelemetryAndErrorHandling('getChildren', async (context: IActionContext) => { - context.telemetry.properties.experience = this.experience.api; - context.telemetry.properties.parentContext = this.contextValue; - context.errorHandling.rethrow = true; - - const accountInfo = await this.getAccountInfo(this.account); - const cosmosClient = getCosmosClient(accountInfo.endpoint, accountInfo.credentials, false); - const databases = await this.getDatabases(accountInfo, cosmosClient); - return await this.getChildrenImpl(accountInfo, databases); - }); + const accountInfo = await this.getAccountInfo(this.account); + const cosmosClient = getCosmosClient(accountInfo.endpoint, accountInfo.credentials, false); + const databases = await this.getDatabases(accountInfo, cosmosClient); - return result ?? []; + return this.getChildrenImpl(accountInfo, databases); } public getTreeItem(): TreeItem { - // This function is a bit easier than the ancestor's getTreeItem function - return { - id: this.id, - contextValue: this.contextValue, - iconPath: getThemeAgnosticIconPath('CosmosDBAccount.svg'), - label: this.account.name, - description: `(${this.experience.shortName})`, - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - }; + return { ...super.getTreeItem(), iconPath: getThemeAgnosticIconPath('CosmosDBAccount.svg') }; } protected async getAccountInfo(account: CosmosDBAttachedAccountModel): Promise | never { diff --git a/src/tree/docdb/DocumentDBAccountResourceItem.ts b/src/tree/docdb/DocumentDBAccountResourceItem.ts index 2cd03a0cf..968a1b1f6 100644 --- a/src/tree/docdb/DocumentDBAccountResourceItem.ts +++ b/src/tree/docdb/DocumentDBAccountResourceItem.ts @@ -9,6 +9,7 @@ import { type CosmosClient, type DatabaseDefinition, type Resource } from '@azur import { callWithTelemetryAndErrorHandling, type IActionContext } from '@microsoft/vscode-azext-utils'; import vscode, { type TreeItem } from 'vscode'; import { type Experience } from '../../AzureDBExperiences'; +import { getThemeAgnosticIconPath } from '../../constants'; import { type CosmosDBCredential, type CosmosDBKeyCredential, getCosmosClient } from '../../docdb/getCosmosClient'; import { getSignedInPrincipalIdForAccountEndpoint } from '../../docdb/utils/azureSessionHelper'; import { ensureRbacPermissionV2, isRbacException, showRbacPermissionError } from '../../docdb/utils/rbacUtils'; @@ -16,54 +17,44 @@ import { createCosmosDBManagementClient } from '../../utils/azureClients'; import { localize } from '../../utils/localize'; import { nonNullProp } from '../../utils/nonNull'; import { type CosmosAccountModel } from '../CosmosAccountModel'; +import { CosmosAccountResourceItemBase } from '../CosmosAccountResourceItemBase'; import { type CosmosDBTreeElement } from '../CosmosDBTreeElement'; import { type AccountInfo } from './AccountInfo'; -export abstract class DocumentDBAccountResourceItem implements CosmosDBTreeElement { - public id: string; - public contextValue: string = 'cosmosDB.item.account'; +export abstract class DocumentDBAccountResourceItem extends CosmosAccountResourceItemBase { + public declare readonly account: CosmosAccountModel; // To prevent the RBAC notification from showing up multiple times protected hasShownRbacNotification: boolean = false; - protected constructor( - protected account: CosmosAccountModel, - protected experience: Experience, - ) { - this.contextValue = `${experience.api}.item.account`; + protected constructor(account: CosmosAccountModel, experience: Experience) { + super(account, experience); } public async getChildren(): Promise { - const result = await callWithTelemetryAndErrorHandling('getChildren', async (context: IActionContext) => { - context.telemetry.properties.experience = this.experience.api; - context.telemetry.properties.parentContext = this.contextValue; - context.errorHandling.rethrow = true; - - const accountInfo = await this.getAccountInfo(context, this.account); - const cosmosClient = getCosmosClient(accountInfo.endpoint, accountInfo.credentials, false); - const databases = await this.getDatabases(accountInfo, cosmosClient); - return await this.getChildrenImpl(accountInfo, databases); - }); + const accountInfo = await this.getAccountInfo(this.account); + const cosmosClient = getCosmosClient(accountInfo.endpoint, accountInfo.credentials, false); + const databases = await this.getDatabases(accountInfo, cosmosClient); - return result ?? []; + return this.getChildrenImpl(accountInfo, databases); } public getTreeItem(): TreeItem { - // This function is a bit easier than the ancestor's getTreeItem function - return { - id: this.id, - contextValue: this.contextValue, - label: this.account.name, - description: `(${this.experience.shortName})`, - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - }; + return { ...super.getTreeItem(), iconPath: getThemeAgnosticIconPath('CosmosDBAccount.svg') }; } - protected async getAccountInfo(context: IActionContext, account: CosmosAccountModel): Promise | never { + protected async getAccountInfo(account: CosmosAccountModel): Promise | never { const id = nonNullProp(account, 'id'); const name = nonNullProp(account, 'name'); const resourceGroup = nonNullProp(account, 'resourceGroup'); - const client = await createCosmosDBManagementClient(context, account.subscription); + + const client = await callWithTelemetryAndErrorHandling('getAccountInfo', async (context: IActionContext) => { + return createCosmosDBManagementClient(context, account.subscription); + }); + + if (!client) { + throw new Error('Failed to connect to Cosmos DB account'); + } const databaseAccount = await client.databaseAccounts.get(resourceGroup, name); const credentials = await this.getCredentials(name, resourceGroup, client, databaseAccount); diff --git a/src/tree/docdb/DocumentDBContainerResourceItem.ts b/src/tree/docdb/DocumentDBContainerResourceItem.ts index da22b28a5..7714a786f 100644 --- a/src/tree/docdb/DocumentDBContainerResourceItem.ts +++ b/src/tree/docdb/DocumentDBContainerResourceItem.ts @@ -3,39 +3,34 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { callWithTelemetryAndErrorHandling, type IActionContext } from '@microsoft/vscode-azext-utils'; -import { v4 as uuid } from 'uuid'; +import { createContextValue } from '@microsoft/vscode-azext-utils'; import vscode, { type TreeItem } from 'vscode'; import { type Experience } from '../../AzureDBExperiences'; import { type CosmosDBTreeElement } from '../CosmosDBTreeElement'; +import { type TreeElementWithContextValue } from '../TreeElementWithContextValue'; +import { type TreeElementWithExperience } from '../TreeElementWithExperience'; import { type DocumentDBContainerModel } from './models/DocumentDBContainerModel'; -export abstract class DocumentDBContainerResourceItem implements CosmosDBTreeElement { - public id: string; - public contextValue: string = 'cosmosDB.item.container'; +export abstract class DocumentDBContainerResourceItem + implements CosmosDBTreeElement, TreeElementWithExperience, TreeElementWithContextValue +{ + public readonly id: string; + public readonly contextValue: string = 'treeItem.container'; protected constructor( - protected readonly model: DocumentDBContainerModel, - protected readonly experience: Experience, + public readonly model: DocumentDBContainerModel, + public readonly experience: Experience, ) { - this.id = uuid(); - this.contextValue = `${experience.api}.item.container`; + this.id = `${model.accountInfo.id}/${model.database.id}/${model.container.id}`; + this.contextValue = createContextValue([this.contextValue, `experience.${this.experience.api}`]); } async getChildren(): Promise { - const result = await callWithTelemetryAndErrorHandling('getChildren', async (context: IActionContext) => { - context.telemetry.properties.experience = this.experience.api; - context.telemetry.properties.parentContext = this.contextValue; - context.errorHandling.rethrow = true; + const triggers = await this.getChildrenTriggersImpl(); + const storedProcedures = await this.getChildrenStoredProceduresImpl(); + const items = await this.getChildrenItemsImpl(); - const triggers = await this.getChildrenTriggersImpl(); - const storedProcedures = await this.getChildrenStoredProceduresImpl(); - const items = await this.getChildrenItemsImpl(); - - return [items, storedProcedures, triggers].filter((r) => r !== undefined); - }); - - return result ?? []; + return [items, storedProcedures, triggers].filter((r) => r !== undefined); } getTreeItem(): TreeItem { diff --git a/src/tree/docdb/DocumentDBDatabaseResourceItem.ts b/src/tree/docdb/DocumentDBDatabaseResourceItem.ts index 20f7e561a..c199ab92b 100644 --- a/src/tree/docdb/DocumentDBDatabaseResourceItem.ts +++ b/src/tree/docdb/DocumentDBDatabaseResourceItem.ts @@ -4,40 +4,35 @@ *--------------------------------------------------------------------------------------------*/ import { type ContainerDefinition, type CosmosClient, type Resource } from '@azure/cosmos'; -import { callWithTelemetryAndErrorHandling, type IActionContext } from '@microsoft/vscode-azext-utils'; -import { v4 as uuid } from 'uuid'; +import { createContextValue } from '@microsoft/vscode-azext-utils'; import vscode, { type TreeItem } from 'vscode'; import { type Experience } from '../../AzureDBExperiences'; import { getCosmosClient } from '../../docdb/getCosmosClient'; import { type CosmosDBTreeElement } from '../CosmosDBTreeElement'; +import { type TreeElementWithContextValue } from '../TreeElementWithContextValue'; +import { type TreeElementWithExperience } from '../TreeElementWithExperience'; import { type DocumentDBDatabaseModel } from './models/DocumentDBDatabaseModel'; -export abstract class DocumentDBDatabaseResourceItem implements CosmosDBTreeElement { - public id: string; - public contextValue: string = 'cosmosDB.item.database'; +export abstract class DocumentDBDatabaseResourceItem + implements CosmosDBTreeElement, TreeElementWithExperience, TreeElementWithContextValue +{ + public readonly id: string; + public readonly contextValue: string = 'treeItem.database'; protected constructor( - protected readonly model: DocumentDBDatabaseModel, - protected readonly experience: Experience, + public readonly model: DocumentDBDatabaseModel, + public readonly experience: Experience, ) { - this.id = uuid(); - this.contextValue = `${experience.api}.item.database`; + this.id = `${model.accountInfo.id}/${model.database.id}`; + this.contextValue = createContextValue([this.contextValue, `experience.${this.experience.api}`]); } async getChildren(): Promise { - const result = await callWithTelemetryAndErrorHandling('getChildren', async (context: IActionContext) => { - context.telemetry.properties.experience = this.experience.api; - context.telemetry.properties.parentContext = this.contextValue; - context.errorHandling.rethrow = true; + const { endpoint, credentials, isEmulator } = this.model.accountInfo; + const cosmosClient = getCosmosClient(endpoint, credentials, isEmulator); + const containers = await this.getContainers(cosmosClient); - const { endpoint, credentials, isEmulator } = this.model.accountInfo; - const cosmosClient = getCosmosClient(endpoint, credentials, isEmulator); - const containers = await this.getContainers(cosmosClient); - - return await this.getChildrenImpl(containers); - }); - - return result ?? []; + return this.getChildrenImpl(containers); } getTreeItem(): TreeItem { diff --git a/src/tree/docdb/DocumentDBItemResourceItem.ts b/src/tree/docdb/DocumentDBItemResourceItem.ts index ea06ccd17..8db80c69a 100644 --- a/src/tree/docdb/DocumentDBItemResourceItem.ts +++ b/src/tree/docdb/DocumentDBItemResourceItem.ts @@ -3,26 +3,28 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { v4 as uuid } from 'uuid'; +import { createContextValue } from '@microsoft/vscode-azext-utils'; import vscode, { type TreeItem } from 'vscode'; import { type Experience } from '../../AzureDBExperiences'; import { extractPartitionKey, getDocumentId } from '../../utils/document'; import { type CosmosDBTreeElement } from '../CosmosDBTreeElement'; +import { type TreeElementWithContextValue } from '../TreeElementWithContextValue'; +import { type TreeElementWithExperience } from '../TreeElementWithExperience'; import { type DocumentDBItemModel } from './models/DocumentDBItemModel'; -export abstract class DocumentDBItemResourceItem implements CosmosDBTreeElement { - public id: string; - public contextValue: string = 'cosmosDB.item.item'; +export abstract class DocumentDBItemResourceItem + implements CosmosDBTreeElement, TreeElementWithExperience, TreeElementWithContextValue +{ + public readonly id: string; + public readonly contextValue: string = 'treeItem.document'; protected constructor( - protected readonly model: DocumentDBItemModel, - protected readonly experience: Experience, + public readonly model: DocumentDBItemModel, + public readonly experience: Experience, ) { - // Generate a unique ID for the item - // This is used to identify the item in the tree, not the item itself - // The item id is not guaranteed to be unique - this.id = uuid(); - this.contextValue = `${experience.api}.item.item`; + const uniqueId = this.generateUniqueId(this.model); + this.id = `${model.accountInfo.id}/${model.database.id}/${model.container.id}/documents/${uniqueId}`; + this.contextValue = createContextValue([this.contextValue, `experience.${this.experience.api}`]); } getTreeItem(): TreeItem { @@ -60,27 +62,49 @@ export abstract class DocumentDBItemResourceItem implements CosmosDBTreeElement if (!this.model.container.partitionKey || this.model.container.partitionKey.paths.length === 0) { return ''; } + const partitionKeyPaths = this.model.container.partitionKey.paths.join(', '); - let partitionKeyValues = extractPartitionKey(this.model.item, this.model.container.partitionKey); - partitionKeyValues = Array.isArray(partitionKeyValues) ? partitionKeyValues : [partitionKeyValues]; - partitionKeyValues = partitionKeyValues.map((v) => { - if (v === null) { - return '\\'; - } - if (v === undefined) { - return '\\'; - } - if (typeof v === 'object') { - return JSON.stringify(v); - } - return v; - }); + const partitionKeyValues = this.generatePartitionKeyValue(this.model); return ( '### Partition Key\n' + '---\n' + `- Paths: **${partitionKeyPaths}**\n` + - `- Values: **${partitionKeyValues.join(', ')}**\n` + `- Values: **${partitionKeyValues}**\n` ); } + + protected generateUniqueId(model: DocumentDBItemModel): string { + const documentId = getDocumentId(model.item, model.container.partitionKey); + const id = documentId?.id; + const rid = documentId?._rid; + const partitionKeyValues = this.generatePartitionKeyValue(model); + + return `${id || ''}|${partitionKeyValues || ''}|${rid || ''}`; + } + + protected generatePartitionKeyValue(model: DocumentDBItemModel): string { + if (!model.container.partitionKey || model.container.partitionKey.paths.length === 0) { + return ''; + } + + let partitionKeyValues = extractPartitionKey(model.item, model.container.partitionKey); + partitionKeyValues = Array.isArray(partitionKeyValues) ? partitionKeyValues : [partitionKeyValues]; + partitionKeyValues = partitionKeyValues + .map((v) => { + if (v === null) { + return '\\'; + } + if (v === undefined) { + return '\\'; + } + if (typeof v === 'object') { + return JSON.stringify(v); + } + return v; + }) + .join(', '); + + return partitionKeyValues; + } } diff --git a/src/tree/docdb/DocumentDBItemsResourceItem.ts b/src/tree/docdb/DocumentDBItemsResourceItem.ts index 89e085c9f..5a1dce80e 100644 --- a/src/tree/docdb/DocumentDBItemsResourceItem.ts +++ b/src/tree/docdb/DocumentDBItemsResourceItem.ts @@ -4,58 +4,51 @@ *--------------------------------------------------------------------------------------------*/ import { type CosmosClient, type FeedOptions, type ItemDefinition, type QueryIterator } from '@azure/cosmos'; -import { - callWithTelemetryAndErrorHandling, - createGenericElement, - type IActionContext, -} from '@microsoft/vscode-azext-utils'; -import { v4 as uuid } from 'uuid'; +import { createContextValue, createGenericElement, type IActionContext } from '@microsoft/vscode-azext-utils'; import vscode, { type TreeItem } from 'vscode'; import { type Experience } from '../../AzureDBExperiences'; import { getCosmosClient } from '../../docdb/getCosmosClient'; import { getBatchSizeSetting } from '../../utils/workspacUtils'; import { type CosmosDBTreeElement } from '../CosmosDBTreeElement'; +import { type TreeElementWithContextValue } from '../TreeElementWithContextValue'; +import { type TreeElementWithExperience } from '../TreeElementWithExperience'; import { type DocumentDBItemsModel } from './models/DocumentDBItemsModel'; -export abstract class DocumentDBItemsResourceItem implements CosmosDBTreeElement { - public id: string; - public contextValue: string = 'cosmosDB.item.items'; +export abstract class DocumentDBItemsResourceItem + implements CosmosDBTreeElement, TreeElementWithExperience, TreeElementWithContextValue +{ + public readonly id: string; + public readonly contextValue: string = 'treeItem.documents'; protected iterator: QueryIterator | undefined; protected cachedItems: ItemDefinition[] = []; protected hasMoreChildren: boolean = true; protected constructor( - protected readonly model: DocumentDBItemsModel, - protected readonly experience: Experience, + public readonly model: DocumentDBItemsModel, + public readonly experience: Experience, ) { - this.id = uuid(); - this.contextValue = `${experience.api}.item.items`; + this.id = `${model.accountInfo.id}/${model.database.id}/${model.container.id}/documents`; + this.contextValue = createContextValue([this.contextValue, `experience.${this.experience.api}`]); } public async getChildren(): Promise { - const result = await callWithTelemetryAndErrorHandling('getChildren', async (context: IActionContext) => { - context.telemetry.properties.experience = this.experience.api; - context.telemetry.properties.parentContext = this.contextValue; - context.errorHandling.rethrow = true; + if (this.iterator && this.cachedItems.length > 0) { + // ignore + } else { + // Fetch the first batch + const batchSize = getBatchSizeSetting(); + const { endpoint, credentials, isEmulator } = this.model.accountInfo; + const cosmosClient = getCosmosClient(endpoint, credentials, isEmulator); - if (this.iterator && this.cachedItems.length > 0) { - // ignore - } else { - // Fetch the first batch - const batchSize = getBatchSizeSetting(); - const { endpoint, credentials, isEmulator } = this.model.accountInfo; - const cosmosClient = getCosmosClient(endpoint, credentials, isEmulator); + this.iterator = this.getIterator(cosmosClient, { maxItemCount: batchSize }); - this.iterator = this.getIterator(cosmosClient, { maxItemCount: batchSize }); - - await this.getItems(this.iterator); - } + await this.getItems(this.iterator); + } - return await this.getChildrenImpl(this.cachedItems); - }); + const result = await this.getChildrenImpl(this.cachedItems); - if (result && this.hasMoreChildren) { + if (this.hasMoreChildren) { result.push( createGenericElement({ contextValue: this.contextValue, @@ -81,7 +74,7 @@ export abstract class DocumentDBItemsResourceItem implements CosmosDBTreeElement ); } - return result ?? []; + return result; } getTreeItem(): TreeItem { diff --git a/src/tree/docdb/DocumentDBStoredProcedureResourceItem.ts b/src/tree/docdb/DocumentDBStoredProcedureResourceItem.ts index 67dcfacd5..8ec5890b8 100644 --- a/src/tree/docdb/DocumentDBStoredProcedureResourceItem.ts +++ b/src/tree/docdb/DocumentDBStoredProcedureResourceItem.ts @@ -3,22 +3,26 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { v4 as uuid } from 'uuid'; +import { createContextValue } from '@microsoft/vscode-azext-utils'; import vscode, { type TreeItem } from 'vscode'; import { type Experience } from '../../AzureDBExperiences'; import { type CosmosDBTreeElement } from '../CosmosDBTreeElement'; +import { type TreeElementWithContextValue } from '../TreeElementWithContextValue'; +import { type TreeElementWithExperience } from '../TreeElementWithExperience'; import { type DocumentDBStoredProcedureModel } from './models/DocumentDBStoredProcedureModel'; -export abstract class DocumentDBStoredProcedureResourceItem implements CosmosDBTreeElement { - public id: string; - public contextValue: string = 'cosmosDB.item.storedProcedure'; +export abstract class DocumentDBStoredProcedureResourceItem + implements CosmosDBTreeElement, TreeElementWithExperience, TreeElementWithContextValue +{ + public readonly id: string; + public readonly contextValue: string = 'treeItem.storedProcedure'; protected constructor( - protected readonly model: DocumentDBStoredProcedureModel, - protected readonly experience: Experience, + public readonly model: DocumentDBStoredProcedureModel, + public readonly experience: Experience, ) { - this.id = uuid(); - this.contextValue = `${experience.api}.item.storedProcedure`; + this.id = `${model.accountInfo.id}/${model.database.id}/${model.container.id}/storedProcedures/${model.procedure.id}`; + this.contextValue = createContextValue([this.contextValue, `experience.${this.experience.api}`]); } getTreeItem(): TreeItem { diff --git a/src/tree/docdb/DocumentDBStoredProceduresResourceItem.ts b/src/tree/docdb/DocumentDBStoredProceduresResourceItem.ts index 313494a47..36d7d93c8 100644 --- a/src/tree/docdb/DocumentDBStoredProceduresResourceItem.ts +++ b/src/tree/docdb/DocumentDBStoredProceduresResourceItem.ts @@ -4,40 +4,35 @@ *--------------------------------------------------------------------------------------------*/ import { type CosmosClient, type Resource, type StoredProcedureDefinition } from '@azure/cosmos'; -import { callWithTelemetryAndErrorHandling, type IActionContext } from '@microsoft/vscode-azext-utils'; -import { v4 as uuid } from 'uuid'; +import { createContextValue } from '@microsoft/vscode-azext-utils'; import vscode, { type TreeItem } from 'vscode'; import { type Experience } from '../../AzureDBExperiences'; import { getCosmosClient } from '../../docdb/getCosmosClient'; import { type CosmosDBTreeElement } from '../CosmosDBTreeElement'; +import { type TreeElementWithContextValue } from '../TreeElementWithContextValue'; +import { type TreeElementWithExperience } from '../TreeElementWithExperience'; import { type DocumentDBStoredProceduresModel } from './models/DocumentDBStoredProceduresModel'; -export abstract class DocumentDBStoredProceduresResourceItem implements CosmosDBTreeElement { - public id: string; - public contextValue: string = 'cosmosDB.item.storedProcedures'; +export abstract class DocumentDBStoredProceduresResourceItem + implements CosmosDBTreeElement, TreeElementWithExperience, TreeElementWithContextValue +{ + public readonly id: string; + public readonly contextValue: string = 'treeItem.storedProcedures'; protected constructor( - protected readonly model: DocumentDBStoredProceduresModel, - protected readonly experience: Experience, + public readonly model: DocumentDBStoredProceduresModel, + public readonly experience: Experience, ) { - this.id = uuid(); - this.contextValue = `${experience.api}.item.storedProcedures`; + this.id = `${model.accountInfo.id}/${model.database.id}/${model.container.id}/storedProcedures`; + this.contextValue = createContextValue([this.contextValue, `experience.${this.experience.api}`]); } public async getChildren(): Promise { - const result = await callWithTelemetryAndErrorHandling('getChildren', async (context: IActionContext) => { - context.telemetry.properties.experience = this.experience.api; - context.telemetry.properties.parentContext = this.contextValue; - context.errorHandling.rethrow = true; + const { endpoint, credentials, isEmulator } = this.model.accountInfo; + const cosmosClient = getCosmosClient(endpoint, credentials, isEmulator); + const storedProcedures = await this.getStoredProcedures(cosmosClient); - const { endpoint, credentials, isEmulator } = this.model.accountInfo; - const cosmosClient = getCosmosClient(endpoint, credentials, isEmulator); - const storedProcedures = await this.getStoredProcedures(cosmosClient); - - return await this.getChildrenImpl(storedProcedures); - }); - - return result ?? []; + return this.getChildrenImpl(storedProcedures); } getTreeItem(): TreeItem { diff --git a/src/tree/docdb/DocumentDBTriggerResourceItem.ts b/src/tree/docdb/DocumentDBTriggerResourceItem.ts index e7f885418..1a3acd125 100644 --- a/src/tree/docdb/DocumentDBTriggerResourceItem.ts +++ b/src/tree/docdb/DocumentDBTriggerResourceItem.ts @@ -3,22 +3,26 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { v4 as uuid } from 'uuid'; +import { createContextValue } from '@microsoft/vscode-azext-utils'; import vscode, { type TreeItem } from 'vscode'; import { type Experience } from '../../AzureDBExperiences'; import { type CosmosDBTreeElement } from '../CosmosDBTreeElement'; +import { type TreeElementWithContextValue } from '../TreeElementWithContextValue'; +import { type TreeElementWithExperience } from '../TreeElementWithExperience'; import { type DocumentDBTriggerModel } from './models/DocumentDBTriggerModel'; -export abstract class DocumentDBTriggerResourceItem implements CosmosDBTreeElement { - public id: string; - public contextValue: string = 'cosmosDB.item.trigger'; +export abstract class DocumentDBTriggerResourceItem + implements CosmosDBTreeElement, TreeElementWithExperience, TreeElementWithContextValue +{ + public readonly id: string; + public readonly contextValue: string = 'treeItem.trigger'; protected constructor( - protected readonly model: DocumentDBTriggerModel, - protected readonly experience: Experience, + public readonly model: DocumentDBTriggerModel, + public readonly experience: Experience, ) { - this.id = uuid(); - this.contextValue = `${experience.api}.item.trigger`; + this.id = `${model.accountInfo.id}/${model.database.id}/${model.container.id}/triggers/${model.trigger.id}`; + this.contextValue = createContextValue([this.contextValue, `experience.${this.experience.api}`]); } getTreeItem(): TreeItem { diff --git a/src/tree/docdb/DocumentDBTriggersResourceItem.ts b/src/tree/docdb/DocumentDBTriggersResourceItem.ts index 3adb78907..e3dcd3351 100644 --- a/src/tree/docdb/DocumentDBTriggersResourceItem.ts +++ b/src/tree/docdb/DocumentDBTriggersResourceItem.ts @@ -4,40 +4,35 @@ *--------------------------------------------------------------------------------------------*/ import { type CosmosClient, type Resource, type TriggerDefinition } from '@azure/cosmos'; -import { callWithTelemetryAndErrorHandling, type IActionContext } from '@microsoft/vscode-azext-utils'; -import { v4 as uuid } from 'uuid'; +import { createContextValue } from '@microsoft/vscode-azext-utils'; import vscode, { type TreeItem } from 'vscode'; import { type Experience } from '../../AzureDBExperiences'; import { getCosmosClient } from '../../docdb/getCosmosClient'; import { type CosmosDBTreeElement } from '../CosmosDBTreeElement'; +import { type TreeElementWithContextValue } from '../TreeElementWithContextValue'; +import { type TreeElementWithExperience } from '../TreeElementWithExperience'; import { type DocumentDBTriggersModel } from './models/DocumentDBTriggersModel'; -export abstract class DocumentDBTriggersResourceItem implements CosmosDBTreeElement { - public id: string; - public contextValue: string = 'cosmosDB.item.triggers'; +export abstract class DocumentDBTriggersResourceItem + implements CosmosDBTreeElement, TreeElementWithExperience, TreeElementWithContextValue +{ + public readonly id: string; + public readonly contextValue: string = 'treeItem.triggers'; protected constructor( - protected readonly model: DocumentDBTriggersModel, - protected readonly experience: Experience, + public readonly model: DocumentDBTriggersModel, + public readonly experience: Experience, ) { - this.id = uuid(); - this.contextValue = `${experience.api}.item.triggers`; + this.id = `${model.accountInfo.id}/${model.database.id}/${model.container.id}/triggers`; + this.contextValue = createContextValue([this.contextValue, `experience.${this.experience.api}`]); } public async getChildren(): Promise { - const result = await callWithTelemetryAndErrorHandling('getChildren', async (context: IActionContext) => { - context.telemetry.properties.experience = this.experience.api; - context.telemetry.properties.parentContext = this.contextValue; - context.errorHandling.rethrow = true; + const { endpoint, credentials, isEmulator } = this.model.accountInfo; + const cosmosClient = getCosmosClient(endpoint, credentials, isEmulator); + const triggers = await this.getTriggers(cosmosClient); - const { endpoint, credentials, isEmulator } = this.model.accountInfo; - const cosmosClient = getCosmosClient(endpoint, credentials, isEmulator); - const triggers = await this.getTriggers(cosmosClient); - - return await this.getChildrenImpl(triggers); - }); - - return result ?? []; + return this.getChildrenImpl(triggers); } getTreeItem(): TreeItem { diff --git a/src/tree/mongo/MongoAccountResourceItem.ts b/src/tree/mongo/MongoAccountResourceItem.ts index ada360b5b..48e4e2eb0 100644 --- a/src/tree/mongo/MongoAccountResourceItem.ts +++ b/src/tree/mongo/MongoAccountResourceItem.ts @@ -27,15 +27,16 @@ import { type MongoAccountModel } from './MongoAccountModel'; // TODO: currently MongoAccountResourceItem does not reuse MongoClusterItemBase, this will be refactored after the v1 to v2 tree migration export class MongoAccountResourceItem extends CosmosAccountResourceItemBase { - declare account: MongoAccountModel; + public declare readonly account: MongoAccountModel; + public readonly contextValue: string = 'treeItem.mongoCluster'; // TODO: this is a bug and overwrites the contextValue from the base class, fix this. constructor( account: MongoAccountModel, - readonly experience: Experience, + experience: Experience, readonly databaseAccount?: DatabaseAccountGetResults, // TODO: exploring during v1->v2 migration readonly isEmulator?: boolean, // TODO: exploring during v1->v2 migration ) { - super(account); + super(account, experience); } async discoverConnectionString(): Promise { diff --git a/src/tree/table/TableAccountAttachedResourceItem.ts b/src/tree/table/TableAccountAttachedResourceItem.ts index b42e68548..6950cb746 100644 --- a/src/tree/table/TableAccountAttachedResourceItem.ts +++ b/src/tree/table/TableAccountAttachedResourceItem.ts @@ -25,9 +25,9 @@ export class TableAccountAttachedResourceItem extends DocumentDBAccountAttachedR return Promise.resolve([ createGenericElement({ - contextValue: this.contextValue, + contextValue: `${this.contextValue}/notSupported`, label: 'Table Accounts are not supported yet.', - id: `${this.id}/no-databases`, + id: `${this.id}/notSupported`, }) as CosmosDBTreeElement, ]); }); diff --git a/src/tree/table/TableAccountResourceItem.ts b/src/tree/table/TableAccountResourceItem.ts index 7cbba8686..262d89f3e 100644 --- a/src/tree/table/TableAccountResourceItem.ts +++ b/src/tree/table/TableAccountResourceItem.ts @@ -25,9 +25,9 @@ export class TableAccountResourceItem extends DocumentDBAccountResourceItem { return Promise.resolve([ createGenericElement({ - contextValue: this.contextValue, + contextValue: `${this.contextValue}/notSupported`, label: 'Table Accounts are not supported yet.', - id: `${this.id}/no-databases`, + id: `${this.id}/notSupported`, }) as CosmosDBTreeElement, ]); });