From cbb44efb2bb8f7634feefa52e6f7e2b35819ef42 Mon Sep 17 00:00:00 2001 From: James Muehlner Date: Sat, 4 Feb 2023 02:05:17 +0000 Subject: [PATCH] GUACAMOLE-926: Translate to API patches in patch service. --- .../import/services/connectionParseService.js | 146 +++++++++++++++--- .../main/frontend/src/translations/en.json | 5 +- 2 files changed, 124 insertions(+), 27 deletions(-) diff --git a/guacamole/src/main/frontend/src/app/import/services/connectionParseService.js b/guacamole/src/main/frontend/src/app/import/services/connectionParseService.js index 82b7e0c6b..322e22c15 100644 --- a/guacamole/src/main/frontend/src/app/import/services/connectionParseService.js +++ b/guacamole/src/main/frontend/src/app/import/services/connectionParseService.js @@ -31,6 +31,7 @@ angular.module('import').factory('connectionParseService', // Required types const Connection = $injector.get('Connection'); + const DirectoryPatch = $injector.get('DirectoryPatch'); const ParseError = $injector.get('ParseError'); const TranslatableMessage = $injector.get('TranslatableMessage'); @@ -124,6 +125,62 @@ angular.module('import').factory('connectionParseService', return deferredGroupLookups.promise; } + /** + * Returns a promise that will resolve to a transformer function that will + * take an object that may a "group" field, replacing it if present with a + * "parentIdentifier". If both a "group" and "parentIdentifier" field are + * present on the provided object, or if no group exists at the specified + * path, the function will throw a ParseError describing the failure. + * + * @returns {Promise.>} + * A promise that will resolve to a function that will transform a + * "group" field into a "parentIdentifier" field if possible. + */ + function getGroupTransformer() { + return getGroupLookups().then(lookups => connection => { + + // If there's no group to translate, do nothing + if (!connection.group) + return; + + // If both are specified, the parent group is ambigious + if (connection.parentIdentifier) + throw new ParseError({ + message: 'Only one of group or parentIdentifier can be set', + key: 'CONNECTION_IMPORT.ERROR_AMBIGUOUS_PARENT_GROUP' + }); + + // Look up the parent identifier for the specified group path + const identifier = lookups[connection.group]; + + // If the group doesn't match anything in the tree + if (!identifier) + throw new ParseError({ + message: 'No group found named: ' + connection.group, + key: 'CONNECTION_IMPORT.ERROR_INVALID_GROUP', + variables: { GROUP: connection.group } + }); + + // Set the parent identifier now that it's known + return { + ...connection, + parentIdentifier: identifier + }; + + }); + } + + // Translate a given javascript object to a full-fledged Connection + const connectionTransformer = connection => new Connection(connection); + + // Translate a Connection object to a patch requesting the creation of said + // Connection + const patchTransformer = connection => new DirectoryPatch({ + op: 'add', + path: '/', + value: connection + }); + /** * Convert a provided CSV representation of a connection list into a JSON * string to be submitted to the PATCH REST endpoint. The returned JSON @@ -172,14 +229,29 @@ angular.module('import').factory('connectionParseService', // The header row - an array of string header values const header = parsedData[0]; - - return connectionCSVService.getCSVTransformer(header).then( - - // If the transformer was successfully generated, apply it to the - // data rows - // TODO: Also apply the group -> parentIdentifier transform - csvTransformer => connectionData.map(csvTransformer) - ); + + return $q.all({ + csvTransformer : connectionCSVService.getCSVTransformer(header), + groupTransformer : getGroupTransformer() + }) + + // Transform the rows from the CSV file to an array of API patches + .then(({csvTransformer, groupTransformer}) => connectionData.map( + dataRow => { + + // Translate the raw CSV data to a javascript object + let connectionObject = csvTransformer(dataRow); + + // Translate the group on the object to a parentIdentifier + connectionObject = groupTransformer(connectionObject); + + // Translate to a full-fledged Connection + const connection = connectionTransformer(connectionObject); + + // Finally, translate to a patch for creating the connection + return patchTransformer(connection); + + })); }; @@ -203,14 +275,26 @@ angular.module('import').factory('connectionParseService', // Check that the data is the correct format, and not empty const checkError = performBasicChecks(connectionData); - if (checkError) - return $q.defer().reject(checkError); - - // Convert to an array of Connection objects and return - const deferredConnections = $q.defer(); - deferredConnections.resolve( - parsedData.map(connection => new Connection(connection))); - return deferredConnections.promise; + if (checkError) { + const deferred = $q.defer(); + deferred.reject(checkError); + return deferred.promise; + } + + // Transform the data from the YAML file to an array of API patches + return getGroupTransformer().then( + groupTransformer => parsedData.map(connectionObject => { + + // Translate the group on the object to a parentIdentifier + connectionObject = groupTransformer(connectionObject); + + // Translate to a full-fledged Connection + const connection = connectionTransformer(connectionObject); + + // Finally, translate to a patch for creating the connection + return patchTransformer(connection); + + })); }; @@ -231,17 +315,29 @@ angular.module('import').factory('connectionParseService', // Parse from JSON into a javascript array const parsedData = JSON.parse(yamlData); - + // Check that the data is the correct format, and not empty const checkError = performBasicChecks(connectionData); - if (checkError) - return $q.defer().reject(checkError); - - // Convert to an array of Connection objects and return - const deferredConnections = $q.defer(); - deferredConnections.resolve( - parsedData.map(connection => new Connection(connection))); - return deferredConnections.promise; + if (checkError) { + const deferred = $q.defer(); + deferred.reject(checkError); + return deferred.promise; + } + + // Transform the data from the YAML file to an array of API patches + return getGroupTransformer().then( + groupTransformer => parsedData.map(connectionObject => { + + // Translate the group on the object to a parentIdentifier + connectionObject = groupTransformer(connectionObject); + + // Translate to a full-fledged Connection + const connection = connectionTransformer(connectionObject); + + // Finally, translate to a patch for creating the connection + return patchTransformer(connection); + + })); }; diff --git a/guacamole/src/main/frontend/src/translations/en.json b/guacamole/src/main/frontend/src/translations/en.json index 89647a491..98b983b9f 100644 --- a/guacamole/src/main/frontend/src/translations/en.json +++ b/guacamole/src/main/frontend/src/translations/en.json @@ -197,11 +197,12 @@ "ERROR_EMPTY_FILE": "The provided file is empty", "ERROR_INVALID_CSV_HEADER": "Invalid CSV Header \"{HEADER}\" is neither an attribute or parameter", + "ERROR_INVALID_GROUP": "No group matching \"{GROUP}\" found", "ERROR_INVALID_FILE_TYPE": "Invalid import file type \"{TYPE}\"", "ERROR_NO_FILE_SUPPLIED": "Please select a file to import", - "ERROR_REQUIRED_GROUP": - "Either group or parentIdentifier must be specified, but not both", + "ERROR_AMBIGUOUS_PARENT_GROUP": + "Both group and parentIdentifier may be not specified at the same time", "ERROR_REQUIRED_PROTOCOL": "No connection protocol found in the provided file", "ERROR_REQUIRED_NAME":