diff --git a/extensions/cms/.vscodeignore b/extensions/cms/.vscodeignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/extensions/cms/package.json b/extensions/cms/package.json new file mode 100644 index 0000000000..850a78ae96 --- /dev/null +++ b/extensions/cms/package.json @@ -0,0 +1,640 @@ +{ + "name": "cms", + "displayName": "%cms.displayName%", + "description": "%cms.description%", + "version": "0.1.0", + "publisher": "Microsoft", + "preview": true, + "engines": { + "vscode": "^1.25.0", + "sqlops": "*" + }, + "activationEvents": [ + "*" + ], + "main": "./out/extension", + "contributes": { + "configuration": { + "type": "object", + "title": "%cms.title%", + "properties": { + "cms.cmsServers": { + "type": "object" + } + } + }, + "connectionProvider": { + "providerId": "MSSQL-CMS", + "displayName": "%cms.connectionProvider.displayName%", + "connectionOptions": [ + { + "specialValueType": "connectionName", + "isIdentity": true, + "name": "connectionName", + "displayName": "%cms.connectionOptions.connectionName.displayName%", + "description": "%cms.connectionOptions.connectionName.description%", + "groupName": "Source", + "valueType": "string", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": "serverName", + "isIdentity": true, + "name": "server", + "displayName": "%cms.connectionOptions.serverName.displayName%", + "description": "%cms.connectionOptions.serverName.description%", + "groupName": "Source", + "valueType": "string", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": true, + "isArray": false + }, + { + "specialValueType": "serverDescription", + "isIdentity": true, + "name": "serverDescription", + "displayName": "%cms.connectionOptions.serverDescription.displayName%", + "description": "%cms.connectionOptions.serverDescription.description%", + "groupName": "Source", + "valueType": "string", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": "authType", + "isIdentity": true, + "name": "authenticationType", + "displayName": "%cms.connectionOptions.authType.displayName%", + "description": "%cms.connectionOptions.authType.description%", + "groupName": "Security", + "valueType": "category", + "defaultValue": null, + "objectType": null, + "categoryValues": [ + { + "displayName": "%cms.connectionOptions.authType.categoryValues.sqlLogin%", + "name": "SqlLogin" + }, + { + "displayName": "%cms.connectionOptions.authType.categoryValues.integrated%", + "name": "Integrated" + }, + { + "displayName": "%cms.connectionOptions.authType.categoryValues.azureMFA%", + "name": "AzureMFA" + } + ], + "isRequired": true, + "isArray": false + }, + { + "specialValueType": "userName", + "isIdentity": true, + "name": "user", + "displayName": "%cms.connectionOptions.userName.displayName%", + "description": "%cms.connectionOptions.userName.description%", + "groupName": "Security", + "valueType": "string", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": true, + "isArray": false + }, + { + "specialValueType": "password", + "isIdentity": true, + "name": "password", + "displayName": "%cms.connectionOptions.password.displayName%", + "description": "%cms.connectionOptions.password.description%", + "groupName": "Security", + "valueType": "password", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": true, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "applicationIntent", + "displayName": "%cms.connectionOptions.applicationIntent.displayName%", + "description": "%cms.connectionOptions.applicationIntent.description%", + "groupName": "Initialization", + "valueType": "category", + "defaultValue": null, + "objectType": null, + "categoryValues": [ + { + "displayName": "ReadWrite", + "name": "ReadWrite" + }, + { + "displayName": "ReadOnly", + "name": "ReadOnly" + } + ], + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "asynchronousProcessing", + "displayName": "%cms.connectionOptions.asynchronousProcessing.displayName%", + "description": "%cms.connectionOptions.asynchronousProcessing.description%", + "groupName": "Initialization", + "valueType": "boolean", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "connectTimeout", + "displayName": "%cms.connectionOptions.connectTimeout.displayName%", + "description": "%cms.connectionOptions.connectTimeout.description%", + "groupName": "Initialization", + "valueType": "number", + "defaultValue": "15", + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "currentLanguage", + "displayName": "%cms.connectionOptions.currentLanguage.displayName%", + "description": "%cms.connectionOptions.currentLanguage.description%", + "groupName": "Initialization", + "valueType": "string", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "columnEncryptionSetting", + "displayName": "%cms.connectionOptions.columnEncryptionSetting.displayName%", + "description": "%cms.connectionOptions.columnEncryptionSetting.description%", + "groupName": "Security", + "valueType": "category", + "defaultValue": null, + "objectType": null, + "categoryValues": [ + { + "displayName": null, + "name": "Disabled" + }, + { + "displayName": null, + "name": "Enabled" + } + ], + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "encrypt", + "displayName": "%cms.connectionOptions.encrypt.displayName%", + "description": "%cms.connectionOptions.encrypt.description%", + "groupName": "Security", + "valueType": "boolean", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "persistSecurityInfo", + "displayName": "%cms.connectionOptions.persistSecurityInfo.displayName%", + "description": "%cms.connectionOptions.persistSecurityInfo.description%", + "groupName": "Security", + "valueType": "boolean", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "trustServerCertificate", + "displayName": "%cms.connectionOptions.trustServerCertificate.displayName%", + "description": "%cms.connectionOptions.trustServerCertificate.description%", + "groupName": "Security", + "valueType": "boolean", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "attachedDBFileName", + "displayName": "%cms.connectionOptions.attachedDBFileName.displayName%", + "description": "%cms.connectionOptions.attachedDBFileName.description%", + "groupName": "Source", + "valueType": "string", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "contextConnection", + "displayName": "%cms.connectionOptions.contextConnection.displayName%", + "description": "%cms.connectionOptions.contextConnection.description%", + "groupName": "Source", + "valueType": "boolean", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "port", + "displayName": "%cms.connectionOptions.port.displayName%", + "description": null, + "groupName": null, + "valueType": "number", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "connectRetryCount", + "displayName": "%cms.connectionOptions.connectRetryCount.displayName%", + "description": "%cms.connectionOptions.connectRetryCount.description%", + "groupName": "Connection Resiliency", + "valueType": "number", + "defaultValue": "1", + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "connectRetryInterval", + "displayName": "%cms.connectionOptions.connectRetryInterval.displayName%", + "description": "%cms.connectionOptions.connectRetryInterval.description%", + "groupName": "Connection Resiliency", + "valueType": "number", + "defaultValue": "10", + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": "appName", + "isIdentity": false, + "name": "applicationName", + "displayName": "%cms.connectionOptions.applicationName.displayName%", + "description": "%cms.connectionOptions.applicationName.description%", + "groupName": "Context", + "valueType": "string", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "workstationId", + "displayName": "%cms.connectionOptions.workstationId.displayName%", + "description": "%cms.connectionOptions.workstationId.description%", + "groupName": "Context", + "valueType": "string", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "pooling", + "displayName": "%cms.connectionOptions.pooling.displayName%", + "description": "%cms.connectionOptions.pooling.description%", + "groupName": "Pooling", + "valueType": "boolean", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "maxPoolSize", + "displayName": "%cms.connectionOptions.maxPoolSize.displayName%", + "description": "%cms.connectionOptions.maxPoolSize.description%", + "groupName": "Pooling", + "valueType": "number", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "minPoolSize", + "displayName": "%cms.connectionOptions.minPoolSize.displayName%", + "description": "%cms.connectionOptions.minPoolSize.description%", + "groupName": "Pooling", + "valueType": "number", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "loadBalanceTimeout", + "displayName": "%cms.connectionOptions.loadBalanceTimeout.displayName%", + "description": "%cms.connectionOptions.loadBalanceTimeout.description%", + "groupName": "Pooling", + "valueType": "number", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "replication", + "displayName": "%cms.connectionOptions.replication.displayName%", + "description": "%cms.connectionOptions.replication.description%", + "groupName": "Replication", + "valueType": "boolean", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "attachDbFilename", + "displayName": "%cms.connectionOptions.attachDbFilename.displayName%", + "description": null, + "groupName": null, + "valueType": "string", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "failoverPartner", + "displayName": "%cms.connectionOptions.failoverPartner.displayName%", + "description": "%cms.connectionOptions.failoverPartner.description%", + "groupName": " Source", + "valueType": "string", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "multiSubnetFailover", + "displayName": "%cms.connectionOptions.multiSubnetFailover.displayName%", + "description": null, + "groupName": null, + "valueType": "boolean", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "multipleActiveResultSets", + "displayName": "%cms.connectionOptions.multipleActiveResultSets.displayName%", + "description": "%cms.connectionOptions.multipleActiveResultSets.description%", + "groupName": "Advanced", + "valueType": "boolean", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "packetSize", + "displayName": "%cms.connectionOptions.packetSize.displayName%", + "description": "%cms.connectionOptions.packetSize.description%", + "groupName": "Advanced", + "valueType": "number", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": null, + "isIdentity": false, + "name": "typeSystemVersion", + "displayName": "%cms.connectionOptions.typeSystemVersion.displayName%", + "description": "%cms.connectionOptions.typeSystemVersion.description%", + "groupName": "Advanced", + "valueType": "string", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + } + ] + }, + "commands": [ + { + "command": "cms.resource.deleteRegisteredServer", + "title": "%cms.resource.deleteRegisteredServer.title%", + "when": "viewItem == cms.resource.itemType.registeredServer" + }, + { + "command": "cms.resource.addRegisteredServer", + "title": "%cms.resource.addRegisteredServer.title%", + "when": "viewItem == cms.resource.itemType.serverGroup" + }, + { + "command": "cms.resource.addServerGroup", + "title": "%cms.resource.addServerGroup.title%", + "when": "viewItem == cms.resource.itemType.serverGroup" + }, + { + "command": "cms.resource.deleteServerGroup", + "title": "%cms.resource.deleteServerGroup.title%", + "when": "viewItem == cms.resource.itemType.serverGroup" + }, + { + "command": "cms.resource.refreshServerGroup", + "title": "%cms.resource.refreshServerGroup.title%", + "when": "viewItem == cms.resource.itemType.serverGroup", + "icon": { + "light": "resources/light/refresh.svg", + "dark": "resources/dark/refresh_inverse.svg" + } + }, + { + "command": "cms.resource.refreshCmsServer", + "title": "%cms.resource.refreshCmsServer.title%", + "when": "viewItem == cms.resource.itemType.cmsNodeContainer", + "icon": { + "light": "resources/light/refresh.svg", + "dark": "resources/dark/refresh_inverse.svg" + } + }, + { + "command": "cms.resource.registerCMSServer", + "title": "%cms.resource.registerCMSServer.title%", + "icon": { + "light": "resources/light/add.svg", + "dark": "resources/dark/add_inverse.svg" + } + }, + { + "command": "cms.resource.deleteCMSServer", + "title": "%cms.resource.deleteCMSServer.title%" + } + ], + "dataExplorer": { + "cmsResource": [ + { + "id": "cmsResourceExplorer", + "name": "%cms.resource.explorer.title%" + } + ] + }, + "menus": { + "view/title": [ + { + "command": "cms.resource.registerCMSServer", + "when": "view == cmsResourceExplorer", + "group": "navigation" + } + ], + "view/item/context": [ + { + "command": "cms.resource.deleteRegisteredServer", + "when": "viewItem == cms.resource.itemType.registeredServer", + "group": "navigation@2" + }, + { + "command": "cms.resource.deleteServerGroup", + "when": "viewItem == cms.resource.itemType.serverGroup", + "group": "navigation@10" + }, + { + "command": "cms.resource.addRegisteredServer", + "when": "viewItem == cms.resource.itemType.serverGroup", + "group": "navigation@10" + }, + { + "command": "cms.resource.addRegisteredServer", + "when": "viewItem == cms.resource.itemType.cmsNodeContainer", + "group": "navigation@10" + }, + { + "command": "cms.resource.addServerGroup", + "when": "viewItem == cms.resource.itemType.serverGroup", + "group": "navigation@10" + }, + { + "command": "cms.resource.addServerGroup", + "when": "viewItem == cms.resource.itemType.cmsNodeContainer", + "group": "navigation@10" + }, + { + "command": "cms.resource.refreshServerGroup", + "when": "viewItem == cms.resource.itemType.serverGroup", + "group": "navigation@10" + }, + { + "command": "cms.resource.refreshCmsServer", + "when": "viewItem == cms.resource.itemType.cmsNodeContainer", + "group": "navigation@10" + }, + { + "command": "cms.resource.deleteCMSServer", + "when": "viewItem == cms.resource.itemType.cmsNodeContainer", + "group": "navigation@10" + } + ] + } + }, + "dependencies": { + "request": "2.88.0", + "vscode-nls": "^4.0.0" + }, + "devDependencies": { + "@types/mocha": "^5.2.5", + "@types/node": "^8.0.24", + "mocha": "^5.2.0", + "should": "^13.2.1", + "vscode": "^1.1.26", + "typemoq": "^2.1.0" + } +} \ No newline at end of file diff --git a/extensions/cms/package.nls.json b/extensions/cms/package.nls.json new file mode 100644 index 0000000000..5710045a7c --- /dev/null +++ b/extensions/cms/package.nls.json @@ -0,0 +1,111 @@ +{ + "cms.displayName": "CMS", + "cms.description": "Central Management Servers", + "cms.title": "Central Management Servers", + "cms.connectionProvider.displayName": "Microsoft SQL Server - CMS", + "cms.resource.explorer.title": "Central Management Servers", + "cms.resource.refreshServerGroup.title": "Refresh Server Group", + "cms.resource.refreshCmsServer.title": "Refresh Central Management Server", + "cms.resource.deleteRegisteredServer.title": "Delete Registered Server", + "cms.resource.addRegisteredServer.title": "Add Registered Server", + "cms.resource.deleteServerGroup.title": "Delete Server Group", + "cms.resource.addServerGroup.title": "Add Server Group", + "cms.resource.registerCMSServer.title": "Add Central Management Server", + "cms.resource.deleteCMSServer.title": "Delete Central Management Server", + + "cms.configuration.title": "MSSQL configuration", + "cms.query.displayBitAsNumber": "Should BIT columns be displayed as numbers (1 or 0)? If false, BIT columns will be displayed as 'true' or 'false'", + "cms.format.alignColumnDefinitionsInColumns": "Should column definitions be aligned?", + "cms.format.datatypeCasing": "Should data types be formatted as UPPERCASE, lowercase, or none (not formatted)", + "cms.format.keywordCasing": "Should keywords be formatted as UPPERCASE, lowercase, or none (not formatted)", + "cms.format.placeCommasBeforeNextStatement": "should commas be placed at the beginning of each statement in a list e.g. ', mycolumn2' instead of at the end e.g. 'mycolumn1,'", + "cms.format.placeSelectStatementReferencesOnNewLine": "Should references to objects in a select statements be split into separate lines? E.g. for 'SELECT C1, C2 FROM T1' both C1 and C2 will be on separate lines", + "cms.logDebugInfo": "[Optional] Log debug output to the console (View -> Output) and then select appropriate output channel from the dropdown", + "cms.tracingLevel": "[Optional] Log level for backend services. Azure Data Studio generates a file name every time it starts and if the file already exists the logs entries are appended to that file. For cleanup of old log files see logRetentionMinutes and logFilesRemovalLimit settings. The default tracingLevel does not log much. Changing verbosity could lead to extensive logging and disk space requirements for the logs. Error includes Critical, Warning includes Error, Information includes Warning and Verbose includes Information", + "cms.logRetentionMinutes": "Number of minutes to retain log files for backend services. Default is 1 week.", + "cms.logFilesRemovalLimit": "Maximum number of old files to remove upon startup that have expired mssql.logRetentionMinutes. Files that do not get cleaned up due to this limitation get cleaned up next time Azure Data Studio starts up.", + "ignorePlatformWarning": "[Optional] Do not show unsupported platform warnings", + + "onprem.databaseProperties.recoveryModel": "Recovery Model", + "onprem.databaseProperties.lastBackupDate": "Last Database Backup", + "onprem.databaseProperties.lastLogBackupDate": "Last Log Backup", + "onprem.databaseProperties.compatibilityLevel": "Compatibility Level", + "onprem.databaseProperties.owner": "Owner", + "onprem.serverProperties.serverVersion": "Version", + "onprem.serverProperties.serverEdition": "Edition", + "onprem.serverProperties.machineName": "Computer Name", + "onprem.serverProperties.osVersion": "OS Version", + + "cloud.databaseProperties.azureEdition": "Edition", + "cloud.databaseProperties.serviceLevelObjective": "Pricing Tier", + "cloud.databaseProperties.compatibilityLevel": "Compatibility Level", + "cloud.databaseProperties.owner": "Owner", + "cloud.serverProperties.serverVersion": "Version", + "cloud.serverProperties.serverEdition": "Type", + + "cms.provider.displayName": "Microsoft SQL Server", + "cms.connectionOptions.connectionName.displayName": "Name (optional)", + "cms.connectionOptions.connectionName.description": "Custom name of the connection", + "cms.connectionOptions.serverName.displayName": "Server", + "cms.connectionOptions.serverName.description": "Name of the SQL Server instance", + "cms.connectionOptions.serverDescription.displayName": "Server Description", + "cms.connectionOptions.serverDescription.description": "Description of the SQL Server instance", + "cms.connectionOptions.authType.displayName": "Authentication type", + "cms.connectionOptions.authType.description": "Specifies the method of authenticating with SQL Server", + "cms.connectionOptions.authType.categoryValues.sqlLogin": "SQL Login", + "cms.connectionOptions.authType.categoryValues.integrated": "Windows Authentication", + "cms.connectionOptions.authType.categoryValues.azureMFA": "Azure Active Directory - Universal with MFA support", + "cms.connectionOptions.userName.displayName": "User name", + "cms.connectionOptions.userName.description": "Indicates the user ID to be used when connecting to the data source", + "cms.connectionOptions.password.displayName": "Password", + "cms.connectionOptions.password.description": "Indicates the password to be used when connecting to the data source", + "cms.connectionOptions.applicationIntent.displayName": "Application intent", + "cms.connectionOptions.applicationIntent.description": "Declares the application workload type when connecting to a server", + "cms.connectionOptions.asynchronousProcessing.displayName": "Asynchronous processing", + "cms.connectionOptions.asynchronousProcessing.description": "When true, enables usage of the Asynchronous functionality in the .Net Framework Data Provider", + "cms.connectionOptions.connectTimeout.displayName": "Connect timeout", + "cms.connectionOptions.connectTimeout.description": "The length of time (in seconds) to wait for a connection to the server before terminating the attempt and generating an error", + "cms.connectionOptions.currentLanguage.displayName": "Current language", + "cms.connectionOptions.currentLanguage.description": "The SQL Server language record name", + "cms.connectionOptions.columnEncryptionSetting.displayName": "Column encryption", + "cms.connectionOptions.columnEncryptionSetting.description": "Default column encryption setting for all the commands on the connection", + "cms.connectionOptions.encrypt.displayName": "Encrypt", + "cms.connectionOptions.encrypt.description": "When true, SQL Server uses SSL encryption for all data sent between the client and server if the servers has a certificate installed", + "cms.connectionOptions.persistSecurityInfo.displayName": "Persist security info", + "cms.connectionOptions.persistSecurityInfo.description": "When false, security-sensitive information, such as the password, is not returned as part of the connection", + "cms.connectionOptions.trustServerCertificate.displayName": "Trust server certificate", + "cms.connectionOptions.trustServerCertificate.description": "When true (and encrypt=true), SQL Server uses SSL encryption for all data sent between the client and server without validating the server certificate", + "cms.connectionOptions.attachedDBFileName.displayName": "Attached DB file name", + "cms.connectionOptions.attachedDBFileName.description": "The name of the primary file, including the full path name, of an attachable database", + "cms.connectionOptions.contextConnection.displayName": "Context connection", + "cms.connectionOptions.contextConnection.description": "When true, indicates the connection should be from the SQL server context. Available only when running in the SQL Server process", + "cms.connectionOptions.port.displayName": "Port", + "cms.connectionOptions.connectRetryCount.displayName": "Connect retry count", + "cms.connectionOptions.connectRetryCount.description": "Number of attempts to restore connection", + "cms.connectionOptions.connectRetryInterval.displayName": "Connect retry interval", + "cms.connectionOptions.connectRetryInterval.description": "Delay between attempts to restore connection", + "cms.connectionOptions.applicationName.displayName": "Application name", + "cms.connectionOptions.applicationName.description": "The name of the application", + "cms.connectionOptions.workstationId.displayName": "Workstation Id", + "cms.connectionOptions.workstationId.description": "The name of the workstation connecting to SQL Server", + "cms.connectionOptions.pooling.displayName": "Pooling", + "cms.connectionOptions.pooling.description": "When true, the connection object is drawn from the appropriate pool, or if necessary, is created and added to the appropriate pool", + "cms.connectionOptions.maxPoolSize.displayName": "Max pool size", + "cms.connectionOptions.maxPoolSize.description": "The maximum number of connections allowed in the pool", + "cms.connectionOptions.minPoolSize.displayName": "Min pool size", + "cms.connectionOptions.minPoolSize.description": "The minimum number of connections allowed in the pool", + "cms.connectionOptions.loadBalanceTimeout.displayName": "Load balance timeout", + "cms.connectionOptions.loadBalanceTimeout.description": "The minimum amount of time (in seconds) for this connection to live in the pool before being destroyed", + "cms.connectionOptions.replication.displayName": "Replication", + "cms.connectionOptions.replication.description": "Used by SQL Server in Replication", + "cms.connectionOptions.attachDbFilename.displayName": "Attach DB filename", + "cms.connectionOptions.failoverPartner.displayName": "Failover partner", + "cms.connectionOptions.failoverPartner.description": "The name or network address of the instance of SQL Server that acts as a failover partner", + "cms.connectionOptions.multiSubnetFailover.displayName": "Multi subnet failover", + "cms.connectionOptions.multipleActiveResultSets.displayName": "Multiple active result sets", + "cms.connectionOptions.multipleActiveResultSets.description": "When true, multiple result sets can be returned and read from one connection", + "cms.connectionOptions.packetSize.displayName": "Packet size", + "cms.connectionOptions.packetSize.description": "Size in bytes of the network packets used to communicate with an instance of SQL Server", + "cms.connectionOptions.typeSystemVersion.displayName": "Type system version", + "cms.connectionOptions.typeSystemVersion.description": "Indicates which server type system then provider will expose through the DataReader" +} \ No newline at end of file diff --git a/extensions/cms/resources/dark/add_inverse.svg b/extensions/cms/resources/dark/add_inverse.svg new file mode 100644 index 0000000000..7d3fd77ffd --- /dev/null +++ b/extensions/cms/resources/dark/add_inverse.svg @@ -0,0 +1 @@ +add \ No newline at end of file diff --git a/extensions/cms/resources/dark/refresh_inverse.svg b/extensions/cms/resources/dark/refresh_inverse.svg new file mode 100644 index 0000000000..d79fdaa4e8 --- /dev/null +++ b/extensions/cms/resources/dark/refresh_inverse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extensions/cms/resources/light/add.svg b/extensions/cms/resources/light/add.svg new file mode 100644 index 0000000000..2b679663e8 --- /dev/null +++ b/extensions/cms/resources/light/add.svg @@ -0,0 +1 @@ +add \ No newline at end of file diff --git a/extensions/cms/resources/light/centralmanagement_server.svg b/extensions/cms/resources/light/centralmanagement_server.svg new file mode 100644 index 0000000000..428b8af38c --- /dev/null +++ b/extensions/cms/resources/light/centralmanagement_server.svg @@ -0,0 +1 @@ +centralmanagement_server_16x \ No newline at end of file diff --git a/extensions/cms/resources/light/folder.svg b/extensions/cms/resources/light/folder.svg new file mode 100644 index 0000000000..8442363a76 --- /dev/null +++ b/extensions/cms/resources/light/folder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extensions/cms/resources/light/refresh.svg b/extensions/cms/resources/light/refresh.svg new file mode 100644 index 0000000000..e034574819 --- /dev/null +++ b/extensions/cms/resources/light/refresh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extensions/cms/resources/light/regserverserver.svg b/extensions/cms/resources/light/regserverserver.svg new file mode 100644 index 0000000000..5045e2cf67 --- /dev/null +++ b/extensions/cms/resources/light/regserverserver.svg @@ -0,0 +1 @@ +regserverserver_16x \ No newline at end of file diff --git a/extensions/cms/src/apiWrapper.ts b/extensions/cms/src/apiWrapper.ts new file mode 100644 index 0000000000..976918fbf3 --- /dev/null +++ b/extensions/cms/src/apiWrapper.ts @@ -0,0 +1,330 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; +import * as nls from 'vscode-nls'; +import * as vscode from 'vscode'; +import * as azdata from 'azdata'; +import * as mssql from '../../mssql/src/api/mssqlapis'; +import * as Utils from './cmsResource/utils'; +import { ICmsResourceNodeInfo } from './cmsResource/tree/baseTreeNodes'; + +const localize = nls.loadMessageBundle(); + +/** + * Wrapper class to act as a facade over VSCode and Data APIs and allow us to test / mock callbacks into + * this API from our code + * + * @export + * ApiWrapper + */ +export class ApiWrapper { + + private _cmsService: mssql.CmsService; + private _registeredCmsServers: ICmsResourceNodeInfo[]; + + // Data APIs + public registerConnectionProvider(provider: azdata.ConnectionProvider): vscode.Disposable { + return azdata.dataprotocol.registerConnectionProvider(provider); + } + + public registerObjectExplorerProvider(provider: azdata.ObjectExplorerProvider): vscode.Disposable { + return azdata.dataprotocol.registerObjectExplorerProvider(provider); + } + + public registerTaskServicesProvider(provider: azdata.TaskServicesProvider): vscode.Disposable { + return azdata.dataprotocol.registerTaskServicesProvider(provider); + } + + public registerCapabilitiesServiceProvider(provider: azdata.CapabilitiesProvider): vscode.Disposable { + return azdata.dataprotocol.registerCapabilitiesServiceProvider(provider); + } + + public registerTaskHandler(taskId: string, handler: (profile: azdata.IConnectionProfile) => void): void { + azdata.tasks.registerTask(taskId, handler); + } + + public startBackgroundOperation(operationInfo: azdata.BackgroundOperationInfo): void { + azdata.tasks.startBackgroundOperation(operationInfo); + } + + public getActiveConnections(): Thenable { + return azdata.connection.getActiveConnections(); + } + + public getCurrentConnection(): Thenable { + return azdata.connection.getCurrentConnection(); + } + + // VSCode APIs + public createTerminal(name?: string, shellPath?: string, shellArgs?: string[]): vscode.Terminal { + return vscode.window.createTerminal(name, shellPath, shellArgs); + } + + public createTerminalWithOptions(options: vscode.TerminalOptions): vscode.Terminal { + return vscode.window.createTerminal(options); + } + + public executeCommand(command: string, ...rest: any[]): Thenable { + return vscode.commands.executeCommand(command, ...rest); + } + + public getFilePathRelativeToWorkspace(uri: vscode.Uri): string { + return vscode.workspace.asRelativePath(uri); + } + + public getWorkspaceFolders(): vscode.WorkspaceFolder[] { + return vscode.workspace.workspaceFolders; + } + + public getWorkspacePathFromUri(uri: vscode.Uri): string | undefined { + let workspaceFolder = vscode.workspace.getWorkspaceFolder(uri); + return workspaceFolder ? workspaceFolder.uri.fsPath : undefined; + } + + public registerCommand(command: string, callback: (...args: any[]) => any, thisArg?: any): vscode.Disposable { + return vscode.commands.registerCommand(command, callback, thisArg); + } + + public registerDocumentOpenHandler(handler: (doc: vscode.TextDocument) => any): vscode.Disposable { + return vscode.workspace.onDidOpenTextDocument(handler); + } + + public registerTreeDataProvider(viewId: string, treeDataProvider: vscode.TreeDataProvider): vscode.Disposable { + return vscode.window.registerTreeDataProvider(viewId, treeDataProvider); + } + + /** + * Get the configuration for a extensionName + * @param extensionName The string name of the extension to get the configuration for + * @param resource The optional URI, as a URI object or a string, to use to get resource-scoped configurations + */ + public getConfiguration(): vscode.WorkspaceConfiguration { + return vscode.workspace.getConfiguration('cms'); + } + + public async setConfiguration(value: any): Promise { + await vscode.workspace.getConfiguration('cms').update('cmsServers', value, true); + } + + /** + * Parse uri + */ + public parseUri(uri: string): vscode.Uri { + return vscode.Uri.parse(uri); + } + + public showOpenDialog(options: vscode.OpenDialogOptions): Thenable { + return vscode.window.showOpenDialog(options); + } + + public showSaveDialog(options: vscode.SaveDialogOptions): Thenable { + return vscode.window.showSaveDialog(options); + } + + public createDialog(title: string): azdata.window.Dialog { + return azdata.window.createModelViewDialog(title); + } + + public openDialog(dialog: azdata.window.Dialog): void { + return azdata.window.openDialog(dialog); + } + + public closeDialog(dialog: azdata.window.Dialog): void { + return azdata.window.closeDialog(dialog); + } + + public openTextDocument(uri: vscode.Uri): Thenable; + public openTextDocument(options: { language?: string; content?: string; }): Thenable; + public openTextDocument(uriOrOptions): Thenable { + return vscode.workspace.openTextDocument(uriOrOptions); + } + + public showTextDocument(document: vscode.TextDocument, column?: vscode.ViewColumn, preserveFocus?: boolean, preview?: boolean): Thenable { + let options: vscode.TextDocumentShowOptions = { + viewColumn: column, + preserveFocus: preserveFocus, + preview: preview + }; + return vscode.window.showTextDocument(document, options); + } + + public showErrorMessage(message: string, ...items: string[]): Thenable { + return vscode.window.showErrorMessage(message, ...items); + } + + public showWarningMessage(message: string, ...items: string[]): Thenable { + return vscode.window.showWarningMessage(message, ...items); + } + + public showInformationMessage(message: string, ...items: string[]): Thenable { + return vscode.window.showInformationMessage(message, ...items); + } + + public createStatusBarItem(alignment?: vscode.StatusBarAlignment, priority?: number): vscode.StatusBarItem { + return vscode.window.createStatusBarItem(alignment, priority); + } + + public get workspaceFolders(): vscode.WorkspaceFolder[] { + return vscode.workspace.workspaceFolders; + } + + public createOutputChannel(name: string): vscode.OutputChannel { + return vscode.window.createOutputChannel(name); + } + + public registerCompletionItemProvider(selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, ...triggerCharacters: string[]): vscode.Disposable { + return vscode.languages.registerCompletionItemProvider(selector, provider, ...triggerCharacters); + } + + // Connection APIs + public openConnectionDialog(providers: string[], initialConnectionProfile?: azdata.IConnectionProfile, connectionCompletionOptions?: azdata.IConnectionCompletionOptions): Thenable { + return azdata.connection.openConnectionDialog(providers, initialConnectionProfile, connectionCompletionOptions); + } + + // CMS APIs + public async getCmsService(): Promise { + if (!this._cmsService) { + let extensionApi: mssql.MssqlExtensionApi = vscode.extensions.getExtension('Microsoft.mssql').exports; + this._cmsService = await extensionApi.getCmsServiceProvider(); + } + return this._cmsService; + } + + public async getRegisteredServers(ownerUri: string, relativePath: string): Promise { + return this.getCmsService().then((service) => { + return service.getRegisteredServers(ownerUri, relativePath).then((result) => { + if (result && result.registeredServersList && result.registeredServersList) { + return result; + } + }); + }); + } + + public async createCmsServer(connection: azdata.connection.Connection, + name: string, description: string): Promise { + let provider = await this.getCmsService(); + connection.providerName = connection.providerName === 'MSSQL-CMS' ? 'MSSQL' : connection.providerName; + let ownerUri = await azdata.connection.getUriForConnection(connection.connectionId); + if (!ownerUri) { + // Make a connection if it's not already connected + await azdata.connection.connect(Utils.toConnectionProfile(connection), false, false).then(async (result) => { + ownerUri = await azdata.connection.getUriForConnection(result.connectionId); + }); + } + return provider.createCmsServer(name, description, connection, ownerUri).then((result) => { + if (result) { + return result; + } + }); + } + + public async deleteCmsServer(cmsServer: any) { + let config = this.getConfiguration(); + if (config && config.cmsServers) { + let newServers = config.cmsServers.filter((cachedServer) => { + return cachedServer.name !== cmsServer; + }); + await this.setConfiguration(newServers); + this._registeredCmsServers = this._registeredCmsServers.filter((cachedServer) => { + return cachedServer.name !== cmsServer; + }); + } + } + + public cacheRegisteredCmsServer(name: string, description: string, ownerUri: string, connection: azdata.connection.Connection): void { + if (!this._registeredCmsServers) { + this._registeredCmsServers = []; + } + let cmsServerNode: ICmsResourceNodeInfo = { + name: name, + description: description, + ownerUri: ownerUri, + connection: connection + }; + this._registeredCmsServers.push(cmsServerNode); + } + + public async addRegisteredServer(relativePath: string, ownerUri: string, + parentServerName?: string): Promise { + let provider = await this.getCmsService(); + // Initial profile to disallow SQL Login without + // changing provider. + let initialProfile: azdata.IConnectionProfile = { + connectionName: undefined, + serverName: undefined, + databaseName: undefined, + userName: undefined, + password: undefined, + authenticationType: undefined, + savePassword: undefined, + groupFullName: undefined, + groupId: undefined, + providerName: undefined, + saveProfile: undefined, + id: undefined, + options: { + authTypeChanged: true + } + }; + return this.openConnectionDialog(['MSSQL-CMS'], initialProfile, undefined).then((connection) => { + if (connection && connection.options) { + if (connection.options.server === parentServerName) { + // error out for same server registration + let errorText = localize('cms.errors.sameServerUnderCMS', 'You cannot add a shared registered server with the same name as the Configuration Server'); + this.showErrorMessage(errorText); + return; + } else { + return provider.addRegisteredServer(ownerUri, relativePath, + connection.options.registeredServerName, connection.options.registeredServerDescription, connection).then((result) => { + if (result) { + return connection.options.server; + } + }); + } + + } + }); + } + + public async removeRegisteredServer(registeredServerName: string, relativePath: string, ownerUri: string): Promise { + let provider = await this.getCmsService(); + return provider.removeRegisteredServer(ownerUri, relativePath, registeredServerName).then((result) => { + return result; + }); + } + + public async addServerGroup(groupName: string, groupDescription: string, relativePath: string, ownerUri: string): Promise { + let provider = await this.getCmsService(); + return provider.addServerGroup(ownerUri, relativePath, groupName, groupDescription).then((result) => { + return result; + }); + } + + public async removeServerGroup(groupName: string, relativePath: string, ownerUri: string): Promise { + let provider = await this.getCmsService(); + return provider.removeServerGroup(ownerUri, relativePath, groupName).then((result) => { + return result; + }); + } + + // Getters + + public get registeredCmsServers(): ICmsResourceNodeInfo[] { + return this._registeredCmsServers; + } + + public get connection(): Thenable { + return this.openConnectionDialog(['MSSQL-CMS'], undefined, undefined).then((connection) => { + if (connection) { + // remove group ID from connection if a user chose connection + // from the recent connections list + connection.options['groupId'] = null; + connection.providerName = 'MSSQL'; + return connection; + } + }); + } +} \ No newline at end of file diff --git a/extensions/cms/src/appContext.ts b/extensions/cms/src/appContext.ts new file mode 100644 index 0000000000..d5b7c7c2fa --- /dev/null +++ b/extensions/cms/src/appContext.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as vscode from 'vscode'; +import { ApiWrapper } from './apiWrapper'; + +/** + * Global context for the application + */ +export class AppContext { + + constructor(public readonly extensionContext: vscode.ExtensionContext, public readonly apiWrapper: ApiWrapper) { + this.apiWrapper = apiWrapper || new ApiWrapper(); + } +} diff --git a/extensions/cms/src/cmsResource/cms-resource.d.ts b/extensions/cms/src/cmsResource/cms-resource.d.ts new file mode 100644 index 0000000000..5a081f9d13 --- /dev/null +++ b/extensions/cms/src/cmsResource/cms-resource.d.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TreeDataProvider, TreeItem } from 'vscode'; +import { DataProvider, Account } from 'azdata'; + +export namespace cmsResource { + export interface ICmsResourceProvider extends DataProvider { + getTreeDataProvider(): ICmsResourceTreeDataProvider; + } + + export interface ICmsResourceTreeDataProvider extends TreeDataProvider { + } + + export interface ICmsResourceNode { + readonly treeItem: TreeItem; + } +} \ No newline at end of file diff --git a/extensions/cms/src/cmsResource/cmsResourceCommands.ts b/extensions/cms/src/cmsResource/cmsResourceCommands.ts new file mode 100644 index 0000000000..33d7cf3946 --- /dev/null +++ b/extensions/cms/src/cmsResource/cmsResourceCommands.ts @@ -0,0 +1,185 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; +import * as nls from 'vscode-nls'; +import * as azdata from 'azdata'; +import { AppContext } from '../appContext'; +import { TreeNode } from './treeNode'; +import { CmsResourceTreeProvider } from './tree/treeProvider'; +import { CmsResourceEmptyTreeNode } from './tree/cmsResourceEmptyTreeNode'; +import { RegisteredServerTreeNode } from './tree/registeredServerTreeNode'; +import { ServerGroupTreeNode } from './tree/serverGroupTreeNode'; +import { CmsResourceTreeNode } from './tree/cmsResourceTreeNode'; + +const localize = nls.loadMessageBundle(); + +export function registerCmsServerCommand(appContext: AppContext, tree: CmsResourceTreeProvider): void { + // Create a CMS Server + appContext.apiWrapper.registerCommand('cms.resource.registerCmsServer', async (node?: TreeNode) => { + if (node && !(node instanceof CmsResourceEmptyTreeNode)) { + return; + } + await appContext.apiWrapper.connection.then(async (connection) => { + if (connection && connection.options) { + let registeredCmsServerName = connection.options.registeredServerName ? + connection.options.registeredServerName : connection.options.server; + // check if a CMS with the same name is registered or not + let cachedServers = appContext.apiWrapper.registeredCmsServers; + let serverExists: boolean = false; + if (cachedServers) { + serverExists = cachedServers.some((server) => { + return server.name === registeredCmsServerName; + }); + } + if (!serverExists) { + // remove any group ID if user selects a connection from + // recent connection list + connection.options.groupId = null; + let registeredCmsServerDescription = connection.options.registeredServerDescription; + // remove server description from connection uri + connection.options.registeredCmsServerDescription = null; + let ownerUri = await azdata.connection.getUriForConnection(connection.connectionId); + appContext.apiWrapper.cacheRegisteredCmsServer(registeredCmsServerName, registeredCmsServerDescription, ownerUri, connection); + tree.notifyNodeChanged(undefined); + } else { + // error out for same server name + let errorText = localize('cms.errors.sameCmsServerName', 'Central Management Server Group already has a Registered Server with the name {0}', registeredCmsServerName); + appContext.apiWrapper.showErrorMessage(errorText); + return; + } + } + }); + }); +} + +export function deleteCmsServerCommand(appContext: AppContext, tree: CmsResourceTreeProvider): void { + // Delete a CMS Server + appContext.apiWrapper.registerCommand('cms.resource.deleteCmsServer', async (node?: TreeNode) => { + if (!(node instanceof CmsResourceTreeNode)) { + return; + } + await appContext.apiWrapper.deleteCmsServer(node.name); + tree.isSystemInitialized = false; + tree.notifyNodeChanged(undefined); + }); +} + +export function addRegisteredServerCommand(appContext: AppContext, tree: CmsResourceTreeProvider): void { + // Add a registered server + appContext.apiWrapper.registerCommand('cms.resource.addRegisteredServer', async (node?: TreeNode) => { + if (!(node instanceof CmsResourceTreeNode || node instanceof ServerGroupTreeNode)) { + return; + } + let relativePath = node instanceof CmsResourceTreeNode ? '' : node.relativePath; + let serverName = node instanceof CmsResourceTreeNode ? node.connection.options.server : null; + await appContext.apiWrapper.addRegisteredServer(relativePath, node.ownerUri, serverName).then((result) => { + if (result) { + tree.notifyNodeChanged(undefined); + } + }); + }); +} + +export function deleteRegisteredServerCommand(appContext: AppContext, tree: CmsResourceTreeProvider): void { + // Delete a registered server + appContext.apiWrapper.registerCommand('cms.resource.deleteRegisteredServer', async (node?: TreeNode) => { + if (!(node instanceof RegisteredServerTreeNode)) { + return; + } + appContext.apiWrapper.removeRegisteredServer(node.name, node.relativePath, node.ownerUri).then((result) => { + if (result) { + tree.notifyNodeChanged(undefined); + } + }); + }); +} + +export function addServerGroupCommand(appContext: AppContext, tree: CmsResourceTreeProvider): void { + // Add a registered server group + appContext.apiWrapper.registerCommand('cms.resource.addServerGroup', async (node?: TreeNode) => { + if (!(node instanceof ServerGroupTreeNode || node instanceof CmsResourceTreeNode)) { + return; + } + // add a dialog for adding a group + let title = localize('cms.AddServerGroup', 'Add Server Group'); + let dialog = azdata.window.createModelViewDialog(title, 'cms.addServerGroup'); + dialog.okButton.label = localize('cms.OK', 'OK'); + dialog.cancelButton.label = localize('cms.Cancel', 'Cancel'); + let mainTab = azdata.window.createTab(title); + let serverGroupName: string = null; + let serverDescription: string = null; + mainTab.registerContent(async view => { + let nameTextBox = view.modelBuilder.inputBox().component(); + nameTextBox.required = true; + nameTextBox.onTextChanged((e) => { + serverGroupName = e; + }); + if (nameTextBox.value && nameTextBox.value.length > 0) { + dialog.message = null; + } + let descriptionTextBox = view.modelBuilder.inputBox().component(); + descriptionTextBox.required = false; + descriptionTextBox.onTextChanged((e) => { + serverDescription = e; + }); + let formModel = view.modelBuilder.formContainer() + .withFormItems([{ + component: nameTextBox, + title: localize('cms.ServerGroupName', 'Server Group Name') + }, { + component: descriptionTextBox, + title: localize('cms.ServerGroupDescription', 'Server Group Description') + }]).withLayout({ width: '100%' }).component(); + await view.initializeModel(formModel); + }); + dialog.content = [mainTab]; + azdata.window.openDialog(dialog); + let groupExists = false; + dialog.okButton.onClick(() => { + let path = node instanceof ServerGroupTreeNode ? node.relativePath : ''; + if (node.serverGroupNodes.some(node => node.name === serverGroupName)) { + groupExists = true; + } + if (!groupExists) { + appContext.apiWrapper.addServerGroup(serverGroupName, serverDescription, path, node.ownerUri).then((result) => { + if (result) { + tree.notifyNodeChanged(undefined); + } + }); + } else { + // error out for same server group + let errorText = localize('cms.errors.sameServerGroupName', '{0} already has a Server Group with the name {1}', node.name, serverGroupName); + appContext.apiWrapper.showErrorMessage(errorText); + return; + } + }); + }); +} + +export function deleteServerGroupCommand(appContext: AppContext, tree: CmsResourceTreeProvider): void { + // Remove a registered server group + appContext.apiWrapper.registerCommand('cms.resource.deleteServerGroup', async (node?: TreeNode) => { + if (!(node instanceof ServerGroupTreeNode)) { + return; + } + appContext.apiWrapper.removeServerGroup(node.name, node.relativePath, node.ownerUri).then((result) => { + if (result) { + tree.notifyNodeChanged(undefined); + } + }); + }); +} + +export function refreshCommand(appContext: AppContext, tree: CmsResourceTreeProvider): void { + // Refresh the cms resource + appContext.apiWrapper.registerCommand('cms.resource.refresh', async (node?: TreeNode) => { + if (!node) { + return; + } + tree.notifyNodeChanged(undefined); + }); +} + diff --git a/extensions/cms/src/cmsResource/commands.ts b/extensions/cms/src/cmsResource/commands.ts new file mode 100644 index 0000000000..e9b14fa7bc --- /dev/null +++ b/extensions/cms/src/cmsResource/commands.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; +import { AppContext } from '../appContext'; +import { CmsResourceTreeProvider } from './tree/treeProvider'; +import { + registerCmsServerCommand, deleteCmsServerCommand, + addRegisteredServerCommand, deleteRegisteredServerCommand, + deleteServerGroupCommand, addServerGroupCommand, refreshCommand +} from './cmsResourceCommands'; + +export function registerCmsResourceCommands(appContext: AppContext, tree: CmsResourceTreeProvider): void { + + registerCmsServerCommand(appContext, tree); + deleteCmsServerCommand(appContext, tree); + addRegisteredServerCommand(appContext, tree); + deleteRegisteredServerCommand(appContext, tree); + addServerGroupCommand(appContext, tree); + deleteServerGroupCommand(appContext, tree); + refreshCommand(appContext, tree); + +} diff --git a/extensions/cms/src/cmsResource/constants.ts b/extensions/cms/src/cmsResource/constants.ts new file mode 100644 index 0000000000..1547aa9f7c --- /dev/null +++ b/extensions/cms/src/cmsResource/constants.ts @@ -0,0 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +export enum CmsResourceItemType { + cmsMessageNodeContainer = 'cms.resource.itemType.cmsMessageNodeContainer', + cmsEmptyNodeContainer = 'cms.resource.itemType.cmsEmptyNodeContainer', + cmsNodeContainer = 'cms.resource.itemType.cmsNodeContainer', + registeredServer = 'cms.resource.itemType.registeredServer', + serverGroup = 'cms.resource.itemType.serverGroup' +} \ No newline at end of file diff --git a/extensions/cms/src/cmsResource/messageTreeNode.ts b/extensions/cms/src/cmsResource/messageTreeNode.ts new file mode 100644 index 0000000000..bc9c241ba8 --- /dev/null +++ b/extensions/cms/src/cmsResource/messageTreeNode.ts @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { TreeItem, TreeItemCollapsibleState } from 'vscode'; +import { NodeInfo } from 'azdata'; + +import { TreeNode } from './treeNode'; +import { CmsResourceItemType } from './constants'; + +export class CmsResourceMessageTreeNode extends TreeNode { + public constructor( + public readonly message: string, + parent: TreeNode + ) { + super(); + + this.parent = parent; + this._id = `message_${CmsResourceMessageTreeNode._messageNum++}`; + } + + public static create(message: string, parent: TreeNode): CmsResourceMessageTreeNode { + return new CmsResourceMessageTreeNode(message, parent); + } + + public getChildren(): TreeNode[] | Promise { + return []; + } + + public getTreeItem(): TreeItem | Promise { + let item = new TreeItem(this.message, TreeItemCollapsibleState.None); + item.contextValue = CmsResourceItemType.cmsMessageNodeContainer; + return item; + } + + public getNodeInfo(): NodeInfo { + return { + label: this.message, + isLeaf: true, + errorMessage: undefined, + metadata: undefined, + nodePath: this.generateNodePath(), + nodeStatus: undefined, + nodeType: CmsResourceItemType.cmsMessageNodeContainer, + nodeSubType: undefined, + iconType: CmsResourceItemType.cmsMessageNodeContainer + }; + } + + public get nodePathValue(): string { + return this._id; + } + + private _id: string; + + private static _messageNum: number = 0; +} diff --git a/extensions/cms/src/cmsResource/tree/baseTreeNodes.ts b/extensions/cms/src/cmsResource/tree/baseTreeNodes.ts new file mode 100644 index 0000000000..9394c32222 --- /dev/null +++ b/extensions/cms/src/cmsResource/tree/baseTreeNodes.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; +import * as azdata from 'azdata'; +import { AppContext } from '../../appContext'; +import { TreeNode } from '../treeNode'; +import { ICmsResourceTreeChangeHandler } from './treeChangeHandler'; + +export abstract class CmsResourceTreeNodeBase extends TreeNode { + + public constructor( + private _name: string, + private _description: string, + private _ownerUri: string, + public readonly appContext: AppContext, + public readonly treeChangeHandler: ICmsResourceTreeChangeHandler, + parent: TreeNode + ) { + super(); + this.parent = parent; + } + + public get name(): string { + return this._name; + } + + public get description(): string { + return this._description; + } + + public get ownerUri(): string { + return this._ownerUri; + } +} + +export interface ICmsResourceNodeInfo { + name: string; + description: string; + ownerUri: string; + connection: azdata.connection.Connection; +} \ No newline at end of file diff --git a/extensions/cms/src/cmsResource/tree/cmsResourceEmptyTreeNode.ts b/extensions/cms/src/cmsResource/tree/cmsResourceEmptyTreeNode.ts new file mode 100644 index 0000000000..4918c70ef0 --- /dev/null +++ b/extensions/cms/src/cmsResource/tree/cmsResourceEmptyTreeNode.ts @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { TreeItem, TreeItemCollapsibleState } from 'vscode'; +import { NodeInfo } from 'azdata'; +import * as nls from 'vscode-nls'; +const localize = nls.loadMessageBundle(); + +import { TreeNode } from '../treeNode'; +import { CmsResourceItemType } from '../constants'; + +export class CmsResourceEmptyTreeNode extends TreeNode { + public getChildren(): TreeNode[] | Promise { + return []; + } + + public getTreeItem(): TreeItem | Promise { + let item = new TreeItem(CmsResourceEmptyTreeNode.addCmsServerLabel, TreeItemCollapsibleState.None); + item.command = { + title: CmsResourceEmptyTreeNode.addCmsServerLabel, + command: 'cms.resource.registerCMSServer', + arguments: [this] + }; + item.contextValue = CmsResourceItemType.cmsEmptyNodeContainer; + return item; + } + + public getNodeInfo(): NodeInfo { + return { + label: CmsResourceEmptyTreeNode.addCmsServerLabel, + isLeaf: true, + errorMessage: undefined, + metadata: undefined, + nodePath: this.generateNodePath(), + nodeStatus: undefined, + nodeType: CmsResourceItemType.cmsEmptyNodeContainer, + iconType: CmsResourceItemType.cmsEmptyNodeContainer, + nodeSubType: undefined + }; + } + + public get nodePathValue(): string { + return 'message_cmsTreeNode'; + } + + private static readonly addCmsServerLabel = localize('cms.resource.tree.CMSTreeNode.addCmsServerLabel', 'Add Central Management Server...'); +} diff --git a/extensions/cms/src/cmsResource/tree/cmsResourceTreeNode.ts b/extensions/cms/src/cmsResource/tree/cmsResourceTreeNode.ts new file mode 100644 index 0000000000..2c95156c61 --- /dev/null +++ b/extensions/cms/src/cmsResource/tree/cmsResourceTreeNode.ts @@ -0,0 +1,124 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; +import * as azdata from 'azdata'; +import * as nls from 'vscode-nls'; +import { TreeItemCollapsibleState } from 'vscode'; +import { AppContext } from '../../appContext'; +import { TreeNode } from '../treeNode'; +import { CmsResourceTreeNodeBase } from './baseTreeNodes'; +import { CmsResourceItemType } from '../constants'; +import { ICmsResourceTreeChangeHandler } from './treeChangeHandler'; +import { RegisteredServerTreeNode } from './registeredServerTreeNode'; +import { ServerGroupTreeNode } from './serverGroupTreeNode'; +import { CmsResourceMessageTreeNode } from '../messageTreeNode'; + +const localize = nls.loadMessageBundle(); + +export class CmsResourceTreeNode extends CmsResourceTreeNodeBase { + + private _id: string = undefined; + private _serverGroupNodes: ServerGroupTreeNode[] = []; + + public constructor( + name: string, + description: string, + ownerUri: string, + private _connection: azdata.connection.Connection, + appContext: AppContext, + treeChangeHandler: ICmsResourceTreeChangeHandler, + parent: TreeNode + ) { + super(name, description, ownerUri, appContext, treeChangeHandler, parent); + this._id = `cms_cmsServer_${this.name}`; + } + + public async getChildren(): Promise { + try { + let nodes: TreeNode[] = []; + return this.appContext.apiWrapper.createCmsServer(this.connection, this.name, this.description).then(async (result) => { + if (result) { + if (result.registeredServersList) { + result.registeredServersList.forEach((registeredServer) => { + nodes.push(new RegisteredServerTreeNode( + registeredServer.name, + registeredServer.description, + registeredServer.serverName, + registeredServer.relativePath, + this.ownerUri, + this.appContext, + this.treeChangeHandler, this)); + }); + } + if (result.registeredServerGroups) { + if (result.registeredServerGroups) { + this._serverGroupNodes = []; + result.registeredServerGroups.forEach((serverGroup) => { + let serverGroupNode = new ServerGroupTreeNode( + serverGroup.name, + serverGroup.description, + serverGroup.relativePath, + this.ownerUri, + this.appContext, + this.treeChangeHandler, this); + nodes.push(serverGroupNode); + this._serverGroupNodes.push(serverGroupNode); + }); + } + } + if (nodes.length > 0) { + return nodes; + } else { + return [CmsResourceMessageTreeNode.create(CmsResourceTreeNode.noResourcesLabel, undefined)]; + } + + } + }); + } catch { + return []; + } + } + + public getTreeItem(): azdata.TreeItem | Promise { + const item = new azdata.TreeItem(this.name, TreeItemCollapsibleState.Collapsed); + item.contextValue = CmsResourceItemType.cmsNodeContainer; + item.id = this._id; + item.tooltip = this.description; + item.iconPath = { + dark: this.appContext.extensionContext.asAbsolutePath('resources/light/centralmanagement_server.svg'), + light: this.appContext.extensionContext.asAbsolutePath('resources/light/centralmanagement_server.svg') + }; + return item; + } + + public getNodeInfo(): azdata.NodeInfo { + return { + label: this.name, + isLeaf: false, + errorMessage: undefined, + metadata: undefined, + nodePath: this.generateNodePath(), + nodeStatus: undefined, + nodeType: CmsResourceItemType.cmsNodeContainer, + nodeSubType: undefined, + iconType: CmsResourceItemType.cmsNodeContainer + }; + } + + public get nodePathValue(): string { + return this._id; + } + + public get connection(): azdata.connection.Connection { + return this._connection; + } + + public get serverGroupNodes(): ServerGroupTreeNode[] { + return this._serverGroupNodes; + } + + public static readonly noResourcesLabel = localize('cms.resource.cmsResourceTreeNode.noResourcesLabel', 'No resources found'); +} diff --git a/extensions/cms/src/cmsResource/tree/registeredServerTreeNode.ts b/extensions/cms/src/cmsResource/tree/registeredServerTreeNode.ts new file mode 100644 index 0000000000..5f085be4d0 --- /dev/null +++ b/extensions/cms/src/cmsResource/tree/registeredServerTreeNode.ts @@ -0,0 +1,91 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; +import * as azdata from 'azdata'; +import { TreeItemCollapsibleState } from 'vscode'; +import { TreeNode } from '../treeNode'; +import { CmsResourceItemType } from '../constants'; +import { CmsResourceTreeNodeBase } from './baseTreeNodes'; +import { AppContext } from '../../appContext'; +import { ICmsResourceTreeChangeHandler } from './treeChangeHandler'; +import { generateGuid } from '../utils'; + +export class RegisteredServerTreeNode extends CmsResourceTreeNodeBase { + + private _id: string = undefined; + + constructor( + name: string, + description: string, + private serverName: string, + private _relativePath: string, + ownerUri: string, + appContext: AppContext, + treeChangeHandler: ICmsResourceTreeChangeHandler, + parent: TreeNode + ) { + super(name, description, ownerUri, appContext, treeChangeHandler, parent); + this._id = `cms_registeredServer_${this.name ? this.name : this.serverName}`; + } + + public async getChildren(): Promise { + return; + } + + public getTreeItem(): azdata.TreeItem | Promise { + let payload = { + id: generateGuid(), + connectionName: this.name ? this.name : this.serverName, + serverName: this.serverName, + databaseName: '', + userName: undefined, + password: undefined, + authenticationType: 'Integrated', + savePassword: false, + groupFullName: '', + groupId: '', + providerName: 'MSSQL', + saveProfile: false, + options: {} + }; + let treeItem = { + payload: payload, + id: this._id, + tooltip: this.description, + contextValue: CmsResourceItemType.registeredServer, + collapsibleState: TreeItemCollapsibleState.Collapsed, + label: this.name ? this.name : this.serverName, + childProvider: 'MSSQL', + iconPath: { + dark: this.appContext.extensionContext.asAbsolutePath('resources/light/regserverserver.svg'), + light: this.appContext.extensionContext.asAbsolutePath('resources/light/regserverserver.svg') + } + }; + + return treeItem; + } + + public getNodeInfo(): azdata.NodeInfo { + return { + label: this.name ? this.name : this.serverName, + isLeaf: false, + errorMessage: undefined, + metadata: undefined, + nodePath: this.generateNodePath(), + nodeStatus: undefined, + nodeType: CmsResourceItemType.registeredServer, + nodeSubType: undefined + }; + } + + public get nodePathValue(): string { + return this._id; + } + + public get relativePath(): string { + return this._relativePath; + } +} diff --git a/extensions/cms/src/cmsResource/tree/serverGroupTreeNode.ts b/extensions/cms/src/cmsResource/tree/serverGroupTreeNode.ts new file mode 100644 index 0000000000..310b1bd25f --- /dev/null +++ b/extensions/cms/src/cmsResource/tree/serverGroupTreeNode.ts @@ -0,0 +1,119 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { TreeItem, TreeItemCollapsibleState } from 'vscode'; +import * as azdata from 'azdata'; +import { TreeNode } from '../treeNode'; +import { CmsResourceItemType } from '../constants'; +import { CmsResourceTreeNodeBase } from './baseTreeNodes'; +import { AppContext } from '../../appContext'; +import { ICmsResourceTreeChangeHandler } from './treeChangeHandler'; +import { RegisteredServerTreeNode } from './registeredServerTreeNode'; +import { CmsResourceMessageTreeNode } from '../messageTreeNode'; +import { CmsResourceTreeNode } from './cmsResourceTreeNode'; + +export class ServerGroupTreeNode extends CmsResourceTreeNodeBase { + + private _id: string = undefined; + private _serverGroupNodes: ServerGroupTreeNode[] = []; + + constructor( + name: string, + description: string, + private _relativePath: string, + ownerUri: string, + appContext: AppContext, + treeChangeHandler: ICmsResourceTreeChangeHandler, + parent: TreeNode + ) { + super(name, description, ownerUri, appContext, treeChangeHandler, parent); + this._id = `cms_serverGroup_${this.name}`; + } + public getChildren(): TreeNode[] | Promise { + try { + let nodes = []; + return this.appContext.apiWrapper.getRegisteredServers(this.ownerUri, this.relativePath).then((result) => { + if (result) { + if (result.registeredServersList) { + result.registeredServersList.forEach((registeredServer) => { + nodes.push(new RegisteredServerTreeNode( + registeredServer.name, + registeredServer.description, + registeredServer.serverName, + registeredServer.relativePath, + this.ownerUri, + this.appContext, + this.treeChangeHandler, this)); + }); + } + if (result.registeredServerGroups) { + if (result.registeredServerGroups) { + this._serverGroupNodes = []; + result.registeredServerGroups.forEach((serverGroup) => { + let serverGroupNode = new ServerGroupTreeNode( + serverGroup.name, + serverGroup.description, + serverGroup.relativePath, + this.ownerUri, + this.appContext, this.treeChangeHandler, this); + nodes.push(serverGroupNode); + this._serverGroupNodes.push(serverGroupNode); + }); + } + } + if (nodes.length > 0) { + return nodes; + } else { + return [CmsResourceMessageTreeNode.create(CmsResourceTreeNode.noResourcesLabel, undefined)]; + } + } else { + return [CmsResourceMessageTreeNode.create(CmsResourceTreeNode.noResourcesLabel, undefined)]; + } + }); + } catch { + return []; + } + + } + + public getTreeItem(): TreeItem | Promise { + let item = new TreeItem(this.name, TreeItemCollapsibleState.Collapsed); + item.contextValue = CmsResourceItemType.serverGroup; + item.id = this._id; + item.tooltip = this.description; + item.iconPath = { + dark: this.appContext.extensionContext.asAbsolutePath('resources/light/folder.svg'), + light: this.appContext.extensionContext.asAbsolutePath('resources/light/folder.svg') + }; + return item; + } + + public getNodeInfo(): azdata.NodeInfo { + return { + label: this.name, + isLeaf: false, + errorMessage: undefined, + metadata: undefined, + nodePath: this.generateNodePath(), + nodeStatus: undefined, + nodeType: CmsResourceItemType.serverGroup, + nodeSubType: undefined + }; + } + + public get nodePathValue(): string { + return this._id; + } + + public get relativePath(): string { + return this._relativePath; + } + + public get serverGroupNodes(): ServerGroupTreeNode[] { + return this._serverGroupNodes; + } +} diff --git a/extensions/cms/src/cmsResource/tree/treeChangeHandler.ts b/extensions/cms/src/cmsResource/tree/treeChangeHandler.ts new file mode 100644 index 0000000000..fa0ba36af3 --- /dev/null +++ b/extensions/cms/src/cmsResource/tree/treeChangeHandler.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { TreeNode } from '../treeNode'; + +export interface ICmsResourceTreeChangeHandler { + notifyNodeChanged(node: TreeNode): void; +} diff --git a/extensions/cms/src/cmsResource/tree/treeProvider.ts b/extensions/cms/src/cmsResource/tree/treeProvider.ts new file mode 100644 index 0000000000..90ca066182 --- /dev/null +++ b/extensions/cms/src/cmsResource/tree/treeProvider.ts @@ -0,0 +1,104 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; +import { TreeDataProvider, EventEmitter, Event, TreeItem } from 'vscode'; +import { AppContext } from '../../appContext'; +import * as nls from 'vscode-nls'; +const localize = nls.loadMessageBundle(); + +import { TreeNode } from '../treeNode'; +import { CmsResourceEmptyTreeNode } from './cmsResourceEmptyTreeNode'; +import { ICmsResourceTreeChangeHandler } from './treeChangeHandler'; +import { CmsResourceMessageTreeNode } from '../messageTreeNode'; +import { CmsResourceTreeNode } from './cmsResourceTreeNode'; + +export class CmsResourceTreeProvider implements TreeDataProvider, ICmsResourceTreeChangeHandler { + + private _appContext: AppContext; + + public constructor( + public readonly appContext: AppContext + ) { + this._appContext = appContext; + } + + public async getChildren(element?: TreeNode): Promise { + if (element) { + return element.getChildren(true); + } + + if (!this.isSystemInitialized) { + try { + // Call to collect all locally saved CMS servers + // to determine whether the system has been initialized. + let cmsConfig = this._appContext.apiWrapper.getConfiguration(); + let cachedServers = cmsConfig ? cmsConfig.cmsServers : []; + if (cachedServers && cachedServers.length > 0) { + let servers = []; + cachedServers.forEach((server) => { + servers.push(new CmsResourceTreeNode( + server.name, + server.description, + server.ownerUri, + server.connection, + this._appContext, this, null)); + this.appContext.apiWrapper.cacheRegisteredCmsServer(server.name, server.description, + server.ownerUri, server.connection); + }); + return servers; + } + this.isSystemInitialized = true; + this._onDidChangeTreeData.fire(undefined); + } catch (error) { + // System not initialized yet + this.isSystemInitialized = false; + } + return [CmsResourceMessageTreeNode.create(CmsResourceTreeProvider.loadingLabel, undefined)]; + } + try { + let registeredCmsServers = this.appContext.apiWrapper.registeredCmsServers; + if (registeredCmsServers && registeredCmsServers.length > 0) { + this.isSystemInitialized = true; + // save the CMS Servers for future use + await this._appContext.apiWrapper.setConfiguration(registeredCmsServers); + return registeredCmsServers.map((server) => { + return new CmsResourceTreeNode( + server.name, + server.description, + server.ownerUri, + server.connection, + this._appContext, this, null); + }); + } else { + return [new CmsResourceEmptyTreeNode()]; + + } + } catch (error) { + return [new CmsResourceEmptyTreeNode()]; + } + } + + public get onDidChangeTreeData(): Event { + return this._onDidChangeTreeData.event; + } + + public notifyNodeChanged(node: TreeNode): void { + this._onDidChangeTreeData.fire(node); + } + + public async refresh(node: TreeNode): Promise { + this._onDidChangeTreeData.fire(node); + } + + public getTreeItem(element: TreeNode): TreeItem | Thenable { + return element.getTreeItem(); + } + + public isSystemInitialized: boolean = false; + private _onDidChangeTreeData = new EventEmitter(); + + private static readonly loadingLabel = localize('cms.resource.tree.treeProvider.loadingLabel', 'Loading ...'); +} diff --git a/extensions/cms/src/cmsResource/treeNode.ts b/extensions/cms/src/cmsResource/treeNode.ts new file mode 100644 index 0000000000..560a5dcb5a --- /dev/null +++ b/extensions/cms/src/cmsResource/treeNode.ts @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as azdata from 'azdata'; +import * as vscode from 'vscode'; + +type TreeNodePredicate = (node: TreeNode) => boolean; + +export abstract class TreeNode { + public generateNodePath(): string { + let path = undefined; + if (this.parent) { + path = this.parent.generateNodePath(); + } + path = path ? `${path}/${this.nodePathValue}` : this.nodePathValue; + return path; + } + + public findNodeByPath(path: string, expandIfNeeded: boolean = false): Promise { + let condition: TreeNodePredicate = (node: TreeNode) => node.getNodeInfo().nodePath === path; + let filter: TreeNodePredicate = (node: TreeNode) => path.startsWith(node.getNodeInfo().nodePath); + return TreeNode.findNode(this, condition, filter, true); + } + + public static async findNode(node: TreeNode, condition: TreeNodePredicate, filter: TreeNodePredicate, expandIfNeeded: boolean): Promise { + if (!node) { + return undefined; + } + + if (condition(node)) { + return node; + } + + let nodeInfo = node.getNodeInfo(); + if (nodeInfo.isLeaf) { + return undefined; + } + + // TODO support filtering by already expanded / not yet expanded + let children = await node.getChildren(false); + if (children) { + for (let child of children) { + if (filter && filter(child)) { + let childNode = await this.findNode(child, condition, filter, expandIfNeeded); + if (childNode) { + return childNode; + } + } + } + } + return undefined; + } + + public get parent(): TreeNode { + return this._parent; + } + + public set parent(node: TreeNode) { + this._parent = node; + } + + public abstract getChildren(refreshChildren: boolean): TreeNode[] | Promise; + public abstract getTreeItem(): vscode.TreeItem | Promise; + + public abstract getNodeInfo(): azdata.NodeInfo; + + /** + * The value to use for this node in the node path + */ + public abstract get nodePathValue(): string; + + private _parent: TreeNode = undefined; +} diff --git a/extensions/cms/src/cmsResource/utils.ts b/extensions/cms/src/cmsResource/utils.ts new file mode 100644 index 0000000000..377f0ed9c6 --- /dev/null +++ b/extensions/cms/src/cmsResource/utils.ts @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as azdata from 'azdata'; + +export function generateGuid(): string { + let hexValues: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']; + let oct: string = ''; + let tmp: number; + for (let a: number = 0; a < 4; a++) { + tmp = (4294967296 * Math.random()) | 0; + oct += hexValues[tmp & 0xF] + + hexValues[tmp >> 4 & 0xF] + + hexValues[tmp >> 8 & 0xF] + + hexValues[tmp >> 12 & 0xF] + + hexValues[tmp >> 16 & 0xF] + + hexValues[tmp >> 20 & 0xF] + + hexValues[tmp >> 24 & 0xF] + + hexValues[tmp >> 28 & 0xF]; + } + let clockSequenceHi: string = hexValues[8 + (Math.random() * 4) | 0]; + return oct.substr(0, 8) + '-' + oct.substr(9, 4) + '-4' + oct.substr(13, 3) + '-' + clockSequenceHi + oct.substr(16, 3) + '-' + oct.substr(19, 12); +} + +export function toConnectionProfile(connectionInfo: azdata.connection.Connection): azdata.IConnectionProfile { + let options = connectionInfo.options; + let connProfile: azdata.IConnectionProfile = Object.assign({}, + connectionInfo, + { + serverName: `${options['server']}`, + userName: options['user'], + password: options['password'], + id: connectionInfo.connectionId, + } + ); + return connProfile; +} \ No newline at end of file diff --git a/extensions/cms/src/controllers/cmsResourceController.ts b/extensions/cms/src/controllers/cmsResourceController.ts new file mode 100644 index 0000000000..f39b82df09 --- /dev/null +++ b/extensions/cms/src/controllers/cmsResourceController.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import ControllerBase from './controllerBase'; +import { CmsResourceTreeProvider } from '../cmsResource/tree/treeProvider'; +import { registerCmsResourceCommands } from '../cmsResource/commands'; + +export default class CmsResourceController extends ControllerBase { + public activate(): Promise { + + const cmsResourceTree = new CmsResourceTreeProvider(this.appContext); + this.extensionContext.subscriptions.push(this.apiWrapper.registerTreeDataProvider('cmsResourceExplorer', cmsResourceTree)); + registerCmsResourceCommands(this.appContext, cmsResourceTree); + + return Promise.resolve(true); + } + + public deactivate(): void { + } +} diff --git a/extensions/cms/src/controllers/controllerBase.ts b/extensions/cms/src/controllers/controllerBase.ts new file mode 100644 index 0000000000..15e31972b4 --- /dev/null +++ b/extensions/cms/src/controllers/controllerBase.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as vscode from 'vscode'; + +import { AppContext } from '../appContext'; +import { ApiWrapper } from '../apiWrapper'; + +export default abstract class ControllerBase implements vscode.Disposable { + + public constructor(protected appContext: AppContext) { + } + + protected get apiWrapper(): ApiWrapper { + return this.appContext.apiWrapper; + } + + public get extensionContext(): vscode.ExtensionContext { + return this.appContext && this.appContext.extensionContext; + } + + abstract activate(): Promise; + + abstract deactivate(): void; + + public dispose(): void { + this.deactivate(); + } +} + diff --git a/extensions/cms/src/extension.ts b/extensions/cms/src/extension.ts new file mode 100644 index 0000000000..8c5527cc23 --- /dev/null +++ b/extensions/cms/src/extension.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as vscode from 'vscode'; +import CmsResourceController from './controllers/cmsResourceController'; +import { AppContext } from './appContext'; +import ControllerBase from './controllers/controllerBase'; +import { ApiWrapper } from './apiWrapper'; + +let controllers: ControllerBase[] = []; + +// this method is called when your extension is activated +// your extension is activated the very first time the command is executed +export function activate(extensionContext: vscode.ExtensionContext) { + const apiWrapper = new ApiWrapper(); + let appContext = new AppContext(extensionContext, apiWrapper); + let activations: Thenable[] = []; + + const cmsResourceController = new CmsResourceController(appContext); + controllers.push(cmsResourceController); + extensionContext.subscriptions.push(cmsResourceController); + activations.push(cmsResourceController.activate()); +} + +// this method is called when your extension is deactivated +export function deactivate() { + for (let controller of controllers) { + controller.deactivate(); + } +} diff --git a/extensions/cms/src/test/cmsResource/tree/cmsResourceEmptyTreeNode.test.ts b/extensions/cms/src/test/cmsResource/tree/cmsResourceEmptyTreeNode.test.ts new file mode 100644 index 0000000000..586bf22789 --- /dev/null +++ b/extensions/cms/src/test/cmsResource/tree/cmsResourceEmptyTreeNode.test.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as should from 'should'; +import * as vscode from 'vscode'; +import 'mocha'; +import { CmsResourceEmptyTreeNode } from '../../../cmsResource/tree/cmsResourceEmptyTreeNode'; +import { CmsResourceItemType } from '../../../cmsResource/constants'; + + +describe('CmsResourceEmptyTreeNode.info', function(): void { + it('Should be correct.', async function(): Promise { + const label = 'Add Central Management Server...'; + + const treeNode = new CmsResourceEmptyTreeNode(); + let children = await treeNode.getChildren(); + should.equal(0, children.length); + should(treeNode.nodePathValue).equal('message_cmsTreeNode'); + + const treeItem = await treeNode.getTreeItem(); + should(treeItem.label).equal(label); + should(treeItem.contextValue).equal(CmsResourceItemType.cmsEmptyNodeContainer); + should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None); + should(treeItem.command).not.undefined(); + should(treeItem.command.title).equal(label); + should(treeItem.command.command).equal('cms.resource.registerCMSServer'); + + const nodeInfo = treeNode.getNodeInfo(); + should(nodeInfo.isLeaf).true(); + should(nodeInfo.label).equal(label); + should(nodeInfo.nodeType).equal(CmsResourceItemType.cmsEmptyNodeContainer); + should(nodeInfo.iconType).equal(CmsResourceItemType.cmsEmptyNodeContainer); + }); +}); diff --git a/extensions/cms/src/test/cmsResource/tree/cmsResourceTreeNode.test.ts b/extensions/cms/src/test/cmsResource/tree/cmsResourceTreeNode.test.ts new file mode 100644 index 0000000000..27705f2f43 --- /dev/null +++ b/extensions/cms/src/test/cmsResource/tree/cmsResourceTreeNode.test.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; +import * as TypeMoq from 'typemoq'; +import * as should from 'should'; +import * as vscode from 'vscode'; +import 'mocha'; +import { AppContext } from '../../../appContext'; +import { ApiWrapper } from '../../../apiWrapper'; + +import { CmsResourceItemType } from '../../../cmsResource/constants'; +import { ServerGroupTreeNode } from '../../../cmsResource/tree/serverGroupTreeNode'; +import { ICmsResourceTreeChangeHandler } from '../../../cmsResource/tree/treeChangeHandler'; +import { cmsResource } from '../../../cmsResource/cms-resource'; + +// Mock services +let mockAppContext: AppContext; + +let mockExtensionContext: TypeMoq.IMock; +let mockApiWrapper: TypeMoq.IMock; +let mockTreeChangeHandler: TypeMoq.IMock; + +let mockResourceTreeDataProvider1: TypeMoq.IMock; +let mockResourceProvider1: TypeMoq.IMock; + +describe('ServerGroupTreeNode.info', function(): void { + beforeEach(() => { + mockExtensionContext = TypeMoq.Mock.ofType(); + mockApiWrapper = TypeMoq.Mock.ofType(); + mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object); + mockTreeChangeHandler = TypeMoq.Mock.ofType(); + mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType(); + mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType().object])); + mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny())); + mockResourceProvider1 = TypeMoq.Mock.ofType(); + mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1'); + mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object); + }); + + it('Should be correct.', async function(): Promise { + const label = 'test'; + + const treeNode = new ServerGroupTreeNode('test', 'test', 'test_path', 'test_ownerUri', mockAppContext, mockTreeChangeHandler.object, null); + + should(treeNode.nodePathValue).equal('cms_serverGroup_test'); + should(treeNode.relativePath).equal('test_path'); + + const treeItem = await treeNode.getTreeItem(); + should(treeItem.label).equal(label); + should(treeItem.contextValue).equal(CmsResourceItemType.serverGroup); + should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed); + should(treeItem.command).undefined(); + + const nodeInfo = treeNode.getNodeInfo(); + should(nodeInfo.isLeaf).false(); + should(nodeInfo.label).equal(label); + should(nodeInfo.nodeType).equal(CmsResourceItemType.serverGroup); + }); +}); diff --git a/extensions/cms/src/test/cmsResource/tree/registeredServerTreeNode.test.ts b/extensions/cms/src/test/cmsResource/tree/registeredServerTreeNode.test.ts new file mode 100644 index 0000000000..9395636f08 --- /dev/null +++ b/extensions/cms/src/test/cmsResource/tree/registeredServerTreeNode.test.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; +import * as TypeMoq from 'typemoq'; +import * as should from 'should'; +import * as vscode from 'vscode'; +import 'mocha'; +import { AppContext } from '../../../appContext'; +import { ApiWrapper } from '../../../apiWrapper'; + +import { CmsResourceItemType } from '../../../cmsResource/constants'; +import { RegisteredServerTreeNode } from '../../../cmsResource/tree/registeredServerTreeNode'; +import { ICmsResourceTreeChangeHandler } from '../../../cmsResource/tree/treeChangeHandler'; +import { cmsResource } from '../../../cmsResource/cms-resource'; + +// Mock services +let mockAppContext: AppContext; + +let mockExtensionContext: TypeMoq.IMock; +let mockApiWrapper: TypeMoq.IMock; +let mockTreeChangeHandler: TypeMoq.IMock; + +let mockResourceTreeDataProvider1: TypeMoq.IMock; +let mockResourceProvider1: TypeMoq.IMock; + +describe('RegisteredServerTreeNode.info', function(): void { + beforeEach(() => { + mockExtensionContext = TypeMoq.Mock.ofType(); + mockApiWrapper = TypeMoq.Mock.ofType(); + mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object); + mockTreeChangeHandler = TypeMoq.Mock.ofType(); + mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType(); + mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType().object])); + mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny())); + mockResourceProvider1 = TypeMoq.Mock.ofType(); + mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1'); + mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object); + }); + + it('Should be correct.', async function(): Promise { + const label = 'test'; + + const treeNode = new RegisteredServerTreeNode('test', 'test', 'test_server', 'test_path', 'test_ownerUri', mockAppContext, mockTreeChangeHandler.object, null); + + should(treeNode.nodePathValue).equal('cms_registeredServer_test'); + should(treeNode.relativePath).equal('test_path'); + + const treeItem = await treeNode.getTreeItem(); + should(treeItem.label).equal(label); + should(treeItem.contextValue).equal(CmsResourceItemType.serverGroup); + should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed); + should(treeItem.command).undefined(); + + const nodeInfo = treeNode.getNodeInfo(); + should(nodeInfo.isLeaf).false(); + should(nodeInfo.label).equal(label); + should(nodeInfo.nodeType).equal(CmsResourceItemType.serverGroup); + }); +}); diff --git a/extensions/cms/src/test/cmsResource/tree/serverGroupTreeNode.test.ts b/extensions/cms/src/test/cmsResource/tree/serverGroupTreeNode.test.ts new file mode 100644 index 0000000000..27705f2f43 --- /dev/null +++ b/extensions/cms/src/test/cmsResource/tree/serverGroupTreeNode.test.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; +import * as TypeMoq from 'typemoq'; +import * as should from 'should'; +import * as vscode from 'vscode'; +import 'mocha'; +import { AppContext } from '../../../appContext'; +import { ApiWrapper } from '../../../apiWrapper'; + +import { CmsResourceItemType } from '../../../cmsResource/constants'; +import { ServerGroupTreeNode } from '../../../cmsResource/tree/serverGroupTreeNode'; +import { ICmsResourceTreeChangeHandler } from '../../../cmsResource/tree/treeChangeHandler'; +import { cmsResource } from '../../../cmsResource/cms-resource'; + +// Mock services +let mockAppContext: AppContext; + +let mockExtensionContext: TypeMoq.IMock; +let mockApiWrapper: TypeMoq.IMock; +let mockTreeChangeHandler: TypeMoq.IMock; + +let mockResourceTreeDataProvider1: TypeMoq.IMock; +let mockResourceProvider1: TypeMoq.IMock; + +describe('ServerGroupTreeNode.info', function(): void { + beforeEach(() => { + mockExtensionContext = TypeMoq.Mock.ofType(); + mockApiWrapper = TypeMoq.Mock.ofType(); + mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object); + mockTreeChangeHandler = TypeMoq.Mock.ofType(); + mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType(); + mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType().object])); + mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny())); + mockResourceProvider1 = TypeMoq.Mock.ofType(); + mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1'); + mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object); + }); + + it('Should be correct.', async function(): Promise { + const label = 'test'; + + const treeNode = new ServerGroupTreeNode('test', 'test', 'test_path', 'test_ownerUri', mockAppContext, mockTreeChangeHandler.object, null); + + should(treeNode.nodePathValue).equal('cms_serverGroup_test'); + should(treeNode.relativePath).equal('test_path'); + + const treeItem = await treeNode.getTreeItem(); + should(treeItem.label).equal(label); + should(treeItem.contextValue).equal(CmsResourceItemType.serverGroup); + should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed); + should(treeItem.command).undefined(); + + const nodeInfo = treeNode.getNodeInfo(); + should(nodeInfo.isLeaf).false(); + should(nodeInfo.label).equal(label); + should(nodeInfo.nodeType).equal(CmsResourceItemType.serverGroup); + }); +}); diff --git a/extensions/cms/src/test/cmsResource/tree/treeProvider.test.ts b/extensions/cms/src/test/cmsResource/tree/treeProvider.test.ts new file mode 100644 index 0000000000..61095ddcb8 --- /dev/null +++ b/extensions/cms/src/test/cmsResource/tree/treeProvider.test.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as vscode from 'vscode'; +import * as should from 'should'; +import * as TypeMoq from 'typemoq'; +import 'mocha'; +import { AppContext } from '../../../appContext'; +import { ApiWrapper } from '../../../apiWrapper'; + +import { CmsResourceTreeProvider } from '../../../cmsResource/tree/treeProvider'; +import { CmsResourceMessageTreeNode } from '../../../cmsResource/messageTreeNode'; +import { CmsResourceEmptyTreeNode } from '../../../cmsResource/tree/cmsResourceEmptyTreeNode'; +import { CmsResourceTreeNode } from '../../../cmsResource/tree/cmsResourceTreeNode'; + +// Mock services +let mockAppContext: AppContext; +let mockExtensionContext: TypeMoq.IMock; +let mockApiWrapper: TypeMoq.IMock; + + +describe('CmsResourceTreeProvider.getChildren', function (): void { + beforeEach(() => { + mockExtensionContext = TypeMoq.Mock.ofType(); + mockApiWrapper = TypeMoq.Mock.ofType(); + mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object); + }); + + it('Should not be initialized.', async function (): Promise { + const treeProvider = new CmsResourceTreeProvider(mockAppContext); + should.notEqual(treeProvider.isSystemInitialized, true); + const children = await treeProvider.getChildren(undefined); + should.equal(children.length, 1); + should.equal(children[0].parent, undefined); + should.equal(children[0] instanceof CmsResourceMessageTreeNode, true); + }); + + it('Should not be loading after initialized.'), async function (): Promise { + const treeProvider = new CmsResourceTreeProvider(mockAppContext); + treeProvider.isSystemInitialized = true; + should.equal(true, treeProvider.isSystemInitialized); + mockApiWrapper.setup(x => x.registeredCmsServers).returns(null); + const children = await treeProvider.getChildren(undefined); + should.equal(children[0] instanceof CmsResourceEmptyTreeNode, true); + }; + + it('Should show CMS nodes if there are cached servers'), async function (): Promise { + const treeProvider = new CmsResourceTreeProvider(mockAppContext); + treeProvider.isSystemInitialized = true; + mockApiWrapper.setup(x => x.registeredCmsServers).returns(() => { + return [{ + name: 'name', + description: 'description', + ownerUri: 'ownerUri', + connection: null + }]; + }); + const children = await treeProvider.getChildren(undefined); + should.equal(children[0] instanceof CmsResourceTreeNode, true); + }; +}); diff --git a/extensions/cms/src/test/index.ts b/extensions/cms/src/test/index.ts new file mode 100644 index 0000000000..f1fccb588a --- /dev/null +++ b/extensions/cms/src/test/index.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const path = require('path'); +const testRunner = require('vscode/lib/testrunner'); + +const suite = 'CMS Unit Tests'; + +const options: any = { + ui: 'bdd', + useColors: true, + timeout: 60000 +}; + +if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) { + options.reporter = 'mocha-multi-reporters'; + options.reporterOptions = { + reporterEnabled: 'spec, mocha-junit-reporter', + mochaJunitReporterReporterOptions: { + testsuitesTitle: `${suite} ${process.platform}`, + mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`) + } + }; +} + +testRunner.configure(options); + +export = testRunner; diff --git a/extensions/cms/src/test/messageTreeNode.test.ts b/extensions/cms/src/test/messageTreeNode.test.ts new file mode 100644 index 0000000000..7447a4385d --- /dev/null +++ b/extensions/cms/src/test/messageTreeNode.test.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as should from 'should'; +import * as vscode from 'vscode'; +import 'mocha'; + +import { CmsResourceItemType } from '../cmsResource/constants'; +import { CmsResourceMessageTreeNode } from '../cmsResource/messageTreeNode'; + +describe('CmsResourceMessageTreeNode.info', function (): void { + it('Should be correct when created.', async function (): Promise { + const mockMessage = 'Test message'; + const treeNode = new CmsResourceMessageTreeNode(mockMessage, undefined); + + should(treeNode.nodePathValue).startWith('message_'); + + const treeItem = await treeNode.getTreeItem(); + should(treeItem.label).equal(mockMessage); + should(treeItem.contextValue).equal(CmsResourceItemType.cmsMessageNodeContainer); + should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None); + + const nodeInfo = treeNode.getNodeInfo(); + should(nodeInfo.isLeaf).true(); + should(nodeInfo.label).equal(mockMessage); + should(nodeInfo.nodeType).equal(CmsResourceItemType.cmsMessageNodeContainer); + should(nodeInfo.iconType).equal(CmsResourceItemType.cmsMessageNodeContainer); + }); +}); + +describe('CmsResourceMessageTreeNode.create', function (): void { + it('Should create a message node.', async function (): Promise { + const mockMessage = 'Test messagse'; + const treeNode = CmsResourceMessageTreeNode.create(mockMessage, undefined); + should(treeNode).instanceof(CmsResourceMessageTreeNode); + }); +}); diff --git a/extensions/cms/src/typings/ref.d.ts b/extensions/cms/src/typings/ref.d.ts new file mode 100644 index 0000000000..4d46be908b --- /dev/null +++ b/extensions/cms/src/typings/ref.d.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/// +/// +/// +/// \ No newline at end of file diff --git a/extensions/cms/tsconfig.json b/extensions/cms/tsconfig.json new file mode 100644 index 0000000000..f7addac327 --- /dev/null +++ b/extensions/cms/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "./out", + "lib": [ + "es6", "es2015.promise" + ], + "typeRoots": [ + "./node_modules/@types" + ], + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "moduleResolution": "node", + "declaration": false + }, + "exclude": [ + "node_modules" + ] +} diff --git a/extensions/cms/yarn.lock b/extensions/cms/yarn.lock new file mode 100644 index 0000000000..c3c2c8d8ff --- /dev/null +++ b/extensions/cms/yarn.lock @@ -0,0 +1,1566 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/mocha@^5.2.5": + version "5.2.5" + resolved "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.5.tgz#8a4accfc403c124a0bafe8a9fc61a05ec1032073" + integrity sha512-lAVp+Kj54ui/vLUFxsJTMtWvZraZxum3w3Nwkble2dNuV5VnPA+Mi2oGX9XYJAaIvZi3tn3cbjS/qcJXRb6Bww== + +"@types/node@^8.0.24": + version "8.10.36" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.36.tgz#eac05d576fbcd0b4ea3c912dc58c20475c08d9e4" + integrity sha512-SL6KhfM7PTqiFmbCW3eVNwVBZ+88Mrzbuvn9olPsfv43mbiWaFY+nRcz/TGGku0/lc2FepdMbImdMY1JrQ+zbw== + +ajv@^5.3.0: + version "5.5.2" + resolved "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" + integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= + dependencies: + co "^4.6.0" + fast-deep-equal "^1.0.0" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.3.0" + +ansi-cyan@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-cyan/-/ansi-cyan-0.1.1.tgz#538ae528af8982f28ae30d86f2f17456d2609873" + integrity sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM= + dependencies: + ansi-wrap "0.1.0" + +ansi-red@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-red/-/ansi-red-0.1.1.tgz#8c638f9d1080800a353c9c28c8a81ca4705d946c" + integrity sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw= + dependencies: + ansi-wrap "0.1.0" + +ansi-wrap@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" + integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768= + +append-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1" + integrity sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE= + dependencies: + buffer-equal "^1.0.0" + +arr-diff@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-1.1.0.tgz#687c32758163588fef7de7b36fabe495eb1a399a" + integrity sha1-aHwydYFjWI/vfeezb6vklesaOZo= + dependencies: + arr-flatten "^1.0.1" + array-slice "^0.2.3" + +arr-flatten@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-2.1.0.tgz#20f9eab5ec70f5c7d215b1077b1c39161d292c7d" + integrity sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0= + +array-differ@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" + integrity sha1-7/UuN1gknTO+QCuLuOVkuytdQDE= + +array-slice@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5" + integrity sha1-3Tz7gO15c6dRF82sabC5nshhhvU= + +array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= + dependencies: + array-uniq "^1.0.1" + +array-uniq@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= + +arrify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.8.0" + resolved "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" + integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +block-stream@*: + version "0.0.9" + resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" + integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo= + dependencies: + inherits "~2.0.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +browser-stdout@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" + integrity sha1-81HTKWnTL6XXpVZxVCY9korjvR8= + +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= + +buffer-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" + integrity sha1-WWFrSYME1Var1GaWayLu2j7KX74= + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +circular-json@^0.3.1: + version "0.3.3" + resolved "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" + integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== + +clone-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" + integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= + +clone-stats@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" + integrity sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE= + +clone-stats@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" + integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA= + +clone@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/clone/-/clone-0.2.0.tgz#c6126a90ad4f72dbf5acdb243cc37724fe93fc1f" + integrity sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8= + +clone@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= + +clone@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= + +cloneable-readable@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.1.2.tgz#d591dee4a8f8bc15da43ce97dceeba13d43e2a65" + integrity sha512-Bq6+4t+lbM8vhTs/Bef5c5AdEMtapp/iFb6+s4/Hh9MVTt8OLKH7ZOOZSCT+Ys7hsHvqv0GuMPJ1lnQJVHvxpg== + dependencies: + inherits "^2.0.1" + process-nextick-args "^2.0.0" + readable-stream "^2.3.5" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + +combined-stream@1.0.6: + version "1.0.6" + resolved "http://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" + integrity sha1-cj599ugBrFYTETp+RFqbactjKBg= + dependencies: + delayed-stream "~1.0.0" + +combined-stream@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" + integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== + dependencies: + delayed-stream "~1.0.0" + +commander@2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" + integrity sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ== + +commander@2.15.1: + version "2.15.1" + resolved "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" + integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +convert-source-map@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" + integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A== + dependencies: + safe-buffer "~5.1.1" + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +debug@3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + +deep-assign@^1.0.0: + version "1.0.0" + resolved "http://registry.npmjs.org/deep-assign/-/deep-assign-1.0.0.tgz#b092743be8427dc621ea0067cdec7e70dd19f37b" + integrity sha1-sJJ0O+hCfcYh6gBnzex+cN0Z83s= + dependencies: + is-obj "^1.0.0" + +define-properties@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +diff@3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" + integrity sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww== + +diff@3.5.0: + version "3.5.0" + resolved "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + +duplexer@^0.1.1, duplexer@~0.1.1: + version "0.1.1" + resolved "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" + integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= + +duplexify@^3.6.0: + version "3.6.1" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.6.1.tgz#b1a7a29c4abfd639585efaecce80d666b1e34125" + integrity sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA== + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +end-of-stream@^1.0.0, end-of-stream@^1.1.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" + integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== + dependencies: + once "^1.4.0" + +escape-string-regexp@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +event-stream@3.3.4: + version "3.3.4" + resolved "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" + integrity sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE= + dependencies: + duplexer "~0.1.1" + from "~0" + map-stream "~0.1.0" + pause-stream "0.0.11" + split "0.3" + stream-combiner "~0.0.4" + through "~2.3.1" + +event-stream@~3.3.4: + version "3.3.5" + resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.5.tgz#e5dd8989543630d94c6cf4d657120341fa31636b" + integrity sha512-vyibDcu5JL20Me1fP734QBH/kenBGLZap2n0+XXM7mvuUPzJ20Ydqj1aKcIeMdri1p+PU+4yAKugjN8KCVst+g== + dependencies: + duplexer "^0.1.1" + from "^0.1.7" + map-stream "0.0.7" + pause-stream "^0.0.11" + split "^1.0.1" + stream-combiner "^0.2.2" + through "^2.3.8" + +extend-shallow@^1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-1.1.4.tgz#19d6bf94dfc09d76ba711f39b872d21ff4dd9071" + integrity sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE= + dependencies: + kind-of "^1.1.0" + +extend@^3.0.0, extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +fast-deep-equal@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" + integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= + dependencies: + pend "~1.2.0" + +flush-write-stream@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.3.tgz#c5d586ef38af6097650b49bc41b55fabb19f35bd" + integrity sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw== + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.4" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.2" + resolved "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" + integrity sha1-SXBJi+YEwgwAXU9cI67NIda0kJk= + dependencies: + asynckit "^0.4.0" + combined-stream "1.0.6" + mime-types "^2.1.12" + +from@^0.1.7, from@~0: + version "0.1.7" + resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" + integrity sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4= + +fs-mkdirp-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz#0b7815fc3201c6a69e14db98ce098c16935259eb" + integrity sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes= + dependencies: + graceful-fs "^4.1.11" + through2 "^2.0.3" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fstream@^1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" + integrity sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE= + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob-stream@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4" + integrity sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ= + dependencies: + extend "^3.0.0" + glob "^7.1.1" + glob-parent "^3.1.0" + is-negated-glob "^1.0.0" + ordered-read-streams "^1.0.0" + pumpify "^1.3.5" + readable-stream "^2.1.5" + remove-trailing-separator "^1.0.1" + to-absolute-glob "^2.0.0" + unique-stream "^2.0.2" + +glob@7.1.2: + version "7.1.2" + resolved "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: + version "4.1.15" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" + integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== + +growl@1.10.3: + version "1.10.3" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f" + integrity sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q== + +growl@1.10.5: + version "1.10.5" + resolved "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== + +gulp-chmod@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/gulp-chmod/-/gulp-chmod-2.0.0.tgz#00c390b928a0799b251accf631aa09e01cc6299c" + integrity sha1-AMOQuSigeZslGsz2MaoJ4BzGKZw= + dependencies: + deep-assign "^1.0.0" + stat-mode "^0.2.0" + through2 "^2.0.0" + +gulp-filter@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/gulp-filter/-/gulp-filter-5.1.0.tgz#a05e11affb07cf7dcf41a7de1cb7b63ac3783e73" + integrity sha1-oF4Rr/sHz33PQafeHLe2OsN4PnM= + dependencies: + multimatch "^2.0.0" + plugin-error "^0.1.2" + streamfilter "^1.0.5" + +gulp-gunzip@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gulp-gunzip/-/gulp-gunzip-1.0.0.tgz#15b741145e83a9c6f50886241b57cc5871f151a9" + integrity sha1-FbdBFF6Dqcb1CIYkG1fMWHHxUak= + dependencies: + through2 "~0.6.5" + vinyl "~0.4.6" + +gulp-remote-src-vscode@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/gulp-remote-src-vscode/-/gulp-remote-src-vscode-0.5.1.tgz#a528509457affff3ff30cc73a4a97afe31c41c1d" + integrity sha512-mw4OGjtC/jlCWJFhbcAlel4YPvccChlpsl3JceNiB/DLJi24/UPxXt53/N26lgI3dknEqd4ErfdHrO8sJ5bATQ== + dependencies: + event-stream "3.3.4" + node.extend "^1.1.2" + request "^2.79.0" + through2 "^2.0.3" + vinyl "^2.0.1" + +gulp-untar@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/gulp-untar/-/gulp-untar-0.0.7.tgz#92067d79e0fa1e92d60562a100233a44a5aa08b4" + integrity sha512-0QfbCH2a1k2qkTLWPqTX+QO4qNsHn3kC546YhAP3/n0h+nvtyGITDuDrYBMDZeW4WnFijmkOvBWa5HshTic1tw== + dependencies: + event-stream "~3.3.4" + streamifier "~0.1.1" + tar "^2.2.1" + through2 "~2.0.3" + vinyl "^1.2.0" + +gulp-vinyl-zip@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/gulp-vinyl-zip/-/gulp-vinyl-zip-2.1.2.tgz#b79cc1a0e2c3b158ffee294590ade1e9caaf5e7b" + integrity sha512-wJn09jsb8PyvUeyFF7y7ImEJqJwYy40BqL9GKfJs6UGpaGW9A+N68Q+ajsIpb9AeR6lAdjMbIdDPclIGo1/b7Q== + dependencies: + event-stream "3.3.4" + queue "^4.2.1" + through2 "^2.0.3" + vinyl "^2.0.2" + vinyl-fs "^3.0.3" + yauzl "^2.2.1" + yazl "^2.2.1" + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz#44657f5688a22cfd4b72486e81b3a3fb11742c29" + integrity sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA== + dependencies: + ajv "^5.3.0" + har-schema "^2.0.0" + +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + integrity sha1-6CB68cx7MNRGzHC3NLXovhj4jVE= + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" + integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +he@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" + integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +is-absolute@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" + integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== + dependencies: + is-relative "^1.0.0" + is-windows "^1.0.1" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-extglob@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= + dependencies: + is-extglob "^2.1.0" + +is-negated-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" + integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= + +is-obj@^1.0.0: + version "1.0.1" + resolved "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= + +is-relative@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" + integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== + dependencies: + is-unc-path "^1.0.0" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-unc-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" + integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== + dependencies: + unc-path-regex "^0.1.2" + +is-utf8@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= + +is-valid-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa" + integrity sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao= + +is-windows@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is/-/is-3.2.1.tgz#d0ac2ad55eb7b0bec926a5266f6c662aaa83dca5" + integrity sha1-0Kwq1V63sL7JJqUmb2xmKqqD3KU= + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A= + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stable-stringify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= + dependencies: + jsonify "~0.0.0" + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +kind-of@^1.1.0: + version "1.1.0" + resolved "http://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44" + integrity sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ= + +lazystream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" + integrity sha1-9plf4PggOS9hOWvolGJAe7dxaOQ= + dependencies: + readable-stream "^2.0.5" + +lead@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lead/-/lead-1.0.0.tgz#6f14f99a37be3a9dd784f5495690e5903466ee42" + integrity sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI= + dependencies: + flush-write-stream "^1.0.2" + +lodash@^4.17.4: + version "4.17.11" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== + +map-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.0.7.tgz#8a1f07896d82b10926bd3744a2420009f88974a8" + integrity sha1-ih8HiW2CsQkmvTdEokIACfiJdKg= + +map-stream@~0.1.0: + version "0.1.0" + resolved "http://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" + integrity sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ= + +mime-db@~1.36.0: + version "1.36.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz#5020478db3c7fe93aad7bbcc4dcf869c43363397" + integrity sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.20" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.20.tgz#930cb719d571e903738520f8470911548ca2cc19" + integrity sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A== + dependencies: + mime-db "~1.36.0" + +minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +mkdirp@0.5.1, "mkdirp@>=0.5 0": + version "0.5.1" + resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +mocha@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.1.0.tgz#7d86cfbcf35cb829e2754c32e17355ec05338794" + integrity sha512-0RVnjg1HJsXY2YFDoTNzcc1NKhYuXKRrBAG2gDygmJJA136Cs2QlRliZG1mA0ap7cuaT30mw16luAeln+4RiNA== + dependencies: + browser-stdout "1.3.0" + commander "2.11.0" + debug "3.1.0" + diff "3.3.1" + escape-string-regexp "1.0.5" + glob "7.1.2" + growl "1.10.3" + he "1.1.1" + mkdirp "0.5.1" + supports-color "4.4.0" + +mocha@^5.2.0: + version "5.2.0" + resolved "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6" + integrity sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ== + dependencies: + browser-stdout "1.3.1" + commander "2.15.1" + debug "3.1.0" + diff "3.5.0" + escape-string-regexp "1.0.5" + glob "7.1.2" + growl "1.10.5" + he "1.1.1" + minimatch "3.0.4" + mkdirp "0.5.1" + supports-color "5.4.0" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +multimatch@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-2.1.0.tgz#9c7906a22fb4c02919e2f5f75161b4cdbd4b2a2b" + integrity sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis= + dependencies: + array-differ "^1.0.0" + array-union "^1.0.1" + arrify "^1.0.0" + minimatch "^3.0.0" + +node.extend@^1.1.2: + version "1.1.8" + resolved "https://registry.yarnpkg.com/node.extend/-/node.extend-1.1.8.tgz#0aab3e63789f4e6d68b42bc00073ad1881243cf0" + integrity sha512-L/dvEBwyg3UowwqOUTyDsGBU6kjBQOpOhshio9V3i3BMPv5YUb9+mWNN8MK0IbWqT0AqaTSONZf0aTuMMahWgA== + dependencies: + has "^1.0.3" + is "^3.2.1" + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +now-and-later@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.0.tgz#bc61cbb456d79cb32207ce47ca05136ff2e7d6ee" + integrity sha1-vGHLtFbXnLMiB85HygUTb/Ln1u4= + dependencies: + once "^1.3.2" + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +object-keys@^1.0.11, object-keys@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2" + integrity sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag== + +object.assign@^4.0.4: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +ordered-read-streams@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e" + integrity sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4= + dependencies: + readable-stream "^2.0.1" + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +pause-stream@0.0.11, pause-stream@^0.0.11: + version "0.0.11" + resolved "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" + integrity sha1-/lo0sMvOErWqaitAPuLnO2AvFEU= + dependencies: + through "~2.3" + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +plugin-error@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-0.1.2.tgz#3b9bb3335ccf00f425e07437e19276967da47ace" + integrity sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4= + dependencies: + ansi-cyan "^0.1.1" + ansi-red "^0.1.1" + arr-diff "^1.0.1" + arr-union "^2.0.1" + extend-shallow "^1.1.2" + +postinstall-build@^5.0.1: + version "5.0.3" + resolved "https://registry.npmjs.org/postinstall-build/-/postinstall-build-5.0.3.tgz#238692f712a481d8f5bc8960e94786036241efc7" + integrity sha512-vPvPe8TKgp4FLgY3+DfxCE5PIfoXBK2lyLfNCxsRbDsV6vS4oU5RG/IWxrblMn6heagbnMED3MemUQllQ2bQUg== + +process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== + +psl@^1.1.24: + version "1.1.29" + resolved "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67" + integrity sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ== + +pump@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pumpify@^1.3.5: + version "1.5.1" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" + integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== + dependencies: + duplexify "^3.6.0" + inherits "^2.0.3" + pump "^2.0.0" + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +querystringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.0.tgz#7ded8dfbf7879dcc60d0a644ac6754b283ad17ef" + integrity sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg== + +queue@^4.2.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/queue/-/queue-4.5.1.tgz#6e4290a2d7e99dc75b34494431633fe5437b0dac" + integrity sha512-AMD7w5hRXcFSb8s9u38acBZ+309u6GsiibP4/0YacJeaurRshogB7v/ZcVPxP5gD5+zIw6ixRHdutiYUJfwKHw== + dependencies: + inherits "~2.0.0" + +"readable-stream@>=1.0.33-1 <1.1.0-0": + version "1.0.34" + resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@~2.3.6: + version "2.3.6" + resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +remove-bom-buffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53" + integrity sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ== + dependencies: + is-buffer "^1.1.5" + is-utf8 "^0.2.1" + +remove-bom-stream@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz#05f1a593f16e42e1fb90ebf59de8e569525f9523" + integrity sha1-BfGlk/FuQuH7kOv1nejlaVJflSM= + dependencies: + remove-bom-buffer "^3.0.0" + safe-buffer "^5.1.0" + through2 "^2.0.3" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +replace-ext@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" + integrity sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ= + +replace-ext@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" + integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= + +request@2.88.0, request@^2.79.0, request@^2.88.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.0" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.4.3" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + +resolve-options@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-1.1.0.tgz#32bb9e39c06d67338dc9378c0d6d6074566ad131" + integrity sha1-MrueOcBtZzONyTeMDW1gdFZq0TE= + dependencies: + value-or-function "^3.0.0" + +rimraf@2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" + integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== + dependencies: + glob "^7.0.5" + +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^5.4.1: + version "5.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" + integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== + +should-equal@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3" + integrity sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA== + dependencies: + should-type "^1.4.0" + +should-format@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz#9bfc8f74fa39205c53d38c34d717303e277124f1" + integrity sha1-m/yPdPo5IFxT04w01xcwPidxJPE= + dependencies: + should-type "^1.3.0" + should-type-adaptors "^1.0.1" + +should-type-adaptors@^1.0.1: + version "1.1.0" + resolved "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz#401e7f33b5533033944d5cd8bf2b65027792e27a" + integrity sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA== + dependencies: + should-type "^1.3.0" + should-util "^1.0.0" + +should-type@^1.3.0, should-type@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz#0756d8ce846dfd09843a6947719dfa0d4cff5cf3" + integrity sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM= + +should-util@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/should-util/-/should-util-1.0.0.tgz#c98cda374aa6b190df8ba87c9889c2b4db620063" + integrity sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM= + +should@^13.2.1: + version "13.2.3" + resolved "https://registry.npmjs.org/should/-/should-13.2.3.tgz#96d8e5acf3e97b49d89b51feaa5ae8d07ef58f10" + integrity sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ== + dependencies: + should-equal "^2.0.0" + should-format "^3.0.3" + should-type "^1.4.0" + should-type-adaptors "^1.0.1" + should-util "^1.0.0" + +source-map-support@^0.5.0: + version "0.5.9" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f" + integrity sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +split@0.3: + version "0.3.3" + resolved "http://registry.npmjs.org/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f" + integrity sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8= + dependencies: + through "2" + +split@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" + integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== + dependencies: + through "2" + +sshpk@^1.7.0: + version "1.14.2" + resolved "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz#c6fc61648a3d9c4e764fd3fcdf4ea105e492ba98" + integrity sha1-xvxhZIo9nE52T9P8306hBeSSupg= + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + dashdash "^1.12.0" + getpass "^0.1.1" + safer-buffer "^2.0.2" + optionalDependencies: + bcrypt-pbkdf "^1.0.0" + ecc-jsbn "~0.1.1" + jsbn "~0.1.0" + tweetnacl "~0.14.0" + +stat-mode@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/stat-mode/-/stat-mode-0.2.2.tgz#e6c80b623123d7d80cf132ce538f346289072502" + integrity sha1-5sgLYjEj19gM8TLOU480YokHJQI= + +stream-combiner@^0.2.2: + version "0.2.2" + resolved "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz#aec8cbac177b56b6f4fa479ced8c1912cee52858" + integrity sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg= + dependencies: + duplexer "~0.1.1" + through "~2.3.4" + +stream-combiner@~0.0.4: + version "0.0.4" + resolved "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" + integrity sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ= + dependencies: + duplexer "~0.1.1" + +stream-shift@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" + integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= + +streamfilter@^1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/streamfilter/-/streamfilter-1.0.7.tgz#ae3e64522aa5a35c061fd17f67620c7653c643c9" + integrity sha512-Gk6KZM+yNA1JpW0KzlZIhjo3EaBJDkYfXtYSbOwNIQ7Zd6006E6+sCFlW1NDvFG/vnXhKmw6TJJgiEQg/8lXfQ== + dependencies: + readable-stream "^2.0.2" + +streamifier@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/streamifier/-/streamifier-0.1.1.tgz#97e98d8fa4d105d62a2691d1dc07e820db8dfc4f" + integrity sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8= + +string_decoder@~0.10.x: + version "0.10.31" + resolved "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= + +string_decoder@~1.1.1: + version "1.1.1" + resolved "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +supports-color@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" + integrity sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ== + dependencies: + has-flag "^2.0.0" + +supports-color@5.4.0: + version "5.4.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" + integrity sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w== + dependencies: + has-flag "^3.0.0" + +tar@^2.2.1: + version "2.2.1" + resolved "http://registry.npmjs.org/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" + integrity sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE= + dependencies: + block-stream "*" + fstream "^1.0.2" + inherits "2" + +through2-filter@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec" + integrity sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw= + dependencies: + through2 "~2.0.0" + xtend "~4.0.0" + +through2@^2.0.0, through2@^2.0.3, through2@~2.0.0, through2@~2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +through2@~0.6.5: + version "0.6.5" + resolved "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" + integrity sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg= + dependencies: + readable-stream ">=1.0.33-1 <1.1.0-0" + xtend ">=4.0.0 <4.1.0-0" + +through@2, through@^2.3.8, through@~2.3, through@~2.3.1, through@~2.3.4: + version "2.3.8" + resolved "http://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +to-absolute-glob@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b" + integrity sha1-GGX0PZ50sIItufFFt4z/fQ98hJs= + dependencies: + is-absolute "^1.0.0" + is-negated-glob "^1.0.0" + +to-through@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-through/-/to-through-2.0.0.tgz#fc92adaba072647bc0b67d6b03664aa195093af6" + integrity sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY= + dependencies: + through2 "^2.0.3" + +tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +typemoq@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/typemoq/-/typemoq-2.1.0.tgz#4452ce360d92cf2a1a180f0c29de2803f87af1e8" + integrity sha512-DtRNLb7x8yCTv/KHlwes+NI+aGb4Vl1iPC63Hhtcvk1DpxSAZzKWQv0RQFY0jX2Uqj0SDBNl8Na4e6MV6TNDgw== + dependencies: + circular-json "^0.3.1" + lodash "^4.17.4" + postinstall-build "^5.0.1" + +unc-path-regex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" + integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= + +unique-stream@^2.0.2: + version "2.2.1" + resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.2.1.tgz#5aa003cfbe94c5ff866c4e7d668bb1c4dbadb369" + integrity sha1-WqADz76Uxf+GbE59ZouxxNuts2k= + dependencies: + json-stable-stringify "^1.0.0" + through2-filter "^2.0.0" + +url-parse@^1.4.3: + version "1.4.4" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.4.tgz#cac1556e95faa0303691fec5cf9d5a1bc34648f8" + integrity sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg== + dependencies: + querystringify "^2.0.0" + requires-port "^1.0.0" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +uuid@^3.3.2: + version "3.3.2" + resolved "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + +value-or-function@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" + integrity sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM= + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +vinyl-fs@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7" + integrity sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng== + dependencies: + fs-mkdirp-stream "^1.0.0" + glob-stream "^6.1.0" + graceful-fs "^4.0.0" + is-valid-glob "^1.0.0" + lazystream "^1.0.0" + lead "^1.0.0" + object.assign "^4.0.4" + pumpify "^1.3.5" + readable-stream "^2.3.3" + remove-bom-buffer "^3.0.0" + remove-bom-stream "^1.2.0" + resolve-options "^1.1.0" + through2 "^2.0.0" + to-through "^2.0.0" + value-or-function "^3.0.0" + vinyl "^2.0.0" + vinyl-sourcemap "^1.1.0" + +vinyl-source-stream@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vinyl-source-stream/-/vinyl-source-stream-1.1.2.tgz#62b53a135610a896e98ca96bee3a87f008a8e780" + integrity sha1-YrU6E1YQqJbpjKlr7jqH8Aio54A= + dependencies: + through2 "^2.0.3" + vinyl "^0.4.3" + +vinyl-sourcemap@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz#92a800593a38703a8cdb11d8b300ad4be63b3e16" + integrity sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY= + dependencies: + append-buffer "^1.0.2" + convert-source-map "^1.5.0" + graceful-fs "^4.1.6" + normalize-path "^2.1.1" + now-and-later "^2.0.0" + remove-bom-buffer "^3.0.0" + vinyl "^2.0.0" + +vinyl@^0.4.3, vinyl@~0.4.6: + version "0.4.6" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847" + integrity sha1-LzVsh6VQolVGHza76ypbqL94SEc= + dependencies: + clone "^0.2.0" + clone-stats "^0.0.1" + +vinyl@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-1.2.0.tgz#5c88036cf565e5df05558bfc911f8656df218884" + integrity sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ= + dependencies: + clone "^1.0.0" + clone-stats "^0.0.1" + replace-ext "0.0.1" + +vinyl@^2.0.0, vinyl@^2.0.1, vinyl@^2.0.2: + version "2.2.0" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.0.tgz#d85b07da96e458d25b2ffe19fece9f2caa13ed86" + integrity sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg== + dependencies: + clone "^2.1.1" + clone-buffer "^1.0.0" + clone-stats "^1.0.0" + cloneable-readable "^1.0.0" + remove-trailing-separator "^1.0.1" + replace-ext "^1.0.0" + +vscode-nls@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" + integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== + +vscode@^1.1.26: + version "1.1.26" + resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.1.26.tgz#33d0feacd8ab5f78a0c4672235376c70cdea494b" + integrity sha512-z1Nf5J38gjUFbuDCbJHPN6OJ//5EG+e/yHlh6ERxj/U9B2Qc3aiHaFr38/fee/GGnxvRw/XegLMOG+UJwKi/Qg== + dependencies: + glob "^7.1.2" + gulp-chmod "^2.0.0" + gulp-filter "^5.0.1" + gulp-gunzip "1.0.0" + gulp-remote-src-vscode "^0.5.1" + gulp-untar "^0.0.7" + gulp-vinyl-zip "^2.1.2" + mocha "^4.0.1" + request "^2.88.0" + semver "^5.4.1" + source-map-support "^0.5.0" + url-parse "^1.4.3" + vinyl-fs "^3.0.3" + vinyl-source-stream "^1.1.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +"xtend@>=4.0.0 <4.1.0-0", xtend@~4.0.0, xtend@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= + +yauzl@^2.2.1: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + +yazl@^2.2.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35" + integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== + dependencies: + buffer-crc32 "~0.2.3" diff --git a/extensions/mssql/package.json b/extensions/mssql/package.json index fdae16a9dc..0e16a1dc83 100644 --- a/extensions/mssql/package.json +++ b/extensions/mssql/package.json @@ -473,15 +473,15 @@ "objectType": null, "categoryValues": [ { - "displayName": "SQL Login", + "displayName": "%mssql.connectionOptions.authType.categoryValues.sqlLogin%", "name": "SqlLogin" }, { - "displayName": "Windows Authentication", + "displayName": "%mssql.connectionOptions.authType.categoryValues.integrated%", "name": "Integrated" }, { - "displayName": "Azure Active Directory - Universal with MFA support", + "displayName": "%mssql.connectionOptions.authType.categoryValues.azureMFA%", "name": "AzureMFA" } ], diff --git a/extensions/mssql/package.nls.json b/extensions/mssql/package.nls.json index e247c88e8d..894bd4ede0 100644 --- a/extensions/mssql/package.nls.json +++ b/extensions/mssql/package.nls.json @@ -68,6 +68,9 @@ "mssql.connectionOptions.databaseName.description": "The name of the initial catalog or database int the data source", "mssql.connectionOptions.authType.displayName": "Authentication type", "mssql.connectionOptions.authType.description": "Specifies the method of authenticating with SQL Server", + "mssql.connectionOptions.authType.categoryValues.sqlLogin": "SQL Login", + "mssql.connectionOptions.authType.categoryValues.integrated": "Windows Authentication", + "mssql.connectionOptions.authType.categoryValues.azureMFA": "Azure Active Directory - Universal with MFA support", "mssql.connectionOptions.userName.displayName": "User name", "mssql.connectionOptions.userName.description": "Indicates the user ID to be used when connecting to the data source", "mssql.connectionOptions.password.displayName": "Password", diff --git a/extensions/mssql/src/main.ts b/extensions/mssql/src/main.ts index cae5ad2be5..2c551b5df5 100644 --- a/extensions/mssql/src/main.ts +++ b/extensions/mssql/src/main.ts @@ -119,7 +119,6 @@ export async function activate(context: vscode.ExtensionContext): Promise; + export function connect(connectionProfile: IConnectionProfile, saveConnection?: boolean, showDashboard?: boolean): Thenable; } export namespace nb { diff --git a/src/sql/platform/connection/common/connectionProfile.ts b/src/sql/platform/connection/common/connectionProfile.ts index 86b8845807..75e97378bb 100644 --- a/src/sql/platform/connection/common/connectionProfile.ts +++ b/src/sql/platform/connection/common/connectionProfile.ts @@ -93,6 +93,14 @@ export class ConnectionProfile extends ProviderConnectionInfo implements interfa this.options['azureTenantId'] = value; } + public get registeredCmsServerDescription(): string { + return this.options['registeredCmsServerDescription']; + } + + public set registeredCmsServerDescription(value: string) { + this.options['registeredCmsServerDescription'] = value; + } + public get groupFullName(): string { return this._groupName; } diff --git a/src/sql/platform/connection/common/connectionProfileGroup.ts b/src/sql/platform/connection/common/connectionProfileGroup.ts index 6f14ebaa3e..d6e02458e0 100644 --- a/src/sql/platform/connection/common/connectionProfileGroup.ts +++ b/src/sql/platform/connection/common/connectionProfileGroup.ts @@ -195,10 +195,10 @@ export class ConnectionProfileGroup implements IConnectionProfileGroup { public static getConnectionsInGroup(group: ConnectionProfileGroup): ConnectionProfile[] { let connections = []; - if (group.connections) { + if (group && group.connections) { group.connections.forEach((con) => connections.push(con)); } - if (group.children) { + if (group && group.children) { group.children.forEach((subgroup) => { connections = connections.concat(this.getConnectionsInGroup(subgroup)); }); diff --git a/src/sql/platform/connection/common/constants.ts b/src/sql/platform/connection/common/constants.ts index f09f28b867..0883d7c101 100644 --- a/src/sql/platform/connection/common/constants.ts +++ b/src/sql/platform/connection/common/constants.ts @@ -3,6 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; + // constants export const sqlConfigSectionName = 'sql'; export const outputChannelName = 'MSSQL'; @@ -34,3 +36,7 @@ export const passwordChars = '***************'; export const sqlLogin = 'SqlLogin'; export const integrated = 'Integrated'; export const azureMFA = 'AzureMFA'; + +/* CMS constants */ +export const cmsProviderName = 'MSSQL-CMS'; +export const cmsProviderDisplayName = localize('constants.cmsProviderDisplayName', 'Microsoft SQL Server (CMS)'); diff --git a/src/sql/sqlops.proposed.d.ts b/src/sql/sqlops.proposed.d.ts index 8e670d5da3..3c4db2a282 100644 --- a/src/sql/sqlops.proposed.d.ts +++ b/src/sql/sqlops.proposed.d.ts @@ -1741,7 +1741,7 @@ declare module 'sqlops' { * Opens the connection and add it to object explorer and opens the dashboard and returns the ConnectionResult * @param connectionProfile connection profile */ - export function connect(connectionProfile: IConnectionProfile): Thenable; + export function connect(connectionProfile: IConnectionProfile, saveConnection?: boolean, showDashboard?: boolean): Thenable; } export namespace nb { diff --git a/src/sql/workbench/api/node/extHostConnectionManagement.ts b/src/sql/workbench/api/node/extHostConnectionManagement.ts index f81e4bdf5b..01f58ba4dd 100644 --- a/src/sql/workbench/api/node/extHostConnectionManagement.ts +++ b/src/sql/workbench/api/node/extHostConnectionManagement.ts @@ -61,7 +61,7 @@ export class ExtHostConnectionManagement extends ExtHostConnectionManagementShap return this._proxy.$getUriForConnection(connectionId); } - public $connect(connectionProfile: azdata.IConnectionProfile): Thenable { - return this._proxy.$connect(connectionProfile); + public $connect(connectionProfile: azdata.IConnectionProfile, saveConnection: boolean = false, showDashboard: boolean = false): Thenable { + return this._proxy.$connect(connectionProfile, saveConnection, showDashboard); } } diff --git a/src/sql/workbench/api/node/mainThreadConnectionManagement.ts b/src/sql/workbench/api/node/mainThreadConnectionManagement.ts index 657f6e05b4..6fab90b832 100644 --- a/src/sql/workbench/api/node/mainThreadConnectionManagement.ts +++ b/src/sql/workbench/api/node/mainThreadConnectionManagement.ts @@ -61,7 +61,8 @@ export class MainThreadConnectionManagement implements MainThreadConnectionManag } public async $openConnectionDialog(providers: string[], initialConnectionProfile?: IConnectionProfile, connectionCompletionOptions?: azdata.IConnectionCompletionOptions): Promise { - let connectionProfile = await this._connectionDialogService.openDialogAndWait(this._connectionManagementService, { connectionType: 1, providers: providers }, initialConnectionProfile); + let connectionProfile = await this._connectionDialogService.openDialogAndWait(this._connectionManagementService, + { connectionType: 1, providers: providers }, initialConnectionProfile, undefined); const connection = connectionProfile ? { connectionId: connectionProfile.id, options: connectionProfile.options, @@ -110,12 +111,12 @@ export class MainThreadConnectionManagement implements MainThreadConnectionManag return connection; } - public $connect(connectionProfile: IConnectionProfile): Thenable { + public $connect(connectionProfile: IConnectionProfile, saveConnection: boolean = false, showDashboard: boolean = false): Thenable { let profile = new ConnectionProfile(this._capabilitiesService, connectionProfile); profile.id = generateUuid(); return this._connectionManagementService.connectAndSaveProfile(profile, undefined, { - saveTheConnection: true, - showDashboard: true, + saveTheConnection: saveConnection, + showDashboard: showDashboard, params: undefined, showConnectionDialogOnError: true, showFirewallRuleOnError: true diff --git a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts b/src/sql/workbench/api/node/sqlExtHost.api.impl.ts index 74f93700fb..efcad0537b 100644 --- a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts +++ b/src/sql/workbench/api/node/sqlExtHost.api.impl.ts @@ -569,8 +569,8 @@ export function createApiFactory( getUriForConnection(connectionId: string): Thenable { return extHostConnectionManagement.$getUriForConnection(connectionId); }, - connect(connectionProfile: sqlops.IConnectionProfile): Thenable { - return extHostConnectionManagement.$connect(connectionProfile); + connect(connectionProfile: sqlops.IConnectionProfile, saveConnection: boolean, showDashboard: boolean): Thenable { + return extHostConnectionManagement.$connect(connectionProfile, saveConnection, showDashboard); } }; @@ -956,7 +956,7 @@ export function createApiFactory( nb: nb, AzureResource: sqlExtHostTypes.AzureResource, extensions: extensions, - TreeItem: sqlExtHostTypes.TreeItem, + TreeItem: sqlExtHostTypes.TreeItem }; } }; diff --git a/src/sql/workbench/api/node/sqlExtHost.protocol.ts b/src/sql/workbench/api/node/sqlExtHost.protocol.ts index 69e1b6439f..41dd8ca35b 100644 --- a/src/sql/workbench/api/node/sqlExtHost.protocol.ts +++ b/src/sql/workbench/api/node/sqlExtHost.protocol.ts @@ -577,7 +577,7 @@ export interface MainThreadConnectionManagementShape extends IDisposable { $listDatabases(connectionId: string): Thenable; $getConnectionString(connectionId: string, includePassword: boolean): Thenable; $getUriForConnection(connectionId: string): Thenable; - $connect(connectionProfile: azdata.IConnectionProfile): Thenable; + $connect(connectionProfile: azdata.IConnectionProfile, saveConnection: boolean, showDashboard: boolean): Thenable; } export interface MainThreadCredentialManagementShape extends IDisposable { diff --git a/src/sql/workbench/services/connection/browser/cmsConnectionController.ts b/src/sql/workbench/services/connection/browser/cmsConnectionController.ts new file mode 100644 index 0000000000..0e7ffe81b5 --- /dev/null +++ b/src/sql/workbench/services/connection/browser/cmsConnectionController.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; +import { IConnectionComponentCallbacks } from 'sql/workbench/services/connection/browser/connectionDialogService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ConnectionProviderProperties } from 'sql/workbench/parts/connection/common/connectionProviderExtension'; +import { ConnectionController } from 'sql/workbench/services/connection/browser/connectionController'; +import { CmsConnectionWidget } from 'sql/workbench/services/connection/browser/cmsConnectionWidget'; + +/** + * Connection Controller for CMS Connections + */ +export class CmsConnectionController extends ConnectionController { + + constructor(container: HTMLElement, + connectionManagementService: IConnectionManagementService, + connectionProperties: ConnectionProviderProperties, + callback: IConnectionComponentCallbacks, + providerName: string, + authTypeChanged: boolean = false, + @IInstantiationService _instantiationService: IInstantiationService) { + super(container, connectionManagementService, connectionProperties, callback, providerName, _instantiationService); + let specialOptions = this._providerOptions.filter( + (property) => (property.specialValueType !== null && property.specialValueType !== undefined)); + this._connectionWidget = this._instantiationService.createInstance(CmsConnectionWidget, specialOptions, { + onSetConnectButton: (enable: boolean) => this._callback.onSetConnectButton(enable), + onCreateNewServerGroup: () => this.onCreateNewServerGroup(), + onAdvancedProperties: () => this.handleOnAdvancedProperties(), + onSetAzureTimeOut: () => this.handleonSetAzureTimeOut(), + onFetchDatabases: (serverName: string, authenticationType: string, userName?: string, password?: string) => this.onFetchDatabases( + serverName, authenticationType, userName, password).then(result => { + return result; + }) + }, providerName, authTypeChanged); + } + + public showUiComponent(container: HTMLElement, authTypeChanged: boolean = false): void { + this._databaseCache = new Map(); + this._connectionWidget.createConnectionWidget(container, authTypeChanged); + } +} \ No newline at end of file diff --git a/src/sql/workbench/services/connection/browser/cmsConnectionWidget.ts b/src/sql/workbench/services/connection/browser/cmsConnectionWidget.ts new file mode 100644 index 0000000000..ee8bbe664b --- /dev/null +++ b/src/sql/workbench/services/connection/browser/cmsConnectionWidget.ts @@ -0,0 +1,164 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./media/sqlConnection'; + +import { Button } from 'sql/base/browser/ui/button/button'; +import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox'; +import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox'; +import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox'; +import * as DialogHelper from 'sql/workbench/browser/modal/dialogHelper'; +import { IConnectionComponentCallbacks } from 'sql/workbench/services/connection/browser/connectionDialogService'; +import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; +import { ConnectionOptionSpecialType } from 'sql/workbench/api/common/sqlExtHostTypes'; +import * as Constants from 'sql/platform/connection/common/constants'; +import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; +import { Dropdown } from 'sql/base/browser/ui/editableDropdown/dropdown'; +import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; +import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; +import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; +import * as styler from 'sql/platform/theme/common/styler'; +import { IAccountManagementService } from 'sql/platform/accounts/common/interfaces'; + +import * as azdata from 'azdata'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { localize } from 'vs/nls'; +import * as DOM from 'vs/base/browser/dom'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { OS, OperatingSystem } from 'vs/base/common/platform'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { ConnectionWidget, AuthenticationType } from 'sql/workbench/services/connection/browser/connectionWidget'; + +/** + * Connection Widget clas for CMS Connections + */ +export class CmsConnectionWidget extends ConnectionWidget { + + private _serverDescriptionInputBox: InputBox; + protected _authTypeMap: { [providerName: string]: AuthenticationType[] } = { + [Constants.cmsProviderName]: [AuthenticationType.SqlLogin, AuthenticationType.Integrated, AuthenticationType.AzureMFA] + }; + + constructor(options: azdata.ConnectionOption[], + callbacks: IConnectionComponentCallbacks, + providerName: string, + authTypeChanged: boolean = false, + @IThemeService _themeService: IThemeService, + @IContextViewService _contextViewService: IContextViewService, + @ILayoutService _layoutService: ILayoutService, + @IConnectionManagementService _connectionManagementService: IConnectionManagementService, + @ICapabilitiesService _capabilitiesService: ICapabilitiesService, + @IClipboardService _clipboardService: IClipboardService, + @IConfigurationService _configurationService: IConfigurationService, + @IAccountManagementService _accountManagementService: IAccountManagementService + ) { + super(options, callbacks, providerName, _themeService, _contextViewService, _layoutService, _connectionManagementService, _capabilitiesService, + _clipboardService, _configurationService, _accountManagementService); + let authTypeOption = this._optionsMaps[ConnectionOptionSpecialType.authType]; + if (authTypeOption) { + if (OS === OperatingSystem.Windows || authTypeChanged) { + authTypeOption.defaultValue = this.getAuthTypeDisplayName(AuthenticationType.Integrated); + } else { + authTypeOption.defaultValue = this.getAuthTypeDisplayName(AuthenticationType.SqlLogin); + } + this._authTypeSelectBox = new SelectBox(authTypeOption.categoryValues.map(c => c.displayName), authTypeOption.defaultValue, this._contextViewService, undefined, { ariaLabel: authTypeOption.displayName }); + } + } + + protected registerListeners(): void { + super.registerListeners(); + if (this._serverDescriptionInputBox) { + this._toDispose.push(styler.attachInputBoxStyler(this._serverDescriptionInputBox, this._themeService)); + } + } + + protected fillInConnectionForm(authTypeChanged: boolean = false): void { + // Server Name + this.addServerNameOption(); + + // Authentication type + this.addAuthenticationTypeOption(authTypeChanged); + + // Login Options + this.addLoginOptions(); + + // Connection Name + this.addConnectionNameOptions(); + + // Server Description + this.addServerDescriptionOption(); + + // Advanced Options + this.addAdvancedOptions(); + } + + protected addAuthenticationTypeOption(authTypeChanged: boolean = false): void { + super.addAuthenticationTypeOption(); + let authTypeOption = this._optionsMaps[ConnectionOptionSpecialType.authType]; + let newAuthTypes = authTypeOption.categoryValues; + if (authTypeChanged) { + newAuthTypes = authTypeOption.categoryValues.filter((option) => option.name !== AuthenticationType.SqlLogin); + } + if (this._authTypeSelectBox) { + this._authTypeSelectBox.setOptions(newAuthTypes.map(c => c.displayName)); + } else { + this._authTypeSelectBox = new SelectBox(newAuthTypes.map(c => c.displayName), authTypeOption.defaultValue, this._contextViewService, undefined, { ariaLabel: authTypeOption.displayName }); + } + } + + private addServerDescriptionOption(): void { + // Registered Server Description + let serverDescriptionOption = this._optionsMaps['serverDescription']; + if (serverDescriptionOption) { + serverDescriptionOption.displayName = localize('serverDescription', 'Server Description (optional)'); + let serverDescriptionBuilder = DialogHelper.appendRow(this._tableContainer, serverDescriptionOption.displayName, 'connection-label', 'connection-input', 'server-description-input'); + this._serverDescriptionInputBox = new InputBox(serverDescriptionBuilder, this._contextViewService, { type: 'textarea', flexibleHeight: true }); + this._serverDescriptionInputBox.setHeight('75px'); + } + } + + public createConnectionWidget(container: HTMLElement, authTypeChanged: boolean = false): void { + this._container = DOM.append(container, DOM.$('div.connection-table')); + this._tableContainer = DOM.append(this._container, DOM.$('table.connection-table-content')); + this.fillInConnectionForm(authTypeChanged); + this.registerListeners(); + if (this._authTypeSelectBox) { + this.onAuthTypeSelected(this._authTypeSelectBox.value); + } + + DOM.addDisposableListener(container, 'paste', e => { + this._handleClipboard(); + }); + } + + public handleOnConnecting(): void { + super.handleOnConnecting(); + if (this._serverDescriptionInputBox) { + this._serverDescriptionInputBox.disable(); + } + } + + public handleResetConnection(): void { + super.handleResetConnection(); + if (this._serverDescriptionInputBox) { + this._serverDescriptionInputBox.enable(); + } + } + + public get registeredServerDescription(): string { + return this._serverDescriptionInputBox.value; + } + + public connect(model: IConnectionProfile): boolean { + let validInputs = super.connect(model); + if (this._serverDescriptionInputBox) { + model.options.registeredServerDescription = this._serverDescriptionInputBox.value; + model.options.registeredServerName = this._connectionNameInputBox.value; + } + return validInputs; + } +} \ No newline at end of file diff --git a/src/sql/workbench/services/connection/browser/connectionController.ts b/src/sql/workbench/services/connection/browser/connectionController.ts index 60597f4fd4..1fc6f320ae 100644 --- a/src/sql/workbench/services/connection/browser/connectionController.ts +++ b/src/sql/workbench/services/connection/browser/connectionController.ts @@ -19,21 +19,21 @@ import { ConnectionWidget } from 'sql/workbench/services/connection/browser/conn export class ConnectionController implements IConnectionComponentController { private _container: HTMLElement; private _connectionManagementService: IConnectionManagementService; - private _callback: IConnectionComponentCallbacks; - private _connectionWidget: ConnectionWidget; private _advancedController: AdvancedPropertiesController; private _model: IConnectionProfile; - private _providerOptions: azdata.ConnectionOption[]; private _providerName: string; + protected _callback: IConnectionComponentCallbacks; + protected _connectionWidget: ConnectionWidget; + protected _providerOptions: azdata.ConnectionOption[]; /* key: uri, value : list of databases */ - private _databaseCache = new Map(); + protected _databaseCache = new Map(); constructor(container: HTMLElement, connectionManagementService: IConnectionManagementService, connectionProperties: ConnectionProviderProperties, callback: IConnectionComponentCallbacks, providerName: string, - @IInstantiationService private _instantiationService: IInstantiationService) { + @IInstantiationService protected _instantiationService: IInstantiationService) { this._container = container; this._connectionManagementService = connectionManagementService; this._callback = callback; @@ -53,7 +53,7 @@ export class ConnectionController implements IConnectionComponentController { this._providerName = providerName; } - private onFetchDatabases(serverName: string, authenticationType: string, userName?: string, password?: string): Promise { + protected onFetchDatabases(serverName: string, authenticationType: string, userName?: string, password?: string): Promise { let tempProfile = this._model; tempProfile.serverName = serverName; tempProfile.authenticationType = authenticationType; @@ -90,14 +90,14 @@ export class ConnectionController implements IConnectionComponentController { }); } - private onCreateNewServerGroup(): void { + protected onCreateNewServerGroup(): void { this._connectionManagementService.showCreateServerGroupDialog({ onAddGroup: (groupName) => this._connectionWidget.updateServerGroup(this.getAllServerGroups(), groupName), onClose: () => this._connectionWidget.focusOnServerGroup() }); } - private handleonSetAzureTimeOut(): void { + protected handleonSetAzureTimeOut(): void { let timeoutPropertyName = 'connectTimeout'; let timeoutOption = this._model.options[timeoutPropertyName]; if (timeoutOption === undefined || timeoutOption === null) { @@ -105,7 +105,7 @@ export class ConnectionController implements IConnectionComponentController { } } - private handleOnAdvancedProperties(): void { + protected handleOnAdvancedProperties(): void { if (!this._advancedController) { this._advancedController = this._instantiationService.createInstance(AdvancedPropertiesController, () => this._connectionWidget.focusOnAdvancedButton()); } diff --git a/src/sql/workbench/services/connection/browser/connectionDialogService.ts b/src/sql/workbench/services/connection/browser/connectionDialogService.ts index 1890e2a25b..6ae3ef25be 100644 --- a/src/sql/workbench/services/connection/browser/connectionDialogService.ts +++ b/src/sql/workbench/services/connection/browser/connectionDialogService.ts @@ -18,7 +18,6 @@ import { entries } from 'sql/base/common/objects'; import { Deferred } from 'sql/base/common/promise'; import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService'; import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService'; - import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import * as platform from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; @@ -30,6 +29,7 @@ import { trim } from 'vs/base/common/strings'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { CmsConnectionController } from 'sql/workbench/services/connection/browser/cmsConnectionController'; export interface IConnectionValidateResult { isValid: boolean; @@ -45,7 +45,7 @@ export interface IConnectionComponentCallbacks { } export interface IConnectionComponentController { - showUiComponent(container: HTMLElement): void; + showUiComponent(container: HTMLElement, didChange?: boolean): void; initDialog(providers: string[], model: IConnectionProfile): void; validateConnection(): IConnectionValidateResult; fillInConnectionInputs(connectionInfo: IConnectionProfile): void; @@ -69,9 +69,11 @@ export class ConnectionDialogService implements IConnectionDialogService { private _providerNameToDisplayNameMap: { [providerDisplayName: string]: string } = {}; private _providerTypes: string[] = []; private _currentProviderType: string = 'Microsoft SQL Server'; + private _previousProviderType: string = undefined; private _connecting: boolean = false; private _connectionErrorTitle = localize('connectionError', 'Connection error'); private _dialogDeferredPromise: Deferred; + private _toDispose = []; /** * This is used to work around the interconnectedness of this code @@ -123,7 +125,7 @@ export class ConnectionDialogService implements IConnectionDialogService { /** * Gets the default provider with the following actions * 1. Checks if master provider(map) has data - * 2. If so, filters provider paramter against master map + * 2. If so, filters provider parameter against master map * 3. Fetches the result array and extracts the first element * 4. If none of the above data exists, returns 'MSSQL' * @returns: Default provider as string @@ -169,13 +171,15 @@ export class ConnectionDialogService implements IConnectionDialogService { profile.serverName = trim(profile.serverName); // append the port to the server name for SQL Server connections - if (this.getCurrentProviderName() === Constants.mssqlProviderName) { + if (this.getCurrentProviderName() === Constants.mssqlProviderName || + this.getCurrentProviderName() === Constants.cmsProviderName) { let portPropertyName: string = 'port'; let portOption: string = profile.options[portPropertyName]; if (portOption && portOption.indexOf(',') === -1) { profile.serverName = profile.serverName + ',' + portOption; } profile.options[portPropertyName] = undefined; + profile.providerName = Constants.mssqlProviderName; } // Disable password prompt during reconnect if connected with an empty password @@ -273,9 +277,21 @@ export class ConnectionDialogService implements IConnectionDialogService { // Set the model name, initialize the controller if needed, and return the controller this._model.providerName = providerName; if (!this._connectionControllerMap[providerName]) { - this._connectionControllerMap[providerName] = this._instantiationService.createInstance(ConnectionController, this._container, this._connectionManagementService, this._capabilitiesService.getCapabilities(providerName).connection, { - onSetConnectButton: (enable: boolean) => this.handleSetConnectButtonEnable(enable) - }, providerName); + if (providerName === Constants.cmsProviderName) { + this._connectionControllerMap[providerName] = + this._instantiationService.createInstance(CmsConnectionController, + this._container, this._connectionManagementService, + this._capabilitiesService.getCapabilities(providerName).connection, { + onSetConnectButton: (enable: boolean) => this.handleSetConnectButtonEnable(enable) + }, providerName, this._inputModel ? this._inputModel.options.authTypeChanged : false); + } else { + this._connectionControllerMap[providerName] = + this._instantiationService.createInstance(ConnectionController, + this._container, this._connectionManagementService, + this._capabilitiesService.getCapabilities(providerName).connection, { + onSetConnectButton: (enable: boolean) => this.handleSetConnectButtonEnable(enable) + }, providerName); + } } return this._connectionControllerMap[providerName]; } @@ -291,7 +307,12 @@ export class ConnectionDialogService implements IConnectionDialogService { this._model.providerName = this.getCurrentProviderName(); this._model = new ConnectionProfile(this._capabilitiesService, this._model); - this.uiController.showUiComponent(input.container); + if (this._inputModel && this._inputModel.options) { + this.uiController.showUiComponent(input.container, + this._inputModel.options.authTypeChanged); + } else { + this.uiController.showUiComponent(input.container); + } } private handleInitDialog() { @@ -373,11 +394,11 @@ export class ConnectionDialogService implements IConnectionDialogService { params?: INewConnectionParams, model?: IConnectionProfile, connectionResult?: IConnectionResult): Thenable { + this._connectionManagementService = connectionManagementService; this._params = params; this._inputModel = model; - return new Promise((resolve, reject) => { this.updateModelServerCapabilities(model); // If connecting from a query editor set "save connection" to false @@ -403,15 +424,26 @@ export class ConnectionDialogService implements IConnectionDialogService { this._connectionDialog.databaseDropdownExpanded = this.uiController.databaseDropdownExpanded; this.handleOnCancel(this._connectionDialog.newConnectionParams); }); - this._connectionDialog.onConnect((profile) => this.handleOnConnect(this._connectionDialog.newConnectionParams, profile)); + this._connectionDialog.onConnect((profile) => { + this.handleOnConnect(this._connectionDialog.newConnectionParams, profile); + }); this._connectionDialog.onShowUiComponent((input) => this.handleShowUiComponent(input)); this._connectionDialog.onInitDialog(() => this.handleInitDialog()); this._connectionDialog.onFillinConnectionInputs((input) => this.handleFillInConnectionInputs(input)); this._connectionDialog.onResetConnection(() => this.handleProviderOnResetConnection()); this._connectionDialog.render(); + this._previousProviderType = this._currentProviderType; } this._connectionDialog.newConnectionParams = params; - + // if provider changed + if ((this._previousProviderType !== this._currentProviderType) || + // or if currentProvider not set correctly yet + !(this._currentProviderType === Constants.cmsProviderDisplayName && params.providers && params.providers.length > 1)) { + this._previousProviderType = undefined; + this._connectionDialog.updateProvider(this._providerNameToDisplayNameMap[this.getDefaultProviderName()]); + } else { + this._connectionDialog.newConnectionParams = params; + } return new Promise(() => { this._connectionDialog.open(this._connectionManagementService.getRecentConnections(params.providers).length > 0); this.uiController.focusOnOpen(); diff --git a/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts b/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts index 1c47fcadf2..98bfe87279 100644 --- a/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts +++ b/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/connectionDialog'; - import { Button } from 'sql/base/browser/ui/button/button'; import { attachModalDialogStyler, attachButtonStyler } from 'sql/platform/theme/common/styler'; import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox'; @@ -19,7 +18,7 @@ import { RecentConnectionTreeController, RecentConnectionActionsProvider } from import { SavedConnectionTreeController } from 'sql/workbench/parts/connection/browser/savedConnectionTreeController'; import * as TelemetryKeys from 'sql/platform/telemetry/telemetryKeys'; import { ClearRecentConnectionsAction } from 'sql/workbench/parts/connection/common/connectionActions'; - +import * as Constants from 'sql/platform/connection/common/constants'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { Event, Emitter } from 'vs/base/common/event'; @@ -77,8 +76,8 @@ export class ConnectionDialogWidget extends Modal { public onShowUiComponent: Event = this._onShowUiComponent.event; private _onFillinConnectionInputs = new Emitter(); - public onFillinConnectionInputs: Event = this._onFillinConnectionInputs.event; + public onFillinConnectionInputs: Event = this._onFillinConnectionInputs.event; private _onResetConnection = new Emitter(); public onResetConnection: Event = this._onResetConnection.event; @@ -120,6 +119,8 @@ export class ConnectionDialogWidget extends Modal { if (validProviderNames && validProviderNames.length > 0) { filteredProviderTypes = filteredProviderTypes.filter(x => validProviderNames.find(v => this.providerNameToDisplayNameMap[v] === x) !== undefined); } + } else { + filteredProviderTypes = filteredProviderTypes.filter(x => x !== Constants.cmsProviderDisplayName); } this._providerTypeSelectBox.setOptions(filteredProviderTypes); } @@ -440,6 +441,10 @@ export class ConnectionDialogWidget extends Modal { this.onProviderTypeSelected(displayName); } + public dispose(): void { + this._toDispose.forEach(obj => obj.dispose()); + } + public set databaseDropdownExpanded(val: boolean) { this._databaseDropdownExpanded = val; } diff --git a/src/sql/workbench/services/connection/browser/connectionWidget.ts b/src/sql/workbench/services/connection/browser/connectionWidget.ts index 08f42ff851..f9811ec256 100644 --- a/src/sql/workbench/services/connection/browser/connectionWidget.ts +++ b/src/sql/workbench/services/connection/browser/connectionWidget.ts @@ -37,13 +37,9 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; export class ConnectionWidget { - private _container: HTMLElement; - private _serverGroupSelectBox: SelectBox; private _previousGroupOption: string; private _serverGroupOptions: IConnectionProfileGroup[]; - private _connectionNameInputBox: InputBox; private _serverNameInputBox: InputBox; - private _databaseNameInputBox: Dropdown; private _userNameInputBox: InputBox; private _passwordInputBox: InputBox; private _password: string; @@ -55,22 +51,26 @@ export class ConnectionWidget { private readonly _azureProviderId = 'azurePublicCloud'; private _azureTenantId: string; private _azureAccountList: azdata.Account[]; - private _advancedButton: Button; private _callbacks: IConnectionComponentCallbacks; - private _authTypeSelectBox: SelectBox; - private _toDispose: lifecycle.IDisposable[]; - private _optionsMaps: { [optionType: number]: azdata.ConnectionOption }; - private _tableContainer: HTMLElement; private _focusedBeforeHandleOnConnection: HTMLElement; - private _providerName: string; - private _authTypeMap: { [providerName: string]: AuthenticationType[] } = { - [Constants.mssqlProviderName]: [AuthenticationType.SqlLogin, AuthenticationType.Integrated, AuthenticationType.AzureMFA] - }; private _saveProfile: boolean; private _databaseDropdownExpanded: boolean = false; private _defaultDatabaseName: string = localize('defaultDatabaseOption', ''); private _loadingDatabaseName: string = localize('loadingDatabaseOption', 'Loading...'); private _serverGroupDisplayString: string = localize('serverGroup', 'Server group'); + protected _container: HTMLElement; + protected _serverGroupSelectBox: SelectBox; + protected _authTypeSelectBox: SelectBox; + protected _toDispose: lifecycle.IDisposable[]; + protected _optionsMaps: { [optionType: number]: azdata.ConnectionOption }; + protected _tableContainer: HTMLElement; + protected _providerName: string; + protected _authTypeMap: { [providerName: string]: AuthenticationType[] } = { + [Constants.mssqlProviderName]: [AuthenticationType.SqlLogin, AuthenticationType.Integrated, AuthenticationType.AzureMFA] + }; + protected _connectionNameInputBox: InputBox; + protected _databaseNameInputBox: Dropdown; + protected _advancedButton: Button; public DefaultServerGroup: IConnectionProfileGroup = { id: '', name: localize('defaultServerGroup', ''), @@ -96,8 +96,8 @@ export class ConnectionWidget { constructor(options: azdata.ConnectionOption[], callbacks: IConnectionComponentCallbacks, providerName: string, - @IThemeService private _themeService: IThemeService, - @IContextViewService private _contextViewService: IContextViewService, + @IThemeService protected _themeService: IThemeService, + @IContextViewService protected _contextViewService: IContextViewService, @ILayoutService private _layoutService: ILayoutService, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService, @ICapabilitiesService private _capabilitiesService: ICapabilitiesService, @@ -125,13 +125,13 @@ export class ConnectionWidget { this._providerName = providerName; } - public createConnectionWidget(container: HTMLElement): void { + public createConnectionWidget(container: HTMLElement, authTypeChanged: boolean = false): void { this._serverGroupOptions = [this.DefaultServerGroup]; this._serverGroupSelectBox = new SelectBox(this._serverGroupOptions.map(g => g.name), this.DefaultServerGroup.name, this._contextViewService, undefined, { ariaLabel: this._serverGroupDisplayString }); this._previousGroupOption = this._serverGroupSelectBox.value; this._container = DOM.append(container, DOM.$('div.connection-table')); this._tableContainer = DOM.append(this._container, DOM.$('table.connection-table-content')); - this.fillInConnectionForm(); + this.fillInConnectionForm(authTypeChanged); this.registerListeners(); if (this._authTypeSelectBox) { this.onAuthTypeSelected(this._authTypeSelectBox.value); @@ -142,7 +142,7 @@ export class ConnectionWidget { }); } - private _handleClipboard(): void { + protected _handleClipboard(): void { if (this._configurationService.getValue('connection.parseClipboardForConnectionString')) { let paste = this._clipboardService.readText(); this._connectionManagementService.buildConnectionInfo(paste, this._providerName).then(e => { @@ -157,7 +157,37 @@ export class ConnectionWidget { } } - private fillInConnectionForm(): void { + protected fillInConnectionForm(authTypeChanged: boolean = false): void { + // Server Name + this.addServerNameOption(); + + // Authentication type + this.addAuthenticationTypeOption(authTypeChanged); + + // Login Options + this.addLoginOptions(); + + // Database + this.addDatabaseOption(); + + // Server Group + this.addServerGroupOption(); + + // Connection Name + this.addConnectionNameOptions(); + + // Advanced Options + this.addAdvancedOptions(); + } + + protected addAuthenticationTypeOption(authTypeChanged: boolean = false): void { + if (this._optionsMaps[ConnectionOptionSpecialType.authType]) { + let authType = DialogHelper.appendRow(this._tableContainer, this._optionsMaps[ConnectionOptionSpecialType.authType].displayName, 'connection-label', 'connection-input'); + DialogHelper.appendInputSelectBox(authType, this._authTypeSelectBox); + } + } + + protected addServerNameOption(): void { // Server name let serverNameOption = this._optionsMaps[ConnectionOptionSpecialType.serverName]; let serverName = DialogHelper.appendRow(this._tableContainer, serverNameOption.displayName, 'connection-label', 'connection-input'); @@ -174,13 +204,9 @@ export class ConnectionWidget { }, ariaLabel: serverNameOption.displayName }); + } - // Authentication type - if (this._optionsMaps[ConnectionOptionSpecialType.authType]) { - let authType = DialogHelper.appendRow(this._tableContainer, this._optionsMaps[ConnectionOptionSpecialType.authType].displayName, 'connection-label', 'connection-input'); - DialogHelper.appendInputSelectBox(authType, this._authTypeSelectBox); - } - + protected addLoginOptions(): void { // Username let self = this; let userNameOption = this._optionsMaps[ConnectionOptionSpecialType.userName]; @@ -211,35 +237,46 @@ export class ConnectionWidget { this._refreshCredentialsLink = DOM.append(refreshCredentials, DOM.$('a')); this._refreshCredentialsLink.href = '#'; this._refreshCredentialsLink.innerText = localize('connectionWidget.refreshAzureCredentials', 'Refresh account credentials'); - // Azure tenant picker let tenantLabel = localize('connection.azureTenantDropdownLabel', 'Azure AD tenant'); let tenantDropdown = DialogHelper.appendRow(this._tableContainer, tenantLabel, 'connection-label', 'connection-input', ['azure-account-row', 'azure-tenant-row']); this._azureTenantDropdown = new SelectBox([], undefined, this._contextViewService, tenantDropdown, { ariaLabel: tenantLabel }); DialogHelper.appendInputSelectBox(tenantDropdown, this._azureTenantDropdown); + } + private addDatabaseOption(): void { // Database let databaseOption = this._optionsMaps[ConnectionOptionSpecialType.databaseName]; - let databaseName = DialogHelper.appendRow(this._tableContainer, databaseOption.displayName, 'connection-label', 'connection-input'); - - this._databaseNameInputBox = new Dropdown(databaseName, this._contextViewService, this._layoutService, { - values: [this._defaultDatabaseName, this._loadingDatabaseName], - strictSelection: false, - placeholder: this._defaultDatabaseName, - maxHeight: 125, - ariaLabel: databaseOption.displayName, - actionLabel: localize('connectionWidget.toggleDatabaseNameDropdown', 'Select Database Toggle Dropdown') - }); + if (databaseOption) { + let databaseName = DialogHelper.appendRow(this._tableContainer, databaseOption.displayName, 'connection-label', 'connection-input'); + this._databaseNameInputBox = new Dropdown(databaseName, this._contextViewService, this._layoutService, { + values: [this._defaultDatabaseName, this._loadingDatabaseName], + strictSelection: false, + placeholder: this._defaultDatabaseName, + maxHeight: 125, + ariaLabel: databaseOption.displayName, + actionLabel: localize('connectionWidget.toggleDatabaseNameDropdown', 'Select Database Toggle Dropdown') + }); + } + } + private addServerGroupOption(): void { // Server group - let serverGroup = DialogHelper.appendRow(this._tableContainer, this._serverGroupDisplayString, 'connection-label', 'connection-input'); - DialogHelper.appendInputSelectBox(serverGroup, this._serverGroupSelectBox); + if (this._serverGroupSelectBox) { + let serverGroup = DialogHelper.appendRow(this._tableContainer, this._serverGroupDisplayString, 'connection-label', 'connection-input'); + DialogHelper.appendInputSelectBox(serverGroup, this._serverGroupSelectBox); + } + } + protected addConnectionNameOptions(): void { // Connection name let connectionNameOption = this._optionsMaps[ConnectionOptionSpecialType.connectionName]; + connectionNameOption.displayName = localize('connectionName', 'Name (optional)'); let connectionNameBuilder = DialogHelper.appendRow(this._tableContainer, connectionNameOption.displayName, 'connection-label', 'connection-input'); this._connectionNameInputBox = new InputBox(connectionNameBuilder, this._contextViewService, { ariaLabel: connectionNameOption.displayName }); + } + protected addAdvancedOptions(): void { let AdvancedLabel = localize('advanced', 'Advanced...'); this._advancedButton = this.createAdvancedButton(this._tableContainer, AdvancedLabel); } @@ -254,7 +291,7 @@ export class ConnectionWidget { return false; } - private createAdvancedButton(container: HTMLElement, title: string): Button { + protected createAdvancedButton(container: HTMLElement, title: string): Button { let rowContainer = DOM.append(container, DOM.$('tr')); DOM.append(rowContainer, DOM.$('td')); let cellContainer = DOM.append(rowContainer, DOM.$('td')); @@ -276,17 +313,49 @@ export class ConnectionWidget { return new Checkbox(checkboxContainer, { label, checked: isChecked, ariaLabel: label }); } - private registerListeners(): void { + protected registerListeners(): void { // Theme styler this._toDispose.push(styler.attachInputBoxStyler(this._serverNameInputBox, this._themeService)); - this._toDispose.push(styler.attachEditableDropdownStyler(this._databaseNameInputBox, this._themeService)); this._toDispose.push(styler.attachInputBoxStyler(this._connectionNameInputBox, this._themeService)); this._toDispose.push(styler.attachInputBoxStyler(this._userNameInputBox, this._themeService)); this._toDispose.push(styler.attachInputBoxStyler(this._passwordInputBox, this._themeService)); - this._toDispose.push(styler.attachSelectBoxStyler(this._serverGroupSelectBox, this._themeService)); this._toDispose.push(styler.attachButtonStyler(this._advancedButton, this._themeService)); this._toDispose.push(styler.attachCheckboxStyler(this._rememberPasswordCheckBox, this._themeService)); this._toDispose.push(styler.attachSelectBoxStyler(this._azureAccountDropdown, this._themeService)); + if (this._serverGroupSelectBox) { + this._toDispose.push(styler.attachSelectBoxStyler(this._serverGroupSelectBox, this._themeService)); + this._toDispose.push(this._serverGroupSelectBox.onDidSelect(selectedGroup => { + this.onGroupSelected(selectedGroup.selected); + })); + } + if (this._databaseNameInputBox) { + this._toDispose.push(styler.attachEditableDropdownStyler(this._databaseNameInputBox, this._themeService)); + this._toDispose.push(this._databaseNameInputBox.onFocus(() => { + this._databaseDropdownExpanded = true; + if (this.serverName) { + this._databaseNameInputBox.values = [this._loadingDatabaseName]; + this._callbacks.onFetchDatabases(this.serverName, this.authenticationType, this.userName, this._password).then(databases => { + if (databases) { + this._databaseNameInputBox.values = databases.sort((a, b) => a.localeCompare(b)); + } else { + this._databaseNameInputBox.values = [this._defaultDatabaseName]; + } + }).catch(() => { + this._databaseNameInputBox.values = [this._defaultDatabaseName]; + }); + } else { + this._databaseNameInputBox.values = [this._defaultDatabaseName]; + } + })); + + this._toDispose.push(this._databaseNameInputBox.onValueChange(s => { + if (s === this._defaultDatabaseName || s === this._loadingDatabaseName) { + this._databaseNameInputBox.value = ''; + } else { + this._databaseNameInputBox.value = s; + } + })); + } if (this._authTypeSelectBox) { // Theme styler @@ -321,10 +390,6 @@ export class ConnectionWidget { })); } - this._toDispose.push(this._serverGroupSelectBox.onDidSelect(selectedGroup => { - this.onGroupSelected(selectedGroup.selected); - })); - this._toDispose.push(this._serverNameInputBox.onDidChange(serverName => { this.serverNameChanged(serverName); })); @@ -336,33 +401,6 @@ export class ConnectionWidget { this._toDispose.push(this._passwordInputBox.onDidChange(passwordInput => { this._password = passwordInput; })); - - this._toDispose.push(this._databaseNameInputBox.onFocus(() => { - this._databaseDropdownExpanded = true; - if (this.serverName) { - this._databaseNameInputBox.values = [this._loadingDatabaseName]; - this._callbacks.onFetchDatabases(this.serverName, this.authenticationType, this.userName, this._password).then(databases => { - if (databases) { - this._databaseNameInputBox.values = databases.sort((a, b) => a.localeCompare(b)); - } else { - this._databaseNameInputBox.values = [this._defaultDatabaseName]; - } - }).catch(() => { - this._databaseNameInputBox.values = [this._defaultDatabaseName]; - }); - } else { - this._databaseNameInputBox.values = [this._defaultDatabaseName]; - } - })); - - this._toDispose.push(this._databaseNameInputBox.onValueChange(s => { - if (s === this._defaultDatabaseName || s === this._loadingDatabaseName) { - this._databaseNameInputBox.value = ''; - } else { - this._databaseNameInputBox.value = s; - } - })); - } private onGroupSelected(selectedGroup: string) { @@ -376,7 +414,7 @@ export class ConnectionWidget { } private setConnectButton(): void { - let showUsernameAndPassword: boolean = true; + let showUsernameAndPassword: boolean; if (this.authType) { showUsernameAndPassword = this.authType === AuthenticationType.SqlLogin; } @@ -384,7 +422,7 @@ export class ConnectionWidget { this._callbacks.onSetConnectButton(!!this.serverName); } - private onAuthTypeSelected(selectedAuthType: string) { + protected onAuthTypeSelected(selectedAuthType: string) { let currentAuthType = this.getMatchingAuthType(selectedAuthType); if (currentAuthType !== AuthenticationType.SqlLogin) { this._userNameInputBox.disable(); @@ -504,16 +542,20 @@ export class ConnectionWidget { } public focusOnServerGroup() { - this._serverGroupSelectBox.focus(); + if (this._serverGroupSelectBox) { + this._serverGroupSelectBox.focus(); + } } public updateServerGroup(connectionGroups: IConnectionProfileGroup[], groupName?: string) { - this._serverGroupOptions = connectionGroups; - this._serverGroupOptions.push(this._addNewServerGroup); - this._serverGroupSelectBox.setOptions(this._serverGroupOptions.map(g => g.name)); - if (groupName) { - this._serverGroupSelectBox.selectWithOptionName(groupName); - this._previousGroupOption = this._serverGroupSelectBox.value; + if (this._serverGroupSelectBox) { + this._serverGroupOptions = connectionGroups; + this._serverGroupOptions.push(this._addNewServerGroup); + this._serverGroupSelectBox.setOptions(this._serverGroupOptions.map(g => g.name)); + if (groupName) { + this._serverGroupSelectBox.selectWithOptionName(groupName); + this._previousGroupOption = this._serverGroupSelectBox.value; + } } } @@ -541,13 +583,15 @@ export class ConnectionWidget { public fillInConnectionInputs(connectionInfo: IConnectionProfile) { if (connectionInfo) { this._serverNameInputBox.value = this.getModelValue(connectionInfo.serverName); - this._databaseNameInputBox.value = this.getModelValue(connectionInfo.databaseName); this._connectionNameInputBox.value = this.getModelValue(connectionInfo.connectionName); this._userNameInputBox.value = this.getModelValue(connectionInfo.userName); this._passwordInputBox.value = connectionInfo.password ? Constants.passwordChars : ''; this._password = this.getModelValue(connectionInfo.password); this._saveProfile = connectionInfo.saveProfile; this._azureTenantId = connectionInfo.azureTenantId; + if (this._databaseNameInputBox) { + this._databaseNameInputBox.value = this.getModelValue(connectionInfo.databaseName); + } let groupName: string; if (this._saveProfile) { if (!connectionInfo.groupFullName) { @@ -558,8 +602,10 @@ export class ConnectionWidget { } else { groupName = this.NoneServerGroup.name; } - this._serverGroupSelectBox.selectWithOptionName(groupName); - this._previousGroupOption = this._serverGroupSelectBox.value; + if (this._serverGroupSelectBox) { + this._serverGroupSelectBox.selectWithOptionName(groupName); + this._previousGroupOption = this._serverGroupSelectBox.value; + } // To handle the empty password case if (this.getModelValue(connectionInfo.password) === '') { @@ -604,7 +650,7 @@ export class ConnectionWidget { } } - private getAuthTypeDisplayName(authTypeName: string) { + protected getAuthTypeDisplayName(authTypeName: string) { let displayName: string; let authTypeOption = this._optionsMaps[ConnectionOptionSpecialType.authType]; @@ -632,14 +678,17 @@ export class ConnectionWidget { public handleOnConnecting(): void { this._focusedBeforeHandleOnConnection = document.activeElement; this._advancedButton.enabled = false; - - this._serverGroupSelectBox.disable(); this._serverNameInputBox.disable(); - this._databaseNameInputBox.enabled = false; this._userNameInputBox.disable(); this._passwordInputBox.disable(); this._connectionNameInputBox.disable(); this._rememberPasswordCheckBox.enabled = false; + if (this._serverGroupSelectBox) { + this._serverGroupSelectBox.disable(); + } + if (this._databaseNameInputBox) { + this._databaseNameInputBox.enabled = false; + } if (this._authTypeSelectBox) { this._authTypeSelectBox.disable(); } @@ -647,11 +696,9 @@ export class ConnectionWidget { public handleResetConnection(): void { this._advancedButton.enabled = true; - - this._serverGroupSelectBox.enable(); this._serverNameInputBox.enable(); this._connectionNameInputBox.enable(); - this._databaseNameInputBox.enabled = true; + let currentAuthType: AuthenticationType = undefined; if (this._authTypeSelectBox) { this._authTypeSelectBox.enable(); @@ -667,6 +714,14 @@ export class ConnectionWidget { if (this._focusedBeforeHandleOnConnection) { this._focusedBeforeHandleOnConnection.focus(); } + + if (this._serverGroupSelectBox) { + this._serverGroupSelectBox.enable(); + } + + if (this._databaseNameInputBox) { + this._databaseNameInputBox.enabled = true; + } } public get connectionName(): string { @@ -678,7 +733,7 @@ export class ConnectionWidget { } public get databaseName(): string { - return this._databaseNameInputBox.value; + return this._databaseNameInputBox ? this._databaseNameInputBox.value : undefined; } public get userName(): string { @@ -742,24 +797,26 @@ export class ConnectionWidget { public connect(model: IConnectionProfile): boolean { let validInputs = this.validateInputs(); if (validInputs) { - model.connectionName = this.connectionName; model.serverName = this.serverName; - model.databaseName = this.databaseName; model.userName = this.userName; model.password = this.password; model.authenticationType = this.authenticationType; model.savePassword = this._rememberPasswordCheckBox.checked; - if (this._serverGroupSelectBox.value === this.DefaultServerGroup.name) { - model.groupFullName = ''; - model.saveProfile = true; - model.groupId = this.findGroupId(model.groupFullName); - } else if (this._serverGroupSelectBox.value === this.NoneServerGroup.name) { - model.groupFullName = ''; - model.saveProfile = false; - } else if (this._serverGroupSelectBox.value !== this._addNewServerGroup.name) { - model.groupFullName = this._serverGroupSelectBox.value; - model.saveProfile = true; - model.groupId = this.findGroupId(model.groupFullName); + model.connectionName = this.connectionName; + model.databaseName = this.databaseName; + if (this._serverGroupSelectBox) { + if (this._serverGroupSelectBox.value === this.DefaultServerGroup.name) { + model.groupFullName = ''; + model.saveProfile = true; + model.groupId = this.findGroupId(model.groupFullName); + } else if (this._serverGroupSelectBox.value === this.NoneServerGroup.name) { + model.groupFullName = ''; + model.saveProfile = false; + } else if (this._serverGroupSelectBox.value !== this._addNewServerGroup.name) { + model.groupFullName = this._serverGroupSelectBox.value; + model.saveProfile = true; + model.groupId = this.findGroupId(model.groupFullName); + } } if (this.authType === AuthenticationType.AzureMFA) { model.azureTenantId = this._azureTenantId; @@ -814,7 +871,7 @@ export class ConnectionWidget { } } -enum AuthenticationType { +export enum AuthenticationType { SqlLogin = 'SqlLogin', Integrated = 'Integrated', AzureMFA = 'AzureMFA' diff --git a/src/sqltest/parts/connection/connectionManagementService.test.ts b/src/sqltest/parts/connection/connectionManagementService.test.ts index 9bdde32cd0..8f168405bb 100644 --- a/src/sqltest/parts/connection/connectionManagementService.test.ts +++ b/src/sqltest/parts/connection/connectionManagementService.test.ts @@ -94,10 +94,11 @@ suite('SQL ConnectionManagementService tests', () => { let root = new ConnectionProfileGroup(ConnectionProfileGroup.RootGroupName, undefined, ConnectionProfileGroup.RootGroupName, undefined, undefined); root.connections = [ConnectionProfile.fromIConnectionProfile(capabilitiesService, connectionProfile)]; + connectionDialogService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), undefined)).returns(() => Promise.resolve(none)); + connectionDialogService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), undefined, undefined)).returns(() => Promise.resolve(none)); connectionDialogService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), undefined)).returns(() => Promise.resolve(none)); connectionDialogService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), undefined, undefined)).returns(() => Promise.resolve(none)); connectionDialogService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(none)); - connectionDialogService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), undefined, TypeMoq.It.isAny())).returns(() => Promise.resolve(none)); connectionStore.setup(x => x.addActiveConnection(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve()); connectionStore.setup(x => x.saveProfile(TypeMoq.It.isAny())).returns(() => Promise.resolve(connectionProfile)); diff --git a/src/sqltest/parts/query/editor/queryActions.test.ts b/src/sqltest/parts/query/editor/queryActions.test.ts index 6aa7cb0a17..7fa7f625b4 100644 --- a/src/sqltest/parts/query/editor/queryActions.test.ts +++ b/src/sqltest/parts/query/editor/queryActions.test.ts @@ -144,7 +144,7 @@ suite('SQL QueryAction Tests', () => { assert.equal(calledRunQueryOnInput, false, 'run should not call runQuery'); testQueryInput.verify(x => x.runQuery(undefined), TypeMoq.Times.never()); - // and the conneciton dialog should open with the correct parameter details + // and the connection dialog should open with the correct parameter details assert.equal(countCalledShowDialog, 1, 'run should call showDialog'); assert.equal(connectionParams.connectionType, ConnectionType.editor, 'connectionType should be queryEditor'); assert.equal(connectionParams.runQueryOnCompletion, RunQueryOnConnectionMode.executeQuery, 'runQueryOnCompletion should be true`'); @@ -158,6 +158,7 @@ suite('SQL QueryAction Tests', () => { //runQuery should be run, and the conneciton dialog should not open assert.equal(calledRunQueryOnInput, true, 'run should call runQuery'); testQueryInput.verify(x => x.runQuery(undefined), TypeMoq.Times.once()); + assert.equal(countCalledShowDialog, 1, 'run should not call showDialog'); done(); }); @@ -442,7 +443,7 @@ suite('SQL QueryAction Tests', () => { isConnected = false; queryAction.run(); - // The conneciton dialog should open with the params set as below + // The connection dialog should open with the params set as below assert.equal(calledShowDialog, 1, 'showDialog should be called when URI is connected'); assert.equal(connectionParams.connectionType, ConnectionType.editor, 'connectionType should be queryEditor'); assert.equal(connectionParams.runQueryOnCompletion, false, 'runQueryOnCompletion should be false`'); diff --git a/src/sqltest/stubs/connectionDialogTestService.ts b/src/sqltest/stubs/connectionDialogTestService.ts index 494247e65c..4c8e26a670 100644 --- a/src/sqltest/stubs/connectionDialogTestService.ts +++ b/src/sqltest/stubs/connectionDialogTestService.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 'use strict'; - import { INewConnectionParams, IConnectionResult, IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService'; @@ -11,15 +10,13 @@ import { IConnectionDialogService } from 'sql/workbench/services/connection/comm export class ConnectionDialogTestService implements IConnectionDialogService { _serviceBrand: any; - public showDialog( - connectionManagementService: IConnectionManagementService, + public showDialog(connectionManagementService: IConnectionManagementService, params: INewConnectionParams, model: IConnectionProfile, connectionResult?: IConnectionResult): Promise { let none: void; return Promise.resolve(none); } - public openDialogAndWait( - connectionManagementService: IConnectionManagementService, + public openDialogAndWait(connectionManagementService: IConnectionManagementService, params?: INewConnectionParams, model?: IConnectionProfile, connectionResult?: IConnectionResult): Promise { return Promise.resolve(undefined); }