diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contenttype/ContentTypeForm.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contenttype/ContentTypeForm.java index be90eeacf495..db876286a19a 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contenttype/ContentTypeForm.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contenttype/ContentTypeForm.java @@ -20,7 +20,6 @@ import io.vavr.Tuple2; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -80,8 +79,8 @@ public ContentTypeForm buildForm(final String json) { final List typesToSave = new JsonContentTypeTransformer(json).asList(); final List> workflows = getWorkflowsFromJson(json); - final List>> systemActionWorkflowActionIds = systemActionWorkflowActionIdMapFromJson( - json); + final List>> systemActionWorkflowActionIds = + systemActionWorkflowActionIdMapFromJson(json); final List entries = getContentTypeFormEntries(typesToSave, workflows, systemActionWorkflowActionIds); @@ -95,21 +94,25 @@ private List>> systemActionWorkflowActionIdMap try { - final JSONArray jsonArray = new JSONArray(json); + for (Object jsonObject : new JSONArray(json)) { - for (int i = 0; i < jsonArray.size(); i++) { - final JSONObject fieldJsonObject = (JSONObject) jsonArray.get(i); - systemActionWorkflowActionIdMapList.add(this.getSystemActionsWorkflowActionIds(fieldJsonObject)); + final JSONObject fieldJsonObject = (JSONObject) jsonObject; + systemActionWorkflowActionIdMapList.add( + getSystemActionsWorkflowActionIds(fieldJsonObject) + ); } } catch (JSONException e) { try { - final JSONObject fieldJsonObject = new JSONObject(json); - systemActionWorkflowActionIdMapList.add(this.getSystemActionsWorkflowActionIds(fieldJsonObject)); + final JSONObject fieldJsonObject = new JSONObject(json); + systemActionWorkflowActionIdMapList.add( + getSystemActionsWorkflowActionIds(fieldJsonObject) + ); } catch (JSONException e1) { throw new DotRuntimeException(e1); } } + return systemActionWorkflowActionIdMapList; } @@ -123,11 +126,11 @@ private List getContentTypeFormEntries( for (int i = 0; i < workflows.size(); i++) { final List contentTypeWorkflows = workflows.get(i); final ContentType contentType = typesToSave.get(i); - final List> systemActionWorkflowActions = - i < systemActionWorkflowActionIds.size()?systemActionWorkflowActionIds.get(i):Collections.emptyList(); + final var systemActionWorkflowActions = systemActionWorkflowActionIds.get(i); - final ContentTypeFormEntry entry = new ContentTypeFormEntry(contentType, - contentTypeWorkflows, systemActionWorkflowActions); + final ContentTypeFormEntry entry = new ContentTypeFormEntry( + contentType, contentTypeWorkflows, systemActionWorkflowActions + ); entries.add(entry); } return entries; @@ -249,12 +252,15 @@ private static WorkflowFormEntry mapToWorkflowFormEntry(final JSONObject workflo private static List> getSystemActionsWorkflowActionIds( final JSONObject fieldJsonObject) throws JSONException { - SystemAction systemAction = null; - String workflowActionId = null; - final List> tuple2List = new ArrayList<>(); + List> tuple2List = null; if (fieldJsonObject.has(SYSTEM_ACTION_ATTRIBUTE_NAME)) { + SystemAction systemAction; + String workflowActionId; + + tuple2List = new ArrayList<>(); + final JSONObject systemActionWorkflowActionIdJSONObject = (JSONObject) fieldJsonObject.get(SYSTEM_ACTION_ATTRIBUTE_NAME); final Iterator keys = systemActionWorkflowActionIdJSONObject.keys(); @@ -269,6 +275,7 @@ private static List> getSystemActionsWorkflowAction return tuple2List; } + } public static class ContentTypeFormEntry { diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contenttype/ContentTypeHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contenttype/ContentTypeHelper.java index 0c9c985d9822..ee7729d6ca29 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contenttype/ContentTypeHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contenttype/ContentTypeHelper.java @@ -16,18 +16,24 @@ import com.dotcms.enterprise.LicenseUtil; import com.dotcms.enterprise.license.LicenseLevel; import com.dotcms.repackage.com.google.common.annotations.VisibleForTesting; +import com.dotcms.workflow.form.WorkflowSystemActionForm; +import com.dotcms.workflow.helper.WorkflowHelper; import com.dotmarketing.business.APILocator; import com.dotmarketing.exception.DoesNotExistException; import com.dotmarketing.exception.DotDataException; import com.dotmarketing.exception.DotSecurityException; import com.dotmarketing.portlets.workflows.business.DotWorkflowException; import com.dotmarketing.portlets.workflows.business.WorkflowAPI; +import com.dotmarketing.portlets.workflows.business.WorkflowAPI.SystemAction; +import com.dotmarketing.portlets.workflows.model.SystemActionWorkflowActionMapping; import com.dotmarketing.portlets.workflows.model.WorkflowScheme; import com.dotmarketing.util.Logger; +import com.dotmarketing.util.UtilMethods; import com.liferay.portal.language.LanguageException; import com.liferay.portal.language.LanguageUtil; import com.liferay.portal.model.User; import com.liferay.util.LocaleUtil; +import io.vavr.Tuple2; import java.io.Serializable; import java.net.URISyntaxException; import java.util.Comparator; @@ -121,6 +127,169 @@ public ContentType evaluateContentTypeRequest(final String idOrVarParameter, return sortAndFixContentTypeFields(contentType, updatedContentTypeBuilder); } + /** + * This method processes the workflow action mappings for a given content type. It adds or + * updates the mappings based on the formSystemActionMappings parameter. If isNew is false, it + * also checks for mappings that need to be deleted. + * + * @param contentType The content type for which the mappings need to be + * processed. + * @param user The user performing the action. + * @param formSystemActionMappings The list of system action mappings to be added or updated. + * @param isNew A flag indicating whether the content type is new or not. + * @return The updated version of the action mappings for the given content type. + * @throws DotDataException If there is an issue with the data storage. + * @throws DotSecurityException If there is a security violation. + */ + @WrapInTransaction + public List processWorkflowActionMapping( + final ContentType contentType, final User user, + final List> formSystemActionMappings, final boolean isNew) + throws DotDataException, DotSecurityException { + + // If not mapping was sent at all by the user, we should ignore this processing, + // nothing to do. + if (null != formSystemActionMappings) { + + // Add/update the given mappings + saveSystemActions(formSystemActionMappings, contentType, user); + + // Compare to identify what needs to be deleted + if (!isNew) { + deleteSystemActionsIfNecessary(formSystemActionMappings, contentType, user); + } + } + + // Regarding what we do, we need to return the updated version of the action mappings + return WorkflowHelper.getInstance().findSystemActionsByContentType(contentType, user); + } + + /** + * Saves the system actions for a given list of system action mappings. + * + * @param formSystemActionMappings a list of tuples containing system actions and workflow + * action IDs + * @param contentType the content type to save the system actions for + * @param user the user performing the action + * @throws DotDataException if there is an error accessing the data + * @throws DotSecurityException if the user does not have permission to perform the action + */ + private void saveSystemActions( + final List> formSystemActionMappings, + final ContentType contentType, final User user) + throws DotDataException, DotSecurityException { + + final WorkflowHelper workflowHelper = WorkflowHelper.getInstance(); + + // Now we add/update the given mappings + for (final Tuple2 tuple2 : formSystemActionMappings) { + + final WorkflowAPI.SystemAction systemAction = tuple2._1; + final String workflowActionId = tuple2._2; + if (UtilMethods.isSet(workflowActionId)) { + + Logger.warn(this, "Saving the system action: " + systemAction + + ", for content type: " + contentType.variable() + + ", with the workflow action: " + + workflowActionId); + + workflowHelper.mapSystemActionToWorkflowAction( + new WorkflowSystemActionForm.Builder(). + systemAction(systemAction). + actionId(workflowActionId). + contentTypeVariable(contentType.variable()).build(), + user + ); + } + } + } + + /** + * Deletes system actions if necessary, comparing given system actions with the existing ones. + * + * @param formSystemActionMappings a list of tuple containing system actions and their + * respective IDs + * @param contentType the content type + * @param user the user performing the operation + * @throws DotDataException if there is an error accessing the data + * @throws DotSecurityException if there is an error with security permissions + */ + private void deleteSystemActionsIfNecessary( + final List> formSystemActionMappings, + final ContentType contentType, final User user) + throws DotDataException, DotSecurityException { + + final WorkflowHelper workflowHelper = WorkflowHelper.getInstance(); + + // Handle a special case where having the system action name without an ID implies we + // want to delete the mapping. + for (final Tuple2 tuple2 : formSystemActionMappings) { + + final WorkflowAPI.SystemAction systemAction = tuple2._1; + final String workflowActionId = tuple2._2; + + if (UtilMethods.isNotSet(workflowActionId) && UtilMethods.isSet(systemAction)) { + deleteSystemAction( + systemAction, contentType, user + ); + } + } + + // Getting the existing mappings + final var existingSystemActionMappings = workflowHelper.findSystemActionsByContentType( + contentType, user + ); + + // Comparing existing mappings with the mappings in the form to decide whether + // they should be deleted. + for (final var existingSystemActionMapping : existingSystemActionMappings) { + + var found = false; + + for (final Tuple2 formMappingData : formSystemActionMappings) { + + if (existingSystemActionMapping.getSystemAction().name(). + equalsIgnoreCase(formMappingData._1().name())) { + found = true; + break; + } + } + + if (!found) { + + // Was not found in the user request, needs to be deleted. + deleteSystemAction( + existingSystemActionMapping.getSystemAction(), contentType, user + ); + } + + } + } + + /** + * Deletes a system action for a specific content type and user. + * + * @param systemAction The system action to be deleted. + * @param contentType The content type associated with the system action. + * @param user The user performing the deletion. + * @throws DotDataException if there is an issue with the data layer. + * @throws DotSecurityException if there is a security violation. + */ + private void deleteSystemAction(final SystemAction systemAction, final ContentType contentType, + final User user) throws DotDataException, DotSecurityException { + + final WorkflowHelper workflowHelper = WorkflowHelper.getInstance(); + + Logger.warn(this, "Deleting the system action: " + systemAction + + ", for content type: " + contentType.variable()); + + final var mappingDeleted = workflowHelper.deleteSystemAction( + systemAction, contentType, user + ); + + Logger.warn(this, "Deleted the system action mapping: " + mappingDeleted); + } + /** * Saves the associated schemes for the provided content type. * diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contenttype/ContentTypeResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contenttype/ContentTypeResource.java index c38f73e9eeca..623f46c44c85 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contenttype/ContentTypeResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contenttype/ContentTypeResource.java @@ -31,7 +31,6 @@ import com.dotcms.util.diff.DiffResult; import com.dotcms.util.pagination.ContentTypesPaginator; import com.dotcms.util.pagination.OrderDirection; -import com.dotcms.workflow.form.WorkflowSystemActionForm; import com.dotcms.workflow.helper.WorkflowHelper; import com.dotmarketing.beans.Host; import com.dotmarketing.business.APILocator; @@ -776,7 +775,6 @@ private Tuple2> saveContent final ContentTypeAPI contentTypeAPI, final boolean isNew) throws DotSecurityException, DotDataException { - final List systemActionWorkflowActionMappings = new ArrayList<>(); ContentType contentTypeSaved = contentTypeAPI.save(contentType); this.contentTypeHelper.saveSchemesByContentType(contentTypeSaved, workflows); @@ -791,38 +789,11 @@ private Tuple2> saveContent contentTypeSaved.id(), user ); - if (UtilMethods.isSet(systemActionMappings)) { - - for (final Tuple2 tuple2 : systemActionMappings) { - - final WorkflowAPI.SystemAction systemAction = tuple2._1; - final String workflowActionId = tuple2._2; - if (UtilMethods.isSet(workflowActionId)) { - - Logger.warn(this, "Saving the system action: " + systemAction + - ", for content type: " + contentTypeSaved.variable() + ", with the workflow action: " - + workflowActionId ); - - systemActionWorkflowActionMappings.add(this.workflowHelper.mapSystemActionToWorkflowAction(new WorkflowSystemActionForm.Builder() - .systemAction(systemAction).actionId(workflowActionId) - .contentTypeVariable(contentTypeSaved.variable()).build(), user)); - } else if (UtilMethods.isSet(systemAction)) { - - if (!isNew) { - Logger.warn(this, "Deleting the system action: " + systemAction + - ", for content type: " + contentTypeSaved.variable()); - - final SystemActionWorkflowActionMapping mappingDeleted = - this.workflowHelper.deleteSystemAction(systemAction, contentTypeSaved, user); - - Logger.warn(this, "Deleted the system action mapping: " + mappingDeleted); - } - } else { - - throw new IllegalArgumentException("On System Action Mappings, a system action has been sent null or empty"); - } - } - } + // Processing the content type action mappings + final List systemActionWorkflowActionMappings = + this.contentTypeHelper.processWorkflowActionMapping( + contentTypeSaved, user, systemActionMappings, isNew + ); return Tuple.of(contentTypeSaved, systemActionWorkflowActionMappings); } diff --git a/dotcms-postman/src/main/resources/postman/ContentTypeResourceTests.json b/dotcms-postman/src/main/resources/postman/ContentTypeResourceTests.json index bfaf6a87d243..999a261cdaa7 100644 --- a/dotcms-postman/src/main/resources/postman/ContentTypeResourceTests.json +++ b/dotcms-postman/src/main/resources/postman/ContentTypeResourceTests.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "e3f7129d-a7cc-4b52-a5a8-8098344aa0d9", + "_postman_id": "c2e4152e-4233-43ae-8e7c-c2e682ade9f8", "name": "ContentType Resource", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", "_exporter_id": "31066048" @@ -12349,6 +12349,1072 @@ } } ] + }, + { + "name": "System Action Mappings", + "item": [ + { + "name": "Create", + "item": [ + { + "name": "Create ContentType without systemActionMappings", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code should be ok 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Storing content type id\", function () {", + " var jsonData = pm.response.json();", + " pm.collectionVariables.set(\"contentTypeId\", jsonData.entity[0].id);", + " pm.collectionVariables.set(\"contentTypeVariable\", jsonData.entity[0].variable);", + "});", + "", + "pm.test(\"Validate systemActionMappings is empty\", function () {", + " var systemActionMappings = pm.response.json().entity[0].systemActionMappings;", + " pm.expect(systemActionMappings).to.be.an('object').that.is.empty;", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"clazz\": \"com.dotcms.contenttype.model.type.SimpleContentType\",\n\t\"description\": \"Test Content Type\",\n\t\"defaultType\": false,\n\t\"system\": false,\n\t\"folder\": \"SYSTEM_FOLDER\",\n\t\"name\": \"Test Content Type\",\n\t\"variable\": \"testContentType1{{$timestamp}}\",\n\t\"host\": \"SYSTEM_HOST\",\n\t\"fixed\": false,\n \"icon\": \"inventory\",\n\t\"fields\": [\n\t\t{\n\t\t\t\"clazz\": \"com.dotcms.contenttype.model.field.TextField\",\n\t\t\t\"indexed\": true,\n\t\t\t\"dataType\": \"TEXT\",\n\t\t\t\"readOnly\": false,\n\t\t\t\"required\": true,\n\t\t\t\"searchable\": true,\n\t\t\t\"listed\": true,\n\t\t\t\"unique\": false,\n\t\t\t\"name\": \"Name\",\n\t\t\t\"variable\": \"name\",\n\t\t\t\"fixed\": false\n\t\t}\n\t],\n \"workflow\":[\"d61a59e1-a49c-46f2-a929-db2b4bfa88b2\"]\n}" + }, + "url": { + "raw": "{{serverURL}}/api/v1/contenttype", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "contenttype" + ] + }, + "description": "Test to validate the content type is created without `systemActionMappings`." + }, + "response": [] + }, + { + "name": "Get Content Type", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();", + "", + "pm.test(\"Status code should be ok 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Validate systemActionMappings is empty\", function () {", + " var systemActionMappings = pm.response.json().entity.systemActionMappings;", + " pm.expect(systemActionMappings).to.be.an('object').that.is.empty;", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{serverURL}}/api/v1/contenttype/id/{{contentTypeId}}", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "contenttype", + "id", + "{{contentTypeId}}" + ] + }, + "description": "Validates the content type and `systemActionMappings` were created as expected." + }, + "response": [] + }, + { + "name": "Delete ContentType", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code should be ok 200\", function () {", + " pm.response.to.have.status(200);", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{serverURL}}/api/v1/contenttype/id/{{contentTypeId}}", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "contenttype", + "id", + "{{contentTypeId}}" + ] + }, + "description": "Cleanup" + }, + "response": [] + }, + { + "name": "Create ContentType with empty systemActionMappings", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code should be ok 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Storing content type id\", function () {", + " var jsonData = pm.response.json();", + " pm.collectionVariables.set(\"contentTypeId\", jsonData.entity[0].id);", + " pm.collectionVariables.set(\"contentTypeVariable\", jsonData.entity[0].variable);", + "});", + "", + "pm.test(\"Validate systemActionMappings is empty\", function () {", + " var systemActionMappings = pm.response.json().entity[0].systemActionMappings;", + " pm.expect(systemActionMappings).to.be.an('object').that.is.empty;", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"clazz\": \"com.dotcms.contenttype.model.type.SimpleContentType\",\n\t\"description\": \"Test Content Type\",\n\t\"defaultType\": false,\n\t\"system\": false,\n\t\"folder\": \"SYSTEM_FOLDER\",\n\t\"name\": \"Test Content Type\",\n\t\"variable\": \"testContentType2{{$timestamp}}\",\n\t\"host\": \"SYSTEM_HOST\",\n\t\"fixed\": false,\n \"icon\": \"inventory\",\n\t\"fields\": [\n\t\t{\n\t\t\t\"clazz\": \"com.dotcms.contenttype.model.field.TextField\",\n\t\t\t\"indexed\": true,\n\t\t\t\"dataType\": \"TEXT\",\n\t\t\t\"readOnly\": false,\n\t\t\t\"required\": true,\n\t\t\t\"searchable\": true,\n\t\t\t\"listed\": true,\n\t\t\t\"unique\": false,\n\t\t\t\"name\": \"Name\",\n\t\t\t\"variable\": \"name\",\n\t\t\t\"fixed\": false\n\t\t}\n\t],\n \"systemActionMappings\": {},\n \"workflow\":[\"d61a59e1-a49c-46f2-a929-db2b4bfa88b2\"]\n}" + }, + "url": { + "raw": "{{serverURL}}/api/v1/contenttype", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "contenttype" + ] + }, + "description": "Test to validate the content type is created with empty `systemActionMappings`." + }, + "response": [] + }, + { + "name": "Get Content Type", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();", + "", + "pm.test(\"Status code should be ok 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Validate systemActionMappings is empty\", function () {", + " var systemActionMappings = pm.response.json().entity.systemActionMappings;", + " pm.expect(systemActionMappings).to.be.an('object').that.is.empty;", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{serverURL}}/api/v1/contenttype/id/{{contentTypeId}}", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "contenttype", + "id", + "{{contentTypeId}}" + ] + }, + "description": "Validates the content type and `systemActionMappings` were created as expected." + }, + "response": [] + }, + { + "name": "Delete ContentType", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code should be ok 200\", function () {", + " pm.response.to.have.status(200);", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{serverURL}}/api/v1/contenttype/id/{{contentTypeId}}", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "contenttype", + "id", + "{{contentTypeId}}" + ] + }, + "description": "Cleanup" + }, + "response": [] + }, + { + "name": "Create ContentType with systemActionMappings", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code should be ok 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Storing content type id\", function () {", + " var jsonData = pm.response.json();", + " pm.collectionVariables.set(\"contentTypeId\", jsonData.entity[0].id);", + " pm.collectionVariables.set(\"contentTypeVariable\", jsonData.entity[0].variable);", + "});", + "", + "pm.test(\"Validate systemActionMappings exist\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.entity[0]).to.have.property('systemActionMappings');", + "});", + "", + "pm.test(\"Validate systemActionMappings contain 'NEW' and 'ARCHIVE'\", function () {", + " var systemActionMappings = pm.response.json().entity[0].systemActionMappings;", + " pm.expect(systemActionMappings).to.have.property('NEW');", + " pm.expect(systemActionMappings).to.have.property('ARCHIVE');", + "});", + "", + "pm.test(\"Validate workflowAction.id inside 'NEW' and 'ARCHIVE'\", function () {", + " var systemActionMappings = pm.response.json().entity[0].systemActionMappings;", + " ", + " pm.expect(systemActionMappings.NEW.workflowAction.id).to.eql(\"b9d89c80-3d88-4311-8365-187323c96436\");", + " pm.expect(systemActionMappings.ARCHIVE.workflowAction.id).to.eql(\"4da13a42-5d59-480c-ad8f-94a3adf809fe\");", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"clazz\": \"com.dotcms.contenttype.model.type.SimpleContentType\",\n\t\"description\": \"Test Content Type\",\n\t\"defaultType\": false,\n\t\"system\": false,\n\t\"folder\": \"SYSTEM_FOLDER\",\n\t\"name\": \"Test Content Type\",\n\t\"variable\": \"testContentType3{{$timestamp}}\",\n\t\"host\": \"SYSTEM_HOST\",\n\t\"fixed\": false,\n \"icon\": \"inventory\",\n\t\"fields\": [\n\t\t{\n\t\t\t\"clazz\": \"com.dotcms.contenttype.model.field.TextField\",\n\t\t\t\"indexed\": true,\n\t\t\t\"dataType\": \"TEXT\",\n\t\t\t\"readOnly\": false,\n\t\t\t\"required\": true,\n\t\t\t\"searchable\": true,\n\t\t\t\"listed\": true,\n\t\t\t\"unique\": false,\n\t\t\t\"name\": \"Name\",\n\t\t\t\"variable\": \"name\",\n\t\t\t\"fixed\": false\n\t\t}\n\t],\n \"systemActionMappings\" : {\n \"NEW\" : \"b9d89c80-3d88-4311-8365-187323c96436\",\n \"ARCHIVE\": \"4da13a42-5d59-480c-ad8f-94a3adf809fe\"\n },\n \"workflow\":[\"d61a59e1-a49c-46f2-a929-db2b4bfa88b2\"]\n}" + }, + "url": { + "raw": "{{serverURL}}/api/v1/contenttype", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "contenttype" + ] + }, + "description": "Test to validate the content type is created with `systemActionMappings`." + }, + "response": [] + }, + { + "name": "Get Content Type", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();", + "", + "pm.test(\"Status code should be ok 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Validate systemActionMappings exist\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.entity).to.have.property('systemActionMappings');", + "});", + "", + "pm.test(\"Validate systemActionMappings contain 'NEW' and 'ARCHIVE'\", function () {", + " var systemActionMappings = pm.response.json().entity.systemActionMappings;", + " pm.expect(systemActionMappings).to.have.property('NEW');", + " pm.expect(systemActionMappings).to.have.property('ARCHIVE');", + "});", + "", + "pm.test(\"Validate workflowAction.id inside 'NEW' and 'ARCHIVE'\", function () {", + " var systemActionMappings = pm.response.json().entity.systemActionMappings;", + " ", + " pm.expect(systemActionMappings.NEW.workflowAction.id).to.eql(\"b9d89c80-3d88-4311-8365-187323c96436\");", + " pm.expect(systemActionMappings.ARCHIVE.workflowAction.id).to.eql(\"4da13a42-5d59-480c-ad8f-94a3adf809fe\");", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{serverURL}}/api/v1/contenttype/id/{{contentTypeId}}", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "contenttype", + "id", + "{{contentTypeId}}" + ] + }, + "description": "Validates the content type and `systemActionMappings` were created as expected." + }, + "response": [] + }, + { + "name": "Delete ContentType", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code should be ok 200\", function () {", + " pm.response.to.have.status(200);", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{serverURL}}/api/v1/contenttype/id/{{contentTypeId}}", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "contenttype", + "id", + "{{contentTypeId}}" + ] + }, + "description": "Cleanup" + }, + "response": [] + } + ], + "description": "Folder to encapsulate multiple tests to validate the proper creation and handling of `systemActionMappings`." + }, + { + "name": "Update", + "item": [ + { + "name": "Create ContentType", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code should be ok 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Storing content type id\", function () {", + " var jsonData = pm.response.json();", + " pm.collectionVariables.set(\"contentTypeId\", jsonData.entity[0].id);", + " pm.collectionVariables.set(\"contentTypeVariable\", jsonData.entity[0].variable);", + "});", + "", + "pm.test(\"Validate systemActionMappings is empty\", function () {", + " var systemActionMappings = pm.response.json().entity[0].systemActionMappings;", + " pm.expect(systemActionMappings).to.be.an('object').that.is.empty;", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"clazz\": \"com.dotcms.contenttype.model.type.SimpleContentType\",\n\t\"description\": \"Test Content Type\",\n\t\"defaultType\": false,\n\t\"system\": false,\n\t\"folder\": \"SYSTEM_FOLDER\",\n\t\"name\": \"Test Content Type\",\n\t\"variable\": \"testContentType1{{$timestamp}}\",\n\t\"host\": \"SYSTEM_HOST\",\n\t\"fixed\": false,\n \"icon\": \"inventory\",\n\t\"fields\": [\n\t\t{\n\t\t\t\"clazz\": \"com.dotcms.contenttype.model.field.TextField\",\n\t\t\t\"indexed\": true,\n\t\t\t\"dataType\": \"TEXT\",\n\t\t\t\"readOnly\": false,\n\t\t\t\"required\": true,\n\t\t\t\"searchable\": true,\n\t\t\t\"listed\": true,\n\t\t\t\"unique\": false,\n\t\t\t\"name\": \"Name\",\n\t\t\t\"variable\": \"name\",\n\t\t\t\"fixed\": false\n\t\t}\n\t],\n \"workflow\":[\"d61a59e1-a49c-46f2-a929-db2b4bfa88b2\"]\n}" + }, + "url": { + "raw": "{{serverURL}}/api/v1/contenttype", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "contenttype" + ] + }, + "description": "Simple creation to sep up the data for the update tests." + }, + "response": [] + }, + { + "name": "Get Content Type", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();", + "", + "pm.test(\"Status code should be ok 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Validate systemActionMappings is empty\", function () {", + " var systemActionMappings = pm.response.json().entity.systemActionMappings;", + " pm.expect(systemActionMappings).to.be.an('object').that.is.empty;", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{serverURL}}/api/v1/contenttype/id/{{contentTypeId}}", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "contenttype", + "id", + "{{contentTypeId}}" + ] + }, + "description": "Validates the content type and `systemActionMappings` were created as expected." + }, + "response": [] + }, + { + "name": "Update without systemActionMappings", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code should be ok 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Validate systemActionMappings is empty\", function () {", + " var systemActionMappings = pm.response.json().entity.systemActionMappings;", + " pm.expect(systemActionMappings).to.be.an('object').that.is.empty;", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"clazz\": \"com.dotcms.contenttype.model.type.SimpleContentType\",\n \"id\": \"{{contentTypeId}}\",\n \"description\": \"Test Content Type\",\n \"defaultType\": false,\n \"system\": false,\n \"folder\": \"SYSTEM_FOLDER\",\n \"name\": \"Test Content Type\",\n \"variable\": \"{{contentTypeVariable}}\",\n \"host\": \"SYSTEM_HOST\",\n \"fixed\": false,\n \"icon\": \"inventory\",\n \"fields\": [\n {\n \"clazz\": \"com.dotcms.contenttype.model.field.TextField\",\n\t\t\t\"indexed\": true,\n\t\t\t\"dataType\": \"TEXT\",\n\t\t\t\"readOnly\": false,\n\t\t\t\"required\": true,\n\t\t\t\"searchable\": true,\n\t\t\t\"listed\": true,\n\t\t\t\"unique\": false,\n\t\t\t\"name\": \"Name\",\n\t\t\t\"variable\": \"name\",\n\t\t\t\"fixed\": false\n }\n ],\n \"workflow\":[\"d61a59e1-a49c-46f2-a929-db2b4bfa88b2\"]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/contenttype/id/{{contentTypeId}}", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "contenttype", + "id", + "{{contentTypeId}}" + ] + }, + "description": "Updates a content type without `systemActionMappings`." + }, + "response": [] + }, + { + "name": "Update with empty systemActionMappings", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code should be ok 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Validate systemActionMappings is empty\", function () {", + " var systemActionMappings = pm.response.json().entity.systemActionMappings;", + " pm.expect(systemActionMappings).to.be.an('object').that.is.empty;", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"clazz\": \"com.dotcms.contenttype.model.type.SimpleContentType\",\n \"id\": \"{{contentTypeId}}\",\n \"description\": \"Test Content Type\",\n \"defaultType\": false,\n \"system\": false,\n \"folder\": \"SYSTEM_FOLDER\",\n \"name\": \"Test Content Type\",\n \"variable\": \"{{contentTypeVariable}}\",\n \"host\": \"SYSTEM_HOST\",\n \"fixed\": false,\n \"icon\": \"inventory\",\n \"fields\": [\n {\n \"clazz\": \"com.dotcms.contenttype.model.field.TextField\",\n\t\t\t\"indexed\": true,\n\t\t\t\"dataType\": \"TEXT\",\n\t\t\t\"readOnly\": false,\n\t\t\t\"required\": true,\n\t\t\t\"searchable\": true,\n\t\t\t\"listed\": true,\n\t\t\t\"unique\": false,\n\t\t\t\"name\": \"Name\",\n\t\t\t\"variable\": \"name\",\n\t\t\t\"fixed\": false\n }\n ],\n \"systemActionMappings\": {},\n \"workflow\":[\"d61a59e1-a49c-46f2-a929-db2b4bfa88b2\"]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/contenttype/id/{{contentTypeId}}", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "contenttype", + "id", + "{{contentTypeId}}" + ] + }, + "description": "Updates a content type with emtpy `systemActionMappings`." + }, + "response": [] + }, + { + "name": "Update with systemActionMappings", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code should be ok 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Validate systemActionMappings exist\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.entity).to.have.property('systemActionMappings');", + "});", + "", + "pm.test(\"Validate systemActionMappings\", function () {", + " var systemActionMappings = pm.response.json().entity.systemActionMappings;", + " pm.expect(systemActionMappings).to.have.property('NEW');", + " pm.expect(systemActionMappings).to.have.property('ARCHIVE');", + "});", + "", + "pm.test(\"Validate workflowAction.id\", function () {", + " var systemActionMappings = pm.response.json().entity.systemActionMappings;", + " ", + " pm.expect(systemActionMappings.NEW.workflowAction.id).to.eql(\"b9d89c80-3d88-4311-8365-187323c96436\");", + " pm.expect(systemActionMappings.ARCHIVE.workflowAction.id).to.eql(\"4da13a42-5d59-480c-ad8f-94a3adf809fe\");", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"clazz\": \"com.dotcms.contenttype.model.type.SimpleContentType\",\n \"id\": \"{{contentTypeId}}\",\n \"description\": \"Test Content Type\",\n \"defaultType\": false,\n \"system\": false,\n \"folder\": \"SYSTEM_FOLDER\",\n \"name\": \"Test Content Type\",\n \"variable\": \"{{contentTypeVariable}}\",\n \"host\": \"SYSTEM_HOST\",\n \"fixed\": false,\n \"icon\": \"inventory\",\n \"fields\": [\n {\n \"clazz\": \"com.dotcms.contenttype.model.field.TextField\",\n\t\t\t\"indexed\": true,\n\t\t\t\"dataType\": \"TEXT\",\n\t\t\t\"readOnly\": false,\n\t\t\t\"required\": true,\n\t\t\t\"searchable\": true,\n\t\t\t\"listed\": true,\n\t\t\t\"unique\": false,\n\t\t\t\"name\": \"Name\",\n\t\t\t\"variable\": \"name\",\n\t\t\t\"fixed\": false\n }\n ],\n \"systemActionMappings\" : {\n \"NEW\" : \"b9d89c80-3d88-4311-8365-187323c96436\",\n \"ARCHIVE\": \"4da13a42-5d59-480c-ad8f-94a3adf809fe\"\n },\n \"workflow\":[\"d61a59e1-a49c-46f2-a929-db2b4bfa88b2\"]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/contenttype/id/{{contentTypeId}}", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "contenttype", + "id", + "{{contentTypeId}}" + ] + }, + "description": "Updates a content type with `systemActionMappings`." + }, + "response": [] + }, + { + "name": "Update without systemActionMappings again", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code should be ok 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Validate systemActionMappings exist\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.entity).to.have.property('systemActionMappings');", + "});", + "", + "pm.test(\"Validate systemActionMappings\", function () {", + " var systemActionMappings = pm.response.json().entity.systemActionMappings;", + " pm.expect(systemActionMappings).to.have.property('NEW');", + " pm.expect(systemActionMappings).to.have.property('ARCHIVE');", + "});", + "", + "pm.test(\"Validate workflowAction.id\", function () {", + " var systemActionMappings = pm.response.json().entity.systemActionMappings;", + " ", + " pm.expect(systemActionMappings.NEW.workflowAction.id).to.eql(\"b9d89c80-3d88-4311-8365-187323c96436\");", + " pm.expect(systemActionMappings.ARCHIVE.workflowAction.id).to.eql(\"4da13a42-5d59-480c-ad8f-94a3adf809fe\");", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"clazz\": \"com.dotcms.contenttype.model.type.SimpleContentType\",\n \"id\": \"{{contentTypeId}}\",\n \"description\": \"Test Content Type\",\n \"defaultType\": false,\n \"system\": false,\n \"folder\": \"SYSTEM_FOLDER\",\n \"name\": \"Test Content Type\",\n \"variable\": \"{{contentTypeVariable}}\",\n \"host\": \"SYSTEM_HOST\",\n \"fixed\": false,\n \"icon\": \"inventory\",\n \"fields\": [\n {\n \"clazz\": \"com.dotcms.contenttype.model.field.TextField\",\n\t\t\t\"indexed\": true,\n\t\t\t\"dataType\": \"TEXT\",\n\t\t\t\"readOnly\": false,\n\t\t\t\"required\": true,\n\t\t\t\"searchable\": true,\n\t\t\t\"listed\": true,\n\t\t\t\"unique\": false,\n\t\t\t\"name\": \"Name\",\n\t\t\t\"variable\": \"name\",\n\t\t\t\"fixed\": false\n }\n ],\n \"workflow\":[\"d61a59e1-a49c-46f2-a929-db2b4bfa88b2\"]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/contenttype/id/{{contentTypeId}}", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "contenttype", + "id", + "{{contentTypeId}}" + ] + }, + "description": "Updates a content type without `systemActionMappings` existing mappings should be unaffected." + }, + "response": [] + }, + { + "name": "Update with empty systemActionMappings again", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code should be ok 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Validate systemActionMappings is empty\", function () {", + " var systemActionMappings = pm.response.json().entity.systemActionMappings;", + " pm.expect(systemActionMappings).to.be.an('object').that.is.empty;", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"clazz\": \"com.dotcms.contenttype.model.type.SimpleContentType\",\n \"id\": \"{{contentTypeId}}\",\n \"description\": \"Test Content Type\",\n \"defaultType\": false,\n \"system\": false,\n \"folder\": \"SYSTEM_FOLDER\",\n \"name\": \"Test Content Type\",\n \"variable\": \"{{contentTypeVariable}}\",\n \"host\": \"SYSTEM_HOST\",\n \"fixed\": false,\n \"icon\": \"inventory\",\n \"fields\": [\n {\n \"clazz\": \"com.dotcms.contenttype.model.field.TextField\",\n\t\t\t\"indexed\": true,\n\t\t\t\"dataType\": \"TEXT\",\n\t\t\t\"readOnly\": false,\n\t\t\t\"required\": true,\n\t\t\t\"searchable\": true,\n\t\t\t\"listed\": true,\n\t\t\t\"unique\": false,\n\t\t\t\"name\": \"Name\",\n\t\t\t\"variable\": \"name\",\n\t\t\t\"fixed\": false\n }\n ],\n \"systemActionMappings\": {},\n \"workflow\":[\"d61a59e1-a49c-46f2-a929-db2b4bfa88b2\"]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/contenttype/id/{{contentTypeId}}", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "contenttype", + "id", + "{{contentTypeId}}" + ] + }, + "description": "Updates a content type with empty `systemActionMappings` removing all existing mappings." + }, + "response": [] + }, + { + "name": "Update with systemActionMappings adding mappings again", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code should be ok 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Validate systemActionMappings exist\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.entity).to.have.property('systemActionMappings');", + "});", + "", + "pm.test(\"Validate systemActionMappings\", function () {", + " var systemActionMappings = pm.response.json().entity.systemActionMappings;", + " pm.expect(systemActionMappings).to.have.property('NEW');", + " pm.expect(systemActionMappings).to.have.property('ARCHIVE');", + "});", + "", + "pm.test(\"Validate workflowAction.id\", function () {", + " var systemActionMappings = pm.response.json().entity.systemActionMappings;", + " ", + " pm.expect(systemActionMappings.NEW.workflowAction.id).to.eql(\"b9d89c80-3d88-4311-8365-187323c96436\");", + " pm.expect(systemActionMappings.ARCHIVE.workflowAction.id).to.eql(\"4da13a42-5d59-480c-ad8f-94a3adf809fe\");", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"clazz\": \"com.dotcms.contenttype.model.type.SimpleContentType\",\n \"id\": \"{{contentTypeId}}\",\n \"description\": \"Test Content Type\",\n \"defaultType\": false,\n \"system\": false,\n \"folder\": \"SYSTEM_FOLDER\",\n \"name\": \"Test Content Type\",\n \"variable\": \"{{contentTypeVariable}}\",\n \"host\": \"SYSTEM_HOST\",\n \"fixed\": false,\n \"icon\": \"inventory\",\n \"fields\": [\n {\n \"clazz\": \"com.dotcms.contenttype.model.field.TextField\",\n\t\t\t\"indexed\": true,\n\t\t\t\"dataType\": \"TEXT\",\n\t\t\t\"readOnly\": false,\n\t\t\t\"required\": true,\n\t\t\t\"searchable\": true,\n\t\t\t\"listed\": true,\n\t\t\t\"unique\": false,\n\t\t\t\"name\": \"Name\",\n\t\t\t\"variable\": \"name\",\n\t\t\t\"fixed\": false\n }\n ],\n \"systemActionMappings\" : {\n \"NEW\" : \"b9d89c80-3d88-4311-8365-187323c96436\",\n \"ARCHIVE\": \"4da13a42-5d59-480c-ad8f-94a3adf809fe\"\n },\n \"workflow\":[\"d61a59e1-a49c-46f2-a929-db2b4bfa88b2\"]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/contenttype/id/{{contentTypeId}}", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "contenttype", + "id", + "{{contentTypeId}}" + ] + }, + "description": "Updates a content type with `systemActionMappings` adding mappings again as previous test deleted existing mappings." + }, + "response": [] + }, + { + "name": "Update with systemActionMappings adding more mappings", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code should be ok 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Validate systemActionMappings exist\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.entity).to.have.property('systemActionMappings');", + "});", + "", + "pm.test(\"Validate systemActionMappings\", function () {", + " var systemActionMappings = pm.response.json().entity.systemActionMappings;", + " pm.expect(systemActionMappings).to.have.property('NEW');", + " pm.expect(systemActionMappings).to.have.property('ARCHIVE');", + " pm.expect(systemActionMappings).to.have.property('PUBLISH');", + "});", + "", + "pm.test(\"Validate workflowAction.id\", function () {", + " var systemActionMappings = pm.response.json().entity.systemActionMappings;", + " ", + " pm.expect(systemActionMappings.NEW.workflowAction.id).to.eql(\"b9d89c80-3d88-4311-8365-187323c96436\");", + " pm.expect(systemActionMappings.ARCHIVE.workflowAction.id).to.eql(\"4da13a42-5d59-480c-ad8f-94a3adf809fe\");", + " pm.expect(systemActionMappings.PUBLISH.workflowAction.id).to.eql(\"b9d89c80-3d88-4311-8365-187323c96436\");", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"clazz\": \"com.dotcms.contenttype.model.type.SimpleContentType\",\n \"id\": \"{{contentTypeId}}\",\n \"description\": \"Test Content Type\",\n \"defaultType\": false,\n \"system\": false,\n \"folder\": \"SYSTEM_FOLDER\",\n \"name\": \"Test Content Type\",\n \"variable\": \"{{contentTypeVariable}}\",\n \"host\": \"SYSTEM_HOST\",\n \"fixed\": false,\n \"icon\": \"inventory\",\n \"fields\": [\n {\n \"clazz\": \"com.dotcms.contenttype.model.field.TextField\",\n\t\t\t\"indexed\": true,\n\t\t\t\"dataType\": \"TEXT\",\n\t\t\t\"readOnly\": false,\n\t\t\t\"required\": true,\n\t\t\t\"searchable\": true,\n\t\t\t\"listed\": true,\n\t\t\t\"unique\": false,\n\t\t\t\"name\": \"Name\",\n\t\t\t\"variable\": \"name\",\n\t\t\t\"fixed\": false\n }\n ],\n \"systemActionMappings\" : {\n \"NEW\" : \"b9d89c80-3d88-4311-8365-187323c96436\",\n \"ARCHIVE\": \"4da13a42-5d59-480c-ad8f-94a3adf809fe\",\n \"PUBLISH\": \"b9d89c80-3d88-4311-8365-187323c96436\"\n },\n \"workflow\":[\"d61a59e1-a49c-46f2-a929-db2b4bfa88b2\"]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/contenttype/id/{{contentTypeId}}", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "contenttype", + "id", + "{{contentTypeId}}" + ] + }, + "description": "Updates a content type with `systemActionMappings` adding extra mappings to the existing ones." + }, + "response": [] + }, + { + "name": "Update by removing systemActionMappings", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code should be ok 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Validate systemActionMappings exist\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.entity).to.have.property('systemActionMappings');", + "});", + "", + "pm.test(\"Validate systemActionMappings\", function () {", + " var systemActionMappings = pm.response.json().entity.systemActionMappings;", + " pm.expect(systemActionMappings).to.not.have.property('NEW');", + " pm.expect(systemActionMappings).to.have.property('ARCHIVE');", + " pm.expect(systemActionMappings).to.have.property('PUBLISH');", + "});", + "", + "pm.test(\"Validate workflowAction.id\", function () {", + " var systemActionMappings = pm.response.json().entity.systemActionMappings;", + " ", + " pm.expect(systemActionMappings.ARCHIVE.workflowAction.id).to.eql(\"4da13a42-5d59-480c-ad8f-94a3adf809fe\");", + " pm.expect(systemActionMappings.PUBLISH.workflowAction.id).to.eql(\"b9d89c80-3d88-4311-8365-187323c96436\");", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"clazz\": \"com.dotcms.contenttype.model.type.SimpleContentType\",\n \"id\": \"{{contentTypeId}}\",\n \"description\": \"Test Content Type\",\n \"defaultType\": false,\n \"system\": false,\n \"folder\": \"SYSTEM_FOLDER\",\n \"name\": \"Test Content Type\",\n \"variable\": \"{{contentTypeVariable}}\",\n \"host\": \"SYSTEM_HOST\",\n \"fixed\": false,\n \"icon\": \"inventory\",\n \"fields\": [\n {\n \"clazz\": \"com.dotcms.contenttype.model.field.TextField\",\n\t\t\t\"indexed\": true,\n\t\t\t\"dataType\": \"TEXT\",\n\t\t\t\"readOnly\": false,\n\t\t\t\"required\": true,\n\t\t\t\"searchable\": true,\n\t\t\t\"listed\": true,\n\t\t\t\"unique\": false,\n\t\t\t\"name\": \"Name\",\n\t\t\t\"variable\": \"name\",\n\t\t\t\"fixed\": false\n }\n ],\n \"systemActionMappings\" : {\n \"ARCHIVE\": \"4da13a42-5d59-480c-ad8f-94a3adf809fe\",\n \"PUBLISH\": \"b9d89c80-3d88-4311-8365-187323c96436\"\n },\n \"workflow\":[\"d61a59e1-a49c-46f2-a929-db2b4bfa88b2\"]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/contenttype/id/{{contentTypeId}}", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "contenttype", + "id", + "{{contentTypeId}}" + ] + }, + "description": "Updates a content type by removing one `systemActionMappings`." + }, + "response": [] + }, + { + "name": "Delete ContentType", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code should be ok 200\", function () {", + " pm.response.to.have.status(200);", + "});" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{serverURL}}/api/v1/contenttype/id/{{contentTypeId}}", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "contenttype", + "id", + "{{contentTypeId}}" + ] + }, + "description": "Cleanup" + }, + "response": [] + } + ], + "description": "Folder to encapsulate multiple tests to validate the proper update and handling of `systemActionMappings`." + } + ], + "description": "Group of tests to cover the most common operations on the content type `systemActionMappings`.", + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + } + ] } ], "auth": { diff --git a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/field/RelationshipCardinality.java b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/field/RelationshipCardinality.java new file mode 100644 index 000000000000..ae137d38868f --- /dev/null +++ b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/field/RelationshipCardinality.java @@ -0,0 +1,97 @@ +package com.dotcms.contenttype.model.field; + +/** + * The RelationshipCardinality enum represents the cardinality of a relationship between entities. + * The enum constants are: + *

+ *

    + *
  • ONE_TO_MANY: Indicates a one-to-many relationship.
  • + *
  • MANY_TO_MANY: Indicates a many-to-many relationship.
  • + *
  • ONE_TO_ONE: Indicates a one-to-one relationship.
  • + *
  • MANY_TO_ONE: Indicates a many-to-one relationship.
  • + *
+ *

+ * The RelationshipCardinality class provides static methods for retrieving enum constants based on + * their ordinal or name, and for retrieving the ordinal value or name of an enum constant. + *

+ * Usage example: + *

+ *

{@code
+ * RelationshipCardinality cardinality = RelationshipCardinality.ONE_TO_MANY;
+ * String name = RelationshipCardinality.getNameByOrdinal(cardinality.ordinal());
+ * int ordinal = RelationshipCardinality.getOrdinalByName(name);
+ * System.out.println(name); // Output: "ONE_TO_MANY"
+ * System.out.println(ordinal); // Output: 0
+ * }
+ * + * @throws IllegalArgumentException if the ordinal, name or enum constant is invalid or not found + */ +public enum RelationshipCardinality { + + ONE_TO_MANY, + MANY_TO_MANY, + ONE_TO_ONE, + MANY_TO_ONE; + + /** + * Returns the name of an enum constant in the RelationshipCardinality enum, based on its + * ordinal. + * + * @param ordinal the ordinal of the enum constant + * @return the name of the enum constant + * @throws IllegalArgumentException if the given ordinal is invalid + */ + public static String getNameByOrdinal(int ordinal) { + for (RelationshipCardinality rc : values()) { + if (rc.ordinal() == ordinal) { + return rc.name(); + } + } + throw new IllegalArgumentException("Invalid ordinal: " + ordinal); + } + + /** + * Returns the RelationshipCardinality enum constant based on the given name. + * + * @param name the name of the enum constant + * @return the enum constant with the given name + * @throws IllegalArgumentException if no enum constant with the given name is found + */ + public static RelationshipCardinality fromName(String name) { + for (RelationshipCardinality rc : values()) { + if (rc.name().equals(name)) { + return rc; + } + } + throw new IllegalArgumentException("Invalid name: " + name); + } + + /** + * Returns the RelationshipCardinality enum constant based on its ordinal value. + * + * @param ordinal the ordinal value of the enum constant + * @return the enum constant with the given ordinal + * @throws IllegalArgumentException if no enum constant with the given ordinal is found + */ + public static RelationshipCardinality fromOrdinal(int ordinal) { + for (RelationshipCardinality rc : values()) { + if (rc.ordinal() == ordinal) { + return rc; + } + } + throw new IllegalArgumentException("Invalid ordinal: " + ordinal); + } + + /** + * Returns the ordinal value of an enum constant in the RelationshipCardinality enum, based on + * its name. + * + * @param name the name of the enum constant + * @return the ordinal value of the enum constant + * @throws IllegalArgumentException if no enum constant with the given name is found + */ + public static int getOrdinalByName(String name) { + return fromName(name).ordinal(); + } + +} diff --git a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/field/RelationshipCardinalityDeserializer.java b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/field/RelationshipCardinalityDeserializer.java new file mode 100644 index 000000000000..e178961701d5 --- /dev/null +++ b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/field/RelationshipCardinalityDeserializer.java @@ -0,0 +1,40 @@ +package com.dotcms.contenttype.model.field; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import java.io.IOException; + +/** + * The RelationshipCardinalityDeserializer class is a custom deserializer for JSON data representing + * the cardinality of a relationship between entities. It extends the JsonDeserializer class and + * provides a method for deserializing the JSON data into a RelationshipCardinality object. + *

+ * The deserialize() method takes a JsonParser and a DeserializationContext as parameters and + * returns a RelationshipCardinality object. The method first checks the type of the current token + * in the JSON data. If it is a numeric value, it calls the RelationshipCardinality.fromOrdinal() + * method to get the corresponding RelationshipCardinality enum constant. If it is a scalar value, + * it calls the RelationshipCardinality.fromName() method to get the corresponding + * RelationshipCardinality enum constant. If the token type is neither numeric nor scalar, an + * IOException is thrown with an error message stating the unexpected token type. + * + * @throws IOException if an I/O error occurs during deserialization or the JSON data has an + * unexpected format + */ +public class RelationshipCardinalityDeserializer extends + JsonDeserializer { + + @Override + public RelationshipCardinality deserialize( + JsonParser p, DeserializationContext ctxt) throws IOException { + + if (p.getCurrentToken().isNumeric()) { + return RelationshipCardinality.fromOrdinal(p.getIntValue()); + } else if (p.getCurrentToken().isScalarValue()) { + return RelationshipCardinality.fromName(p.getText()); + } else { + throw new IOException("Unexpected token type: " + p.getCurrentToken()); + } + } + +} \ No newline at end of file diff --git a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/field/RelationshipCardinalityNameSerializer.java b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/field/RelationshipCardinalityNameSerializer.java new file mode 100644 index 000000000000..c853f94510e4 --- /dev/null +++ b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/field/RelationshipCardinalityNameSerializer.java @@ -0,0 +1,21 @@ +package com.dotcms.contenttype.model.field; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import java.io.IOException; + +/** + * The RelationshipCardinalityNameSerializer class is a JsonSerializer implementation that + * serializes a RelationshipCardinality enum value by writing its name as a string. + */ +public class RelationshipCardinalityNameSerializer extends JsonSerializer { + + @Override + public void serialize(RelationshipCardinality value, JsonGenerator gen, + SerializerProvider serializers) throws IOException { + + gen.writeString(value.name()); + } + +} \ No newline at end of file diff --git a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/field/RelationshipCardinalityOrdinalSerializer.java b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/field/RelationshipCardinalityOrdinalSerializer.java new file mode 100644 index 000000000000..81f2120aaf58 --- /dev/null +++ b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/field/RelationshipCardinalityOrdinalSerializer.java @@ -0,0 +1,22 @@ +package com.dotcms.contenttype.model.field; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import java.io.IOException; + +/** + * The RelationshipCardinalityOrdinalSerializer class is a JsonSerializer implementation that + * serializes a RelationshipCardinality enum value by writing its ordinal value as a number. + */ +public class RelationshipCardinalityOrdinalSerializer extends + JsonSerializer { + + @Override + public void serialize(RelationshipCardinality value, JsonGenerator gen, + SerializerProvider serializers) throws IOException { + + gen.writeNumber(value.ordinal()); + } + +} \ No newline at end of file diff --git a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/field/RelationshipCardinalityViewBasedSerializer.java b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/field/RelationshipCardinalityViewBasedSerializer.java new file mode 100644 index 000000000000..46bdb77d38ba --- /dev/null +++ b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/field/RelationshipCardinalityViewBasedSerializer.java @@ -0,0 +1,54 @@ +package com.dotcms.contenttype.model.field; + +import com.dotcms.model.views.CommonViews; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import java.io.IOException; + +/** + * The RelationshipCardinalityViewBasedSerializer class is a JsonSerializer implementation that + * serializes a RelationshipCardinality enum value based on the active view of the + * SerializerProvider. It delegates the serialization to different serializers based on the active + * view. The serializers used are RelationshipCardinalityNameSerializer and + * RelationshipCardinalityOrdinalSerializer. If no view is active, it uses the default serializer, + * RelationshipCardinalityNameSerializer. + * + * @see JsonSerializer + * @see RelationshipCardinalityNameSerializer + * @see RelationshipCardinalityOrdinalSerializer + */ +public class RelationshipCardinalityViewBasedSerializer extends + JsonSerializer { + + private static final JsonSerializer nameSerializer = + new RelationshipCardinalityNameSerializer(); + private static final JsonSerializer ordinalSerializer = + new RelationshipCardinalityOrdinalSerializer(); + private static final JsonSerializer defaultSerializer = nameSerializer; + + private static final Class INTERNAL_VIEW = CommonViews.ContentTypeInternalView.class; + private static final Class EXTERNAL_VIEW = CommonViews.ContentTypeExternalView.class; + + @Override + public void serialize(RelationshipCardinality value, JsonGenerator gen, + SerializerProvider serializer) throws IOException { + + Class activeView = serializer.getActiveView(); + + // Check the active view and delegate to the appropriate serializer + if (activeView != null) { + + if (activeView.equals(INTERNAL_VIEW)) { + nameSerializer.serialize(value, gen, serializer); + } else if (activeView.equals(EXTERNAL_VIEW)) { + ordinalSerializer.serialize(value, gen, serializer); + } else { + throw new IllegalStateException("Unexpected value: " + activeView.getName()); + } + } else { + // Fallback behavior when no View is active, use the ordinal serializer + defaultSerializer.serialize(value, gen, serializer); + } + } +} \ No newline at end of file diff --git a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/field/Relationships.java b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/field/Relationships.java index 636b8fe728c8..70c8dcb09202 100644 --- a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/field/Relationships.java +++ b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/field/Relationships.java @@ -23,6 +23,8 @@ public interface Relationships { String velocityVar(); - Integer cardinality(); + @JsonSerialize(using = RelationshipCardinalityViewBasedSerializer.class) + @JsonDeserialize(using = RelationshipCardinalityDeserializer.class) + RelationshipCardinality cardinality(); } diff --git a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/type/ContentType.java b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/type/ContentType.java index b56a23a0b6bd..fed8c88ca469 100644 --- a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/type/ContentType.java +++ b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/type/ContentType.java @@ -21,15 +21,16 @@ import com.fasterxml.jackson.databind.DatabindContext; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; import com.fasterxml.jackson.databind.jsontype.impl.ClassNameIdResolver; import com.fasterxml.jackson.databind.type.TypeFactory; +import jakarta.annotation.Nullable; import java.io.IOException; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; -import jakarta.annotation.Nullable; import org.immutables.value.Value; import org.immutables.value.Value.Default; @@ -181,11 +182,7 @@ public List workflows() { return Collections.emptyList(); } - //System action mappings are rendered quite differently depending on what endpoint gets called - //if it's coming from an endpoint that returns a list of CT we get a simplified version - //if it's coming from an endpoint that returns only one CT then we get a full representation - //Again a different form of this attribute is used when sending the request to create or update the CT - //Therefore it's best if we keep a Generic high level representation of the field through JsonNode + @JsonDeserialize(using = SystemActionMappingsDeserializer.class) @JsonInclude(Include.NON_NULL) @Nullable public abstract JsonNode systemActionMappings(); diff --git a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/type/SystemActionMappingsDeserializer.java b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/type/SystemActionMappingsDeserializer.java new file mode 100644 index 000000000000..50bdd2ad6c45 --- /dev/null +++ b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/type/SystemActionMappingsDeserializer.java @@ -0,0 +1,138 @@ +package com.dotcms.contenttype.model.type; + +import com.dotcms.contenttype.model.workflow.SystemAction; +import com.dotcms.model.views.CommonViews; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.io.IOException; +import java.util.Optional; + +/** + * Custom deserializer for handling system action mappings. This deserializer transforms the JSON + * structure of system action mappings into a simplified form where workflow action IDs are + * inlined. + */ +public class SystemActionMappingsDeserializer extends JsonDeserializer { + + /** + * Deserializes the JSON content to transform system action mappings. + * + * @param p the parser used for reading JSON content + * @param ctxt the context for deserialization + * @return a transformed JSON node or null if no mappings are present + * @throws IOException if an I/O error occurs + * @throws JsonProcessingException if a JSON processing error occurs + */ + @Override + public JsonNode deserialize(JsonParser p, DeserializationContext ctxt) + throws IOException, JacksonException { + + final ObjectMapper mapper = new ObjectMapper(); + final ObjectNode transformedMappings = mapper.createObjectNode(); + + JsonNode rawSystemActionMappings = p.getCodec().readTree(p); + if (isNotNull(rawSystemActionMappings)) { + + for (SystemAction systemAction : SystemAction.values()) { + + if (rawSystemActionMappings.has(systemAction.name())) { + processActionValue( + rawSystemActionMappings.get(systemAction.name()), + systemAction.name(), + transformedMappings + ); + } + } + } + + return jsonNodeByView(ctxt, transformedMappings); + } + + /** + * Retrieves a JSON node based on the active view in the given context. + * + * @param ctxt the deserialization context + * @param transformedMappings the JSON node representing the transformed mappings + * @return the JSON node based on the active view, or null if no mappings are present + * @throws IllegalStateException if an unexpected value is encountered in the active view + */ + private JsonNode jsonNodeByView(final DeserializationContext ctxt, + final ObjectNode transformedMappings) { + + // Check the active view and handle accordingly + if (ctxt.getActiveView() != null) { + + if (ctxt.getActiveView().equals(CommonViews.ContentTypeInternalView.class)) { + return null == transformedMappings || transformedMappings.isEmpty() + ? null : transformedMappings; + } else if (ctxt.getActiveView().equals(CommonViews.ContentTypeExternalView.class)) { + return transformedMappings; + } else { + throw new IllegalStateException( + "Unexpected value: " + ctxt.getActiveView().getName()); + } + } else { + return transformedMappings; + } + } + + /** + * Processes the value of an action and adds it to the transformed mappings. + * + * @param actionValue the JSON node representing the action value + * @param actionName the name of the action + * @param transformedMappings the object node to store transformed mappings + */ + private void processActionValue(JsonNode actionValue, String actionName, + ObjectNode transformedMappings) { + + if (isNotNull(actionValue)) { + if (actionValue instanceof ObjectNode + && actionValue.has("workflowAction")) { + + processWorkflowAction( + actionValue.get("workflowAction"), actionName, transformedMappings + ); + } else if (actionValue.isTextual()) { + transformedMappings.put(actionName, actionValue.asText()); + } + } + } + + /** + * Processes workflow action and extracts the ID to add to the transformed mappings. + * + * @param workflowAction the JSON node representing the workflow action + * @param actionName the name of the action + * @param transformedMappings the object node to store transformed mappings + */ + private void processWorkflowAction(JsonNode workflowAction, String actionName, + ObjectNode transformedMappings) { + + Optional.ofNullable(workflowAction.get("id")) + .ifPresentOrElse( + idNode -> transformedMappings.put(actionName, idNode.asText()), + () -> { + throw new IllegalStateException("Unable to transform actionMappings. " + + "Missing 'id' in workflowAction for " + actionName); + } + ); + } + + /** + * Checks if the given JSON node is not null or empty. + * + * @param node the JSON node to check + * @return true if the node is not null or empty, false otherwise + */ + private boolean isNotNull(JsonNode node) { + return node != null && !node.isNull(); + } + +} \ No newline at end of file diff --git a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/workflow/ActionMapping.java b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/workflow/ActionMapping.java deleted file mode 100644 index 3fc21c6bc60d..000000000000 --- a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/workflow/ActionMapping.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.dotcms.contenttype.model.workflow; - - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import jakarta.annotation.Nullable; -import org.immutables.value.Value; - -/** - * Workflow action mapping - */ -@Value.Immutable -@JsonSerialize(as = ImmutableActionMapping.class) -@JsonDeserialize(as = ImmutableActionMapping.class) -public interface ActionMapping { - - String identifier(); - - @Nullable - String systemAction(); - - @Nullable - WorkflowAction workflowAction(); - - // the owner could be a ContentType or WorkflowScheme - @Nullable - Object owner(); - - @Nullable - Boolean ownerContentType(); - - @Nullable - Boolean ownerScheme(); - -} diff --git a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/workflow/WorkflowAction.java b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/workflow/WorkflowAction.java deleted file mode 100644 index 8bbd0b6df1ec..000000000000 --- a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/contenttype/model/workflow/WorkflowAction.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.dotcms.contenttype.model.workflow; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import java.util.List; -import jakarta.annotation.Nullable; -import org.immutables.value.Value; - -/** - * Workflow action - */ -@Value.Immutable -@JsonSerialize(as = ImmutableWorkflowAction.class) -@JsonDeserialize(as = ImmutableWorkflowAction.class) -public interface WorkflowAction { - - @Nullable - Boolean assignable(); - - @Nullable - Boolean commentable(); - - @Nullable - String condition(); - - @Nullable - String icon(); - - String name(); - - String id(); - - String nextAssign(); - String nextStep(); - - Boolean nextStepCurrentStep(); - - Integer order(); - - @Nullable - Boolean roleHierarchyForAssign(); - - @Nullable - String schemeId(); - - @Nullable - Object owner(); - - @Nullable - List showOn(); - -} diff --git a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/contenttype/AbstractSaveContentTypeRequest.java b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/contenttype/AbstractSaveContentTypeRequest.java index b833fe7b84ac..4a3b37b060f2 100644 --- a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/contenttype/AbstractSaveContentTypeRequest.java +++ b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/model/contenttype/AbstractSaveContentTypeRequest.java @@ -3,7 +3,6 @@ import com.dotcms.api.provider.ClientObjectMapper; import com.dotcms.contenttype.model.type.ContentType; import com.dotcms.contenttype.model.type.SimpleContentType; -import com.dotcms.contenttype.model.workflow.SystemAction; import com.dotcms.contenttype.model.workflow.Workflow; import com.dotcms.model.annotation.ValueType; import com.dotcms.model.views.CommonViews; @@ -12,18 +11,14 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; import com.fasterxml.jackson.databind.jsontype.impl.ClassNameIdResolver; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.type.TypeFactory; import java.util.List; import org.immutables.value.Value; - /** * This class maps a Request to Create of Update a ContentType * A Separate Class was created to deal with different forms of the attributes required by the save and update endpoints @@ -75,60 +70,4 @@ public String idFromValue(Object value) { } - /** - * Override the Immutable Builder to be able to modify systemActionMappings - * Also we're moving workflow attribute down here since it's not really part of the ContentType it is required by the API - */ - public static class Builder extends SaveContentTypeRequest.Builder { - - private Class typeInf = SimpleContentType.class; - - public SaveContentTypeRequest.Builder of(ContentType in) { - this.typeInf = in.getClass(); - return from(in); - } - - - /** - * Custom Builder to deal with the systemActionMappings attribute - * This attribute needs mutate depending on the situation - * Sometimes when returned by the API that list content types it gets delivered as a simplified version - * When Returned by a getContentType API call it gets delivered as a full version - * And here when saving or updating a content type we need to transform it into a totally different representation again - * @return - */ - @Override - public SaveContentTypeRequest build() { - - final SaveContentTypeRequest value = super.build(); - final JsonNode actionMappings = value.systemActionMappings(); - if (null != actionMappings) { - //If we got systemActionMappings then we need to transform them - final ObjectMapper mapper = new ObjectMapper(); - final ObjectNode rootNode = mapper.createObjectNode(); - for (SystemAction sa : SystemAction.values()) { - if (actionMappings.has(sa.name())) { - final JsonNode jsonNode = actionMappings.get(sa.name()); - final JsonNode action = jsonNode.get("workflowAction"); - if(null == action){ - throw new IllegalStateException("Unable to transform actionMappings. We're missing a workflowAction attribute."); - } - String actionIdentifier = action.get("id").asText(); - rootNode.put(sa.name(), actionIdentifier); - } - } - super.systemActionMappings(rootNode); - } - this.typeInf(typeInf); - return super.build(); - } - - } - - /** - * Helper to create the override Builder - */ - public static Builder builder() { - return new Builder(); - } } diff --git a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/ContentTypeAPIIT.java b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/ContentTypeAPIIT.java index 02634d75607f..f48655c19cf7 100644 --- a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/ContentTypeAPIIT.java +++ b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/ContentTypeAPIIT.java @@ -4,7 +4,8 @@ import com.dotcms.api.client.model.RestClientFactory; import com.dotcms.api.client.model.ServiceManager; import com.dotcms.api.provider.ClientObjectMapper; -import com.dotcms.common.ContentTypeTestHelperService; +import com.dotcms.common.ContentTypeLayoutTestHelperService; +import com.dotcms.common.ContentTypesTestHelperService; import com.dotcms.contenttype.model.field.BinaryField; import com.dotcms.contenttype.model.field.FieldLayoutRow; import com.dotcms.contenttype.model.field.ImmutableBinaryField; @@ -13,17 +14,15 @@ import com.dotcms.contenttype.model.field.ImmutableRelationships; import com.dotcms.contenttype.model.field.ImmutableRowField; import com.dotcms.contenttype.model.field.ImmutableTextField; +import com.dotcms.contenttype.model.field.RelationshipCardinality; import com.dotcms.contenttype.model.field.RelationshipField; import com.dotcms.contenttype.model.field.Relationships; import com.dotcms.contenttype.model.type.BaseContentType; import com.dotcms.contenttype.model.type.ContentType; import com.dotcms.contenttype.model.type.ImmutableSimpleContentType; -import com.dotcms.contenttype.model.workflow.ImmutableActionMapping; -import com.dotcms.contenttype.model.workflow.ImmutableWorkflowAction; import com.dotcms.contenttype.model.workflow.SystemAction; import com.dotcms.model.ResponseEntityView; import com.dotcms.model.config.ServiceBean; -import com.dotcms.model.contenttype.AbstractSaveContentTypeRequest; import com.dotcms.model.contenttype.FilterContentTypesRequest; import com.dotcms.model.contenttype.SaveContentTypeRequest; import com.dotcms.model.site.GetSiteByNameRequest; @@ -33,6 +32,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.TestProfile; +import jakarta.inject.Inject; +import jakarta.ws.rs.NotFoundException; import java.io.IOException; import java.net.URL; import java.util.Date; @@ -40,8 +41,6 @@ import java.util.Map; import java.util.Objects; import java.util.Set; -import jakarta.inject.Inject; -import jakarta.ws.rs.NotFoundException; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -70,7 +69,10 @@ class ContentTypeAPIIT { ServiceManager serviceManager; @Inject - ContentTypeTestHelperService contentTypeTestHelperService; + ContentTypeLayoutTestHelperService contentTypeLayoutTestHelperService; + + @Inject + ContentTypesTestHelperService contentTypesTestHelperService; @BeforeEach public void setupTest() throws IOException { @@ -225,8 +227,8 @@ void Test_Create_Then_Update_Then_Delete_Content_Type() { ).build(); final ContentTypeAPI client = apiClientFactory.getClient(ContentTypeAPI.class); - final SaveContentTypeRequest saveRequest = AbstractSaveContentTypeRequest.builder() - .of(contentType).build(); + final SaveContentTypeRequest saveRequest = SaveContentTypeRequest.builder(). + from(contentType).build(); final ResponseEntityView> response = client.createContentTypes(List.of(saveRequest)); Assertions.assertNotNull(response); @@ -239,8 +241,8 @@ void Test_Create_Then_Update_Then_Delete_Content_Type() { client.getContentType(newContentType.variable(), 1L, true); //Now lets test update final ImmutableSimpleContentType updatedContentType = ImmutableSimpleContentType.builder().from(newContentType).description("Updated").build(); - final SaveContentTypeRequest request = AbstractSaveContentTypeRequest.builder() - .of(updatedContentType).build(); + final SaveContentTypeRequest request = SaveContentTypeRequest.builder(). + from(updatedContentType).build(); final ResponseEntityView responseEntityView = client.updateContentType( request.variable(), request); Assertions.assertEquals("Updated", responseEntityView.entity().description()); @@ -266,90 +268,164 @@ void Test_Create_Then_Update_Then_Delete_Content_Type() { } } + /** + * Test: Create action mappings, then update them. + * + *

Scenario:

+ *
    + *
  1. Create a new set of action mappings.
  2. + *
  3. Update the created action mappings with new values.
  4. + *
  5. Verify that the updates are correctly applied.
  6. + *
+ * + *

Expected:

+ *
    + *
  • Action mappings should be created successfully.
  • + *
  • The updates to the action mappings should be reflected correctly.
  • + *
  • The entire process should complete without errors.
  • + *
+ * + * @throws JsonProcessingException If an error occurs while processing JSON. + */ @Test void Test_Create_Then_Update_Action_Mappings() throws JsonProcessingException { + final ContentTypeAPI client = apiClientFactory.getClient(ContentTypeAPI.class); + //We're only using this to extract the existing Workflows - final ResponseEntityView response = client.getContentType("FileAsset", 1L, false); - final ContentType fileAsset = response.entity(); + final ResponseEntityView fileAssetResponse = client.getContentType( + "FileAsset", 1L, false + ); + final ContentType fileAsset = fileAssetResponse.entity(); Assertions.assertFalse(Objects.requireNonNull(fileAsset.workflows()).isEmpty()); - final String systemWorkflowId = fileAsset.workflows().get(0).id(); + // Creating tha action mappings + final Map actionMappingsV1 = Map.of( + SystemAction.NEW.name(), "b9d89c80-3d88-4311-8365-187323c96436" + ); final ObjectMapper mapper = new ObjectMapper(); - final ImmutableWorkflowAction workflowAction = ImmutableWorkflowAction.builder() - .id("b9d89c80-3d88-4311-8365-187323c96436") - .name("Publish") - .assignable(false) - .commentable(false) - .condition("") - .icon("workflowIcon") - .nextAssign("654b0931-1027-41f7-ad4d-173115ed8ec1") - .nextStep("dc3c9cd0-8467-404b-bf95-cb7df3fbc293") - .nextStepCurrentStep(false) - .order(0) - .roleHierarchyForAssign(false) - .schemeId("d61a59e1-a49c-46f2-a929-db2b4bfa88b2") - .showOn(List.of("EDITING", - "PUBLISHED", - "UNLOCKED", - "NEW", - "UNPUBLISHED", - "LISTING", - "LOCKED")) - .build(); - - final ImmutableActionMapping actionMapping = ImmutableActionMapping.builder() - .workflowAction(workflowAction) - .identifier(systemWorkflowId) - .systemAction("NEW") - .build(); - - final Map actionMappings = Map.of(SystemAction.NEW.name(), actionMapping); - final JsonNode jsonNode = mapper.valueToTree(actionMappings); + final JsonNode jsonNodeV1 = mapper.valueToTree(actionMappingsV1); - final long identifier = System.currentTimeMillis(); - final ImmutableSimpleContentType contentType = ImmutableSimpleContentType.builder() + final long identifier = System.currentTimeMillis(); + final String contentTypeVariable = "_var_" + identifier; + final ImmutableSimpleContentType contentTypeWithoutMapping = ImmutableSimpleContentType.builder() .description("ct action mappings.") - .variable("_var_"+identifier) + .variable(contentTypeVariable) .addFields( ImmutableBinaryField.builder() - .name("_bin_var_"+identifier) - .variable("anyField"+System.currentTimeMillis()) + .name("_bin_var_" + identifier) + .variable("anyField" + System.currentTimeMillis()) .build() ).workflows(fileAsset.workflows()) - .systemActionMappings(jsonNode) .build(); - final String content = mapper.writerWithDefaultPrettyPrinter() - .writeValueAsString(contentType); - - System.out.println(content); - - final SaveContentTypeRequest request = AbstractSaveContentTypeRequest.builder() - .of(contentType).build(); - - final ResponseEntityView> response2 = client.createContentTypes(List.of(request)); - - final ContentType creted = response2.entity().get(0); - Assertions.assertNotNull(creted.systemActionMappings()); - - final ImmutableSimpleContentType modifiedContentType = ImmutableSimpleContentType.builder() - .from(creted).addFields(ImmutableBinaryField.builder() - .contentTypeId(creted.id()) - .name("_bin_var_2" + identifier) - .variable("anyField2" + System.currentTimeMillis()) - .build()).description("Modified!").build(); - - final SaveContentTypeRequest request2 = AbstractSaveContentTypeRequest.builder().of(modifiedContentType).build(); - final ResponseEntityView entityView = client.updateContentType( - request2.variable(), request2 - ); + try { - final ContentType updatedContentType = entityView.entity(); - Assertions.assertNotNull(updatedContentType.systemActionMappings()); - Assertions.assertEquals("Modified!",updatedContentType.description()); - Assertions.assertEquals(4, updatedContentType.fields().size()); + // --- + // Creating the content type + final var contentType = contentTypeWithoutMapping.withSystemActionMappings(jsonNodeV1); + final SaveContentTypeRequest request = SaveContentTypeRequest.builder(). + from(contentType).build(); + final ResponseEntityView> createContentTypeResponse = + client.createContentTypes(List.of(request)); + + // Make sure the content type was saved and indexed (This waits in case the + // indexing/saving took time) + final var byVarName = contentTypesTestHelperService.findContentType( + contentTypeVariable); + Assertions.assertTrue(byVarName.isPresent()); + + final ContentType createdContentType = createContentTypeResponse.entity().get(0); + Assertions.assertNotNull(createdContentType.systemActionMappings()); + Assertions.assertEquals(1, createdContentType.systemActionMappings().size()); + + // --- + // Now we are going to modify the content type WITHOUT system mappings, nothing should + // change for the mappings + var modifiedContentType = contentTypeWithoutMapping.withDescription("Modified!"); + + SaveContentTypeRequest contentTypeRequest = SaveContentTypeRequest.builder(). + from(modifiedContentType).build(); + ResponseEntityView updateContentTypeResponse = client.updateContentType( + contentTypeRequest.variable(), contentTypeRequest + ); + + ContentType updatedContentType = updateContentTypeResponse.entity(); + Assertions.assertNotNull(updatedContentType.systemActionMappings()); + Assertions.assertEquals(1, updatedContentType.systemActionMappings().size()); + Assertions.assertEquals("Modified!", updatedContentType.description()); + + // --- + // Updating the system mappings, we are going to add more mappings + final Map actionMappingsV2 = Map.of( + SystemAction.NEW.name(), "b9d89c80-3d88-4311-8365-187323c96436", + SystemAction.ARCHIVE.name(), "4da13a42-5d59-480c-ad8f-94a3adf809fe", + SystemAction.PUBLISH.name(), "b9d89c80-3d88-4311-8365-187323c96436" + ); + final JsonNode jsonNodeV2 = new ObjectMapper().valueToTree(actionMappingsV2); + + modifiedContentType = contentTypeWithoutMapping. + withDescription("Modified 2!"). + withSystemActionMappings(jsonNodeV2); + + contentTypeRequest = SaveContentTypeRequest.builder().from(modifiedContentType).build(); + updateContentTypeResponse = client.updateContentType( + contentTypeRequest.variable(), contentTypeRequest + ); + + updatedContentType = updateContentTypeResponse.entity(); + Assertions.assertNotNull(updatedContentType.systemActionMappings()); + Assertions.assertEquals(3, updatedContentType.systemActionMappings().size()); + Assertions.assertEquals("Modified 2!", updatedContentType.description()); + + // --- + // Updating again the system mappings, removing one + final Map actionMappingsV3 = Map.of( + SystemAction.NEW.name(), "b9d89c80-3d88-4311-8365-187323c96436", + SystemAction.ARCHIVE.name(), "4da13a42-5d59-480c-ad8f-94a3adf809fe" + ); + final JsonNode jsonNodeV3 = new ObjectMapper().valueToTree(actionMappingsV3); + + modifiedContentType = contentTypeWithoutMapping. + withDescription("Modified 3!"). + withSystemActionMappings(jsonNodeV3); + + contentTypeRequest = SaveContentTypeRequest.builder().from(modifiedContentType).build(); + updateContentTypeResponse = client.updateContentType( + contentTypeRequest.variable(), contentTypeRequest + ); + + updatedContentType = updateContentTypeResponse.entity(); + Assertions.assertNotNull(updatedContentType.systemActionMappings()); + Assertions.assertEquals(2, updatedContentType.systemActionMappings().size()); + Assertions.assertEquals("Modified 3!", updatedContentType.description()); + + // --- + // Finally trying to remove all system mappings + final Map actionMappingsV4 = Map.of(); + final JsonNode jsonNodeV4 = new ObjectMapper().valueToTree(actionMappingsV4); + + modifiedContentType = contentTypeWithoutMapping. + withDescription("Modified 4!"). + withSystemActionMappings(jsonNodeV4); + + contentTypeRequest = SaveContentTypeRequest.builder().from(modifiedContentType).build(); + updateContentTypeResponse = client.updateContentType( + contentTypeRequest.variable(), contentTypeRequest + ); + + updatedContentType = updateContentTypeResponse.entity(); + Assertions.assertNull(updatedContentType.systemActionMappings()); + Assertions.assertEquals("Modified 4!", updatedContentType.description()); + } finally { + // Clean up + try { + client.delete(contentTypeVariable); + } catch (Exception e) { + // Ignore any issue here on the cleanup + } + } } /** @@ -497,8 +573,8 @@ void Test_Send_Invalid_Host_And_Folder_Verify_Defaults() { .build() ).build(); - final SaveContentTypeRequest saveRequest = AbstractSaveContentTypeRequest.builder() - .of(contentType1).build(); + final SaveContentTypeRequest saveRequest = SaveContentTypeRequest.builder(). + from(contentType1).build(); final ResponseEntityView> contentTypeResponse1 = client.createContentTypes(List.of(saveRequest)); Assertions.assertNotNull(contentTypeResponse1); @@ -544,8 +620,8 @@ void Test_Send_Folder_Path_Only_Valid_Folder_Expect_Matching_Folder_Id() { .build() ).build(); - final SaveContentTypeRequest saveRequest = AbstractSaveContentTypeRequest.builder() - .of(contentType1).build(); + final SaveContentTypeRequest saveRequest = SaveContentTypeRequest.builder(). + from(contentType1).build(); final ResponseEntityView> contentTypeResponse2 = client.createContentTypes(List.of(saveRequest)); Assertions.assertNotNull(contentTypeResponse2); @@ -592,9 +668,8 @@ void Test_Create_Content_Type_Out_Of_Folder_Path() { .build() ).build(); - - final SaveContentTypeRequest saveRequest = AbstractSaveContentTypeRequest.builder() - .of(contentType2).build(); + final SaveContentTypeRequest saveRequest = SaveContentTypeRequest.builder(). + from(contentType2).build(); final ResponseEntityView> contentTypeResponse2 = client.createContentTypes(List.of(saveRequest)); Assertions.assertNotNull(contentTypeResponse2); @@ -645,8 +720,8 @@ void Test_ContentType_Without_Layout_Attribute() { final ContentTypeAPI client = apiClientFactory.getClient(ContentTypeAPI.class); - final SaveContentTypeRequest saveRequest = AbstractSaveContentTypeRequest.builder() - .of(contentType).build(); + final SaveContentTypeRequest saveRequest = SaveContentTypeRequest.builder(). + from(contentType).build(); final ResponseEntityView> contentTypeResponse = client.createContentTypes(List.of(saveRequest)); Assertions.assertNotNull(contentTypeResponse); @@ -691,20 +766,26 @@ void Test_ContentType_Layout_Attribute_Is_Ignored() { var rowField1 = ImmutableRowField.builder().name("row-1").build(); var columnField1 = ImmutableColumnField.builder().name("column-1").build(); //Four textFields are created - var fieldsList1 = this.contentTypeTestHelperService.buildTextFields(columnField1.name(), 4); + var fieldsList1 = this.contentTypeLayoutTestHelperService.buildTextFields( + columnField1.name(), 4); //The textFields are added to the column - var layoutColumnFieldList1 = this.contentTypeTestHelperService.buildLayoutColumns(List.of(columnField1), fieldsList1); + var layoutColumnFieldList1 = this.contentTypeLayoutTestHelperService.buildLayoutColumns( + List.of(columnField1), fieldsList1); //The layout row is created with the row and its columns - var fieldLayoutRow1 = this.contentTypeTestHelperService.buildFieldLayoutRow(rowField1, layoutColumnFieldList1); + var fieldLayoutRow1 = this.contentTypeLayoutTestHelperService.buildFieldLayoutRow(rowField1, + layoutColumnFieldList1); var rowField2 = ImmutableRowField.builder().name("row-2").build(); var columnField2 = ImmutableColumnField.builder().name("column-1").build(); //Six fields are created - var fieldsList2 = this.contentTypeTestHelperService.buildTextFields(columnField1.name(), 6); + var fieldsList2 = this.contentTypeLayoutTestHelperService.buildTextFields( + columnField1.name(), 6); //The textFields are added to the column - var layoutColumnFieldList2 = this.contentTypeTestHelperService.buildLayoutColumns(List.of(columnField2), fieldsList2); + var layoutColumnFieldList2 = this.contentTypeLayoutTestHelperService.buildLayoutColumns( + List.of(columnField2), fieldsList2); //The layout row is created with the row and its columns - var fieldLayoutRow2 = this.contentTypeTestHelperService.buildFieldLayoutRow(rowField2, layoutColumnFieldList2); + var fieldLayoutRow2 = this.contentTypeLayoutTestHelperService.buildFieldLayoutRow(rowField2, + layoutColumnFieldList2); final ImmutableSimpleContentType contentType = ImmutableSimpleContentType.builder() .baseType(BaseContentType.CONTENT) @@ -734,8 +815,8 @@ void Test_ContentType_Layout_Attribute_Is_Ignored() { final ContentTypeAPI client = apiClientFactory.getClient(ContentTypeAPI.class); - final SaveContentTypeRequest saveRequest = AbstractSaveContentTypeRequest.builder() - .of(contentType).build(); + final SaveContentTypeRequest saveRequest = SaveContentTypeRequest.builder(). + from(contentType).build(); final ResponseEntityView> contentTypeResponse = client.createContentTypes(List.of(saveRequest)); Assertions.assertNotNull(contentTypeResponse); @@ -789,7 +870,7 @@ void Simple_Relationship_Support_Test() throws IOException { .addFields( ImmutableRelationshipField.builder().name("Blog Comment").variable("myBlogComment"+timeMark).indexed(true) .relationships(ImmutableRelationships.builder() - .cardinality(0) + .cardinality(RelationshipCardinality.ONE_TO_MANY) .isParentField(true) .velocityVar("MyBlogComment"+timeMark) .build() @@ -809,7 +890,7 @@ void Simple_Relationship_Support_Test() throws IOException { .addFields( ImmutableRelationshipField.builder().name("Blog").variable("myBlog"+timeMark).indexed(true) .relationships(ImmutableRelationships.builder() - .cardinality(1) + .cardinality(RelationshipCardinality.MANY_TO_MANY) .velocityVar("MyBlog.myBlogComment"+timeMark) .isParentField(false) .build() @@ -818,7 +899,8 @@ void Simple_Relationship_Support_Test() throws IOException { final ContentTypeAPI client = apiClientFactory.getClient(ContentTypeAPI.class); - final SaveContentTypeRequest saveBlogRequest = AbstractSaveContentTypeRequest.builder().of(blog).build(); + final SaveContentTypeRequest saveBlogRequest = SaveContentTypeRequest.builder(). + from(blog).build(); final ResponseEntityView> contentTypeResponse1 = client.createContentTypes(List.of(saveBlogRequest)); ContentType savedContentType1 = null; ContentType savedContentType2 = null; @@ -832,7 +914,8 @@ void Simple_Relationship_Support_Test() throws IOException { .get(2); final Relationships relationships1 = parentRel1.relationships(); Assertions.assertNotNull(relationships1); - Assertions.assertEquals(0, relationships1.cardinality()); + Assertions.assertEquals(RelationshipCardinality.ONE_TO_MANY, + relationships1.cardinality()); // For some reason the server side is not setting the isParentField flag from the Content Type definition //Apparently there some extra logic that takes place when relationships are created from the UI //Relationship creations is triggered by the fields API @@ -840,8 +923,8 @@ void Simple_Relationship_Support_Test() throws IOException { //Assertions.assertTrue(relationships1.isParentField()); Assertions.assertEquals("MyBlogComment" + timeMark, relationships1.velocityVar()); - final SaveContentTypeRequest saveBlogCommentRequest = AbstractSaveContentTypeRequest.builder() - .of(blogComment).build(); + final SaveContentTypeRequest saveBlogCommentRequest = SaveContentTypeRequest.builder(). + from(blogComment).build(); final ResponseEntityView> contentTypeResponse2 = client.createContentTypes( List.of(saveBlogCommentRequest)); final List contentTypes2 = contentTypeResponse2.entity(); @@ -852,7 +935,8 @@ void Simple_Relationship_Support_Test() throws IOException { final Relationships relationships2 = parentRel2.relationships(); Assertions.assertNotNull(relationships2); - Assertions.assertEquals(1, relationships2.cardinality()); + Assertions.assertEquals(RelationshipCardinality.MANY_TO_MANY, + relationships2.cardinality()); // For some reason the server side is not setting the isParentField flag from the Content Type definition //Apparently there some extra logic that takes place when relationships are created from the UI //Relationship creations is triggered by the fields API diff --git a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/common/ContentTypeTestHelperService.java b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/common/ContentTypeLayoutTestHelperService.java similarity index 99% rename from tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/common/ContentTypeTestHelperService.java rename to tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/common/ContentTypeLayoutTestHelperService.java index c8c438b76753..9b9e99cf8986 100644 --- a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/common/ContentTypeTestHelperService.java +++ b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/common/ContentTypeLayoutTestHelperService.java @@ -12,7 +12,7 @@ import java.util.Objects; @ApplicationScoped -public class ContentTypeTestHelperService { +public class ContentTypeLayoutTestHelperService { public static final String SYSTEM_WORKFLOW_ID = "d61a59e1-a49c-46f2-a929-db2b4bfa88b2"; public static final String SYSTEM_WORKFLOW_VARIABLE_NAME = "SystemWorkflow"; diff --git a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/common/ContentTypesTestHelperService.java b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/common/ContentTypesTestHelperService.java new file mode 100644 index 000000000000..9567a286ce2c --- /dev/null +++ b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/common/ContentTypesTestHelperService.java @@ -0,0 +1,84 @@ +package com.dotcms.common; + +import static org.testcontainers.shaded.org.awaitility.Awaitility.await; + +import com.dotcms.api.ContentTypeAPI; +import com.dotcms.api.client.model.RestClientFactory; +import com.dotcms.contenttype.model.type.ContentType; +import com.dotcms.model.ResponseEntityView; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.control.ActivateRequestContext; +import jakarta.inject.Inject; +import jakarta.ws.rs.NotFoundException; +import java.time.Duration; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; +import org.testcontainers.shaded.org.awaitility.core.ConditionTimeoutException; + +@ApplicationScoped +public class ContentTypesTestHelperService { + + private static final Duration MAX_WAIT_TIME = Duration.ofSeconds(15); + private static final Duration POLL_INTERVAL = Duration.ofSeconds(2); + + @Inject + RestClientFactory clientFactory; + + /** + * Searches for a content type by its variable. + * + * @param variable The variable of the content type. + * @return The content type if found, otherwise an empty optional. + */ + public Optional findContentType(final String variable) { + + try { + + final AtomicReference contentTypeRef = new AtomicReference<>(); + + await() + .atMost(MAX_WAIT_TIME) + .pollInterval(POLL_INTERVAL) + .until(() -> { + try { + var response = findContentTypeByVariable(variable); + if (response != null && response.entity() != null) { + contentTypeRef.set(response.entity()); + return true; + } + + return false; + } catch (NotFoundException e) { + return false; + } + }); + + ContentType contentType = contentTypeRef.get(); + if (contentType != null) { + return Optional.of(contentType); + } else { + return Optional.empty(); + } + } catch (ConditionTimeoutException ex) { + return Optional.empty(); + } + } + + /** + * Retrieves a content type by its variable. + * + * @param variable The variable of the content type. + * @return The ResponseEntityView containing the content type. + */ + @ActivateRequestContext + public ResponseEntityView findContentTypeByVariable(final String variable) { + + final ContentTypeAPI contentTypeAPI = clientFactory.getClient(ContentTypeAPI.class); + + // Execute the REST call to retrieve folder contents + return contentTypeAPI.getContentType( + variable, null, null + ); + } + +} diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/push/contenttype/ContentTypePushHandler.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/push/contenttype/ContentTypePushHandler.java index cd025048ee6a..34cc9defa792 100644 --- a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/push/contenttype/ContentTypePushHandler.java +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/push/contenttype/ContentTypePushHandler.java @@ -5,16 +5,15 @@ import com.dotcms.api.client.push.PushHandler; import com.dotcms.api.client.util.NamingUtils; import com.dotcms.contenttype.model.type.ContentType; -import com.dotcms.model.contenttype.AbstractSaveContentTypeRequest.Builder; import com.dotcms.model.contenttype.SaveContentTypeRequest; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.context.control.ActivateRequestContext; +import jakarta.inject.Inject; import java.io.File; import java.text.SimpleDateFormat; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; -import jakarta.enterprise.context.Dependent; -import jakarta.enterprise.context.control.ActivateRequestContext; -import jakarta.inject.Inject; import org.apache.commons.lang3.StringUtils; @Dependent @@ -62,7 +61,8 @@ public ContentType add(File localFile, ContentType localContentType, final ContentTypeAPI contentTypeAPI = clientFactory.getClient(ContentTypeAPI.class); - final SaveContentTypeRequest saveRequest = new Builder().of(localContentType).build(); + final SaveContentTypeRequest saveRequest = SaveContentTypeRequest.builder(). + from(localContentType).build(); final var response = contentTypeAPI.createContentTypes(List.of(saveRequest)); return response.entity().stream() @@ -78,7 +78,8 @@ public ContentType edit(File localFile, ContentType localContentType, final ContentTypeAPI contentTypeAPI = clientFactory.getClient(ContentTypeAPI.class); - final SaveContentTypeRequest saveRequest = new Builder().of(localContentType).build(); + final SaveContentTypeRequest saveRequest = SaveContentTypeRequest.builder(). + from(localContentType).build(); final var response = contentTypeAPI.updateContentType(localContentType.variable(), saveRequest); diff --git a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/contenttype/ContentTypeCommandIT.java b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/contenttype/ContentTypeCommandIT.java index 4b5daf9a79d8..3fedcf12ff6c 100644 --- a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/contenttype/ContentTypeCommandIT.java +++ b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/contenttype/ContentTypeCommandIT.java @@ -28,6 +28,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.TestProfile; +import jakarta.inject.Inject; +import jakarta.ws.rs.NotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; @@ -43,8 +45,6 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; -import jakarta.inject.Inject; -import jakarta.ws.rs.NotFoundException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/common/ContentTypesTestHelperService.java b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/common/ContentTypesTestHelperService.java index c795d6d66c2c..def1db7d8f17 100644 --- a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/common/ContentTypesTestHelperService.java +++ b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/common/ContentTypesTestHelperService.java @@ -14,9 +14,12 @@ import com.dotcms.contenttype.model.workflow.ImmutableWorkflow; import com.dotcms.model.ResponseEntityView; import com.dotcms.model.config.Workspace; -import com.dotcms.model.contenttype.AbstractSaveContentTypeRequest; import com.dotcms.model.contenttype.SaveContentTypeRequest; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.control.ActivateRequestContext; +import jakarta.inject.Inject; +import jakarta.ws.rs.NotFoundException; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -26,10 +29,6 @@ import java.util.Objects; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.context.control.ActivateRequestContext; -import jakarta.inject.Inject; -import jakarta.ws.rs.NotFoundException; import org.testcontainers.shaded.org.awaitility.core.ConditionTimeoutException; @ApplicationScoped @@ -71,8 +70,8 @@ public String createContentTypeOnServer(final String detailPage, final String ur null, null, detailPage, urlMapPattern ); - final SaveContentTypeRequest saveRequest = AbstractSaveContentTypeRequest.builder() - .of(contentType).build(); + final SaveContentTypeRequest saveRequest = SaveContentTypeRequest.builder(). + from(contentType).build(); contentTypeAPI.createContentTypes(List.of(saveRequest)); return varName;