chore: json to map conversion with test template (#37788)

## Description
> [!TIP]  
> _Add a TL;DR when the description is longer than 500 words or
extremely technical (helps the content, marketing, and DevRel team)._
>
> _Please also include relevant motivation and context. List any
dependencies that are required for this change. Add links to Notion,
Figma or any other documents that might be relevant to the PR._


Fixes #`Issue Number`  
_or_  
Fixes `Issue URL`
> [!WARNING]  
> _If no issue exists, please create an issue first, and check with the
maintainers if the issue is valid._

## Automation

/ok-to-test tags="@tag.Git"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/12064617641>
> Commit: bf8b3bf7c9048b489246989285e0e4ede3386ca7
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=12064617641&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Git`
> Spec:
> <hr>Thu, 28 Nov 2024 08:38:34 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [ ] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

- **New Features**
	- Updated Git resource type to include `CONTEXT_CONFIG`.
- Introduced methods to manage artifact-dependent resources and context
lists.
	- Enhanced Git resource management with new actions and collections.
- Added new properties and components in the exported application
structure, including new data sources, pages, and actions.

- **Bug Fixes**
	- Improved handling of metadata fields in application exports.

- **Documentation**
	- Added unit tests for artifact JSON to Git resource map conversion.

- **Chores**
- Updated JSON structure for exported applications with new components
and properties.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Nidhi 2024-11-28 14:20:50 +05:30 committed by GitHub
parent 8d32ee8f22
commit 38e3d45fda
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 563 additions and 14 deletions

View File

@ -6,7 +6,7 @@ public enum GitResourceType {
ROOT_CONFIG,
DATASOURCE_CONFIG,
JSLIB_CONFIG,
PAGE_CONFIG,
CONTEXT_CONFIG,
JSOBJECT_CONFIG,
JSOBJECT_DATA,
QUERY_CONFIG,

View File

@ -1,13 +1,18 @@
package com.appsmith.server.applications.git;
import com.appsmith.external.git.FileInterface;
import com.appsmith.external.git.models.GitResourceIdentity;
import com.appsmith.external.git.models.GitResourceMap;
import com.appsmith.external.git.models.GitResourceType;
import com.appsmith.external.helpers.AppsmithBeanUtils;
import com.appsmith.external.models.ActionDTO;
import com.appsmith.external.models.ApplicationGitReference;
import com.appsmith.external.models.ArtifactGitReference;
import com.appsmith.external.models.DatasourceStorage;
import com.appsmith.external.models.PluginType;
import com.appsmith.git.constants.CommonConstants;
import com.appsmith.git.files.FileUtilsImpl;
import com.appsmith.git.helpers.DSLTransformerHelper;
import com.appsmith.server.actioncollections.base.ActionCollectionService;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.ActionCollection;
@ -55,14 +60,16 @@ import java.util.stream.Collectors;
import static com.appsmith.external.git.constants.GitConstants.NAME_SEPARATOR;
import static com.appsmith.external.helpers.AppsmithBeanUtils.copyNestedNonNullProperties;
import static com.appsmith.external.helpers.AppsmithBeanUtils.copyProperties;
import static com.appsmith.server.constants.ce.FieldNameCE.ACTION_COLLECTION_LIST;
import static com.appsmith.server.constants.ce.FieldNameCE.ACTION_LIST;
import static com.appsmith.server.constants.ce.FieldNameCE.CUSTOM_JS_LIB_LIST;
import static com.appsmith.server.constants.ce.FieldNameCE.DATASOURCE_LIST;
import static com.appsmith.server.constants.ce.FieldNameCE.DECRYPTED_FIELDS;
import static com.appsmith.server.constants.ce.FieldNameCE.EDIT_MODE_THEME;
import static com.appsmith.server.constants.ce.FieldNameCE.EXPORTED_APPLICATION;
import static com.appsmith.server.constants.ce.FieldNameCE.PAGE_LIST;
import static com.appsmith.server.constants.FieldName.ACTION_COLLECTION_LIST;
import static com.appsmith.server.constants.FieldName.ACTION_LIST;
import static com.appsmith.server.constants.FieldName.CHILDREN;
import static com.appsmith.server.constants.FieldName.CUSTOM_JS_LIB_LIST;
import static com.appsmith.server.constants.FieldName.DATASOURCE_LIST;
import static com.appsmith.server.constants.FieldName.DECRYPTED_FIELDS;
import static com.appsmith.server.constants.FieldName.EDIT_MODE_THEME;
import static com.appsmith.server.constants.FieldName.EXPORTED_APPLICATION;
import static com.appsmith.server.constants.FieldName.PAGE_LIST;
import static com.appsmith.server.constants.FieldName.WIDGET_ID;
import static com.appsmith.server.helpers.ce.CommonGitFileUtilsCE.removeUnwantedFieldsFromBaseDomain;
@Slf4j
@ -124,6 +131,63 @@ public class ApplicationGitFileUtilsCEImpl implements ArtifactGitFileUtilsCE<App
setCustomJSLibsInApplicationReference(applicationJson, applicationReference);
}
@Override
public void setArtifactDependentResources(
ArtifactExchangeJson artifactExchangeJson, GitResourceMap gitResourceMap) {
ApplicationJson applicationJson = (ApplicationJson) artifactExchangeJson;
Map<GitResourceIdentity, Object> resourceMap = gitResourceMap.getGitResourceMap();
// application
Application application = applicationJson.getExportedApplication();
removeUnwantedFieldsFromApplication(application);
GitResourceIdentity applicationIdentity = new GitResourceIdentity(
GitResourceType.ROOT_CONFIG, CommonConstants.APPLICATION + CommonConstants.JSON_EXTENSION);
resourceMap.put(applicationIdentity, application);
// metadata
Iterable<String> keys = AppsmithBeanUtils.getAllFields(applicationJson.getClass())
.map(Field::getName)
.filter(name -> !getBlockedMetadataFields().contains(name))
.collect(Collectors.toList());
ApplicationJson applicationMetadata = new ApplicationJson();
applicationJson.setModifiedResources(null);
copyProperties(applicationJson, applicationMetadata, keys);
GitResourceIdentity metadataIdentity = new GitResourceIdentity(
GitResourceType.ROOT_CONFIG, CommonConstants.METADATA + CommonConstants.JSON_EXTENSION);
resourceMap.put(metadataIdentity, applicationMetadata);
// pages and widgets
applicationJson.getPageList().stream()
// As we are expecting the commit will happen only after the application is published, so we can safely
// assume if the unpublished version is deleted entity should not be committed to git
.filter(newPage -> newPage.getUnpublishedPage() != null
&& newPage.getUnpublishedPage().getDeletedAt() == null)
.forEach(newPage -> {
removeUnwantedFieldsFromPage(newPage);
JSONObject dsl =
newPage.getUnpublishedPage().getLayouts().get(0).getDsl();
// Get MainContainer widget data, remove the children and club with Canvas.json file
JSONObject mainContainer = new JSONObject(dsl);
mainContainer.remove(CHILDREN);
newPage.getUnpublishedPage().getLayouts().get(0).setDsl(mainContainer);
// pageName will be used for naming the json file
GitResourceIdentity pageIdentity =
new GitResourceIdentity(GitResourceType.CONTEXT_CONFIG, newPage.getGitSyncId());
resourceMap.put(pageIdentity, newPage);
Map<String, org.json.JSONObject> result =
DSLTransformerHelper.flatten(new org.json.JSONObject(dsl.toString()));
result.forEach((key, jsonObject) -> {
String widgetId = newPage.getGitSyncId() + "-" + jsonObject.getString(WIDGET_ID);
GitResourceIdentity widgetIdentity =
new GitResourceIdentity(GitResourceType.WIDGET_CONFIG, widgetId);
resourceMap.put(widgetIdentity, jsonObject);
});
});
}
private void setApplicationInApplicationReference(
ApplicationJson applicationJson, ApplicationGitReference applicationReference) {
Application application = applicationJson.getExportedApplication();
@ -492,7 +556,7 @@ public class ApplicationGitFileUtilsCEImpl implements ArtifactGitFileUtilsCE<App
// For REMOTE plugin like Twilio the user actions are stored in key value pairs and hence they need
// to be
// deserialized separately unlike the body which is stored as string in the db.
if (newAction.getPluginType().toString().equals("REMOTE")) {
if (PluginType.REMOTE.equals(newAction.getPluginType())) {
Map<String, Object> formData = gson.fromJson(actionBody.get(keyName), Map.class);
newAction
.getUnpublishedAction()

View File

@ -133,4 +133,9 @@ public class ApplicationJsonCE implements ArtifactExchangeJsonCE {
public Theme getUnpublishedTheme() {
return this.getEditModeTheme();
}
@Override
public List<NewPage> getContextList() {
return this.pageList;
}
}

View File

@ -3,12 +3,15 @@ package com.appsmith.server.dtos.ce;
import com.appsmith.external.dtos.ModifiedResources;
import com.appsmith.external.models.DatasourceStorage;
import com.appsmith.external.models.DecryptedSensitiveFields;
import com.appsmith.external.views.Views;
import com.appsmith.server.constants.ArtifactType;
import com.appsmith.server.domains.ActionCollection;
import com.appsmith.server.domains.Artifact;
import com.appsmith.server.domains.Context;
import com.appsmith.server.domains.CustomJSLib;
import com.appsmith.server.domains.NewAction;
import com.appsmith.server.domains.Theme;
import com.fasterxml.jackson.annotation.JsonView;
import java.util.List;
import java.util.Map;
@ -62,4 +65,7 @@ public interface ArtifactExchangeJsonCE {
default Theme getPublishedTheme() {
return null;
}
@JsonView(Views.Internal.class)
List<? extends Context> getContextList();
}

View File

@ -4,11 +4,12 @@ import com.appsmith.external.git.FileInterface;
import com.appsmith.external.git.operations.FileOperations;
import com.appsmith.external.models.ApplicationGitReference;
import com.appsmith.git.files.FileUtilsImpl;
import com.appsmith.server.actioncollections.base.ActionCollectionService;
import com.appsmith.server.helpers.ce.CommonGitFileUtilsCE;
import com.appsmith.server.migrations.JsonSchemaVersions;
import com.appsmith.server.newactions.base.NewActionService;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.SessionUserService;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;
@ -24,7 +25,8 @@ public class CommonGitFileUtils extends CommonGitFileUtilsCE {
FileOperations fileOperations,
AnalyticsService analyticsService,
SessionUserService sessionUserService,
Gson gson,
NewActionService newActionService,
ActionCollectionService actionCollectionService,
JsonSchemaVersions jsonSchemaVersions) {
super(
applicationGitFileUtils,
@ -32,6 +34,8 @@ public class CommonGitFileUtils extends CommonGitFileUtilsCE {
fileOperations,
analyticsService,
sessionUserService,
newActionService,
actionCollectionService,
jsonSchemaVersions);
}
}

View File

@ -1,5 +1,6 @@
package com.appsmith.server.helpers.ce;
import com.appsmith.external.git.models.GitResourceMap;
import com.appsmith.external.models.ArtifactGitReference;
import com.appsmith.server.dtos.ArtifactExchangeJson;
import lombok.NonNull;
@ -12,6 +13,8 @@ public interface ArtifactGitFileUtilsCE<T extends ArtifactGitReference> {
T createArtifactReferenceObject();
void setArtifactDependentResources(ArtifactExchangeJson artifactExchangeJson, GitResourceMap gitResourceMap);
Mono<ArtifactExchangeJson> reconstructArtifactExchangeJsonFromFilesInRepository(
String workspaceId, String baseArtifactId, String repoName, String branchName);

View File

@ -2,17 +2,26 @@ package com.appsmith.server.helpers.ce;
import com.appsmith.external.constants.AnalyticsEvents;
import com.appsmith.external.git.FileInterface;
import com.appsmith.external.git.models.GitResourceIdentity;
import com.appsmith.external.git.models.GitResourceMap;
import com.appsmith.external.git.models.GitResourceType;
import com.appsmith.external.git.operations.FileOperations;
import com.appsmith.external.helpers.Stopwatch;
import com.appsmith.external.models.ApplicationGitReference;
import com.appsmith.external.models.ArtifactGitReference;
import com.appsmith.external.models.BaseDomain;
import com.appsmith.external.models.DatasourceStorage;
import com.appsmith.external.models.PluginType;
import com.appsmith.git.constants.CommonConstants;
import com.appsmith.git.files.FileUtilsImpl;
import com.appsmith.server.actioncollections.base.ActionCollectionService;
import com.appsmith.server.constants.ArtifactType;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.ActionCollection;
import com.appsmith.server.domains.CustomJSLib;
import com.appsmith.server.domains.GitArtifactMetadata;
import com.appsmith.server.domains.NewAction;
import com.appsmith.server.domains.Theme;
import com.appsmith.server.dtos.ApplicationJson;
import com.appsmith.server.dtos.ArtifactExchangeJson;
import com.appsmith.server.dtos.PageDTO;
@ -20,6 +29,7 @@ import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.helpers.ArtifactGitFileUtils;
import com.appsmith.server.migrations.JsonSchemaVersions;
import com.appsmith.server.newactions.base.NewActionService;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.SessionUserService;
import com.google.gson.Gson;
@ -40,13 +50,17 @@ import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.appsmith.external.git.constants.ce.GitConstantsCE.GitCommandConstantsCE.CHECKOUT_BRANCH;
import static com.appsmith.external.git.constants.ce.GitConstantsCE.RECONSTRUCT_PAGE;
import static com.appsmith.git.constants.CommonConstants.CLIENT_SCHEMA_VERSION;
import static com.appsmith.git.constants.CommonConstants.FILE_FORMAT_VERSION;
import static com.appsmith.git.constants.CommonConstants.JSON_EXTENSION;
import static com.appsmith.git.constants.CommonConstants.SERVER_SCHEMA_VERSION;
import static com.appsmith.git.constants.CommonConstants.THEME;
import static com.appsmith.git.files.FileUtilsCEImpl.getJsLibFileName;
import static org.springframework.util.StringUtils.hasText;
@Slf4j
@ -61,6 +75,9 @@ public class CommonGitFileUtilsCE {
private final AnalyticsService analyticsService;
private final SessionUserService sessionUserService;
private final NewActionService newActionService;
private final ActionCollectionService actionCollectionService;
// Number of seconds after lock file is stale
@Value("${appsmith.index.lock.file.time}")
public final int INDEX_LOCK_FILE_STALE_TIME = 300;
@ -177,6 +194,178 @@ public class CommonGitFileUtilsCE {
return artifactGitReference;
}
public GitResourceMap createGitResourceMap(ArtifactExchangeJson artifactExchangeJson) {
ArtifactGitFileUtils<?> artifactGitFileUtils =
getArtifactBasedFileHelper(artifactExchangeJson.getArtifactJsonType());
GitResourceMap gitResourceMap = new GitResourceMap();
gitResourceMap.setModifiedResources(artifactExchangeJson.getModifiedResources());
setArtifactIndependentResources(artifactExchangeJson, gitResourceMap);
artifactGitFileUtils.setArtifactDependentResources(artifactExchangeJson, gitResourceMap);
return gitResourceMap;
}
protected void setArtifactIndependentResources(
ArtifactExchangeJson artifactExchangeJson, GitResourceMap gitResourceMap) {
Map<GitResourceIdentity, Object> resourceMap = gitResourceMap.getGitResourceMap();
// datasources
List<DatasourceStorage> datasourceList = artifactExchangeJson.getDatasourceList();
if (datasourceList != null) {
datasourceList.forEach(datasource -> {
removeUnwantedFieldsFromDatasource(datasource);
GitResourceIdentity identity =
new GitResourceIdentity(GitResourceType.DATASOURCE_CONFIG, datasource.getGitSyncId());
resourceMap.put(identity, datasource);
});
}
// themes
Theme theme = artifactExchangeJson.getUnpublishedTheme();
// Only proceed if the current artifact supports themes
if (theme != null) {
// Reset published mode theme since it is not required
artifactExchangeJson.setThemes(theme, null);
// Remove internal fields from the themes
removeUnwantedFieldsFromBaseDomain(theme);
GitResourceIdentity identity = new GitResourceIdentity(GitResourceType.ROOT_CONFIG, THEME + JSON_EXTENSION);
resourceMap.put(identity, theme);
}
// custom js libs
List<CustomJSLib> customJSLibList = artifactExchangeJson.getCustomJSLibList();
if (customJSLibList != null) {
customJSLibList.forEach(jsLib -> {
removeUnwantedFieldsFromBaseDomain(jsLib);
String jsLibFileName = getJsLibFileName(jsLib.getUidString());
GitResourceIdentity identity = new GitResourceIdentity(GitResourceType.JSLIB_CONFIG, jsLibFileName);
resourceMap.put(identity, jsLib);
});
}
// actions
setNewActionsInResourceMap(artifactExchangeJson, resourceMap);
// action collections
setActionCollectionsInResourceMap(artifactExchangeJson, resourceMap);
}
protected void setNewActionsInResourceMap(
ArtifactExchangeJson artifactExchangeJson, Map<GitResourceIdentity, Object> resourceMap) {
if (artifactExchangeJson.getActionList() == null) {
return;
}
artifactExchangeJson.getActionList().stream()
// As we are expecting the commit will happen only after the application is published, so we can safely
// assume if the unpublished version is deleted entity should not be committed to git
.filter(newAction -> newAction.getUnpublishedAction() != null
&& newAction.getUnpublishedAction().getDeletedAt() == null)
.peek(newAction -> newActionService.generateActionByViewMode(newAction, false))
.forEach(newAction -> {
removeUnwantedFieldFromAction(newAction);
String body = newAction.getUnpublishedAction().getActionConfiguration() != null
&& newAction
.getUnpublishedAction()
.getActionConfiguration()
.getBody()
!= null
? newAction
.getUnpublishedAction()
.getActionConfiguration()
.getBody()
: "";
// This is a special case where we are handling REMOTE type plugins based actions such as Twilio
// The user configured values are stored in an attribute called formData which is a map unlike the
// body
if (PluginType.REMOTE.equals(newAction.getPluginType())
&& newAction.getUnpublishedAction().getActionConfiguration() != null
&& newAction
.getUnpublishedAction()
.getActionConfiguration()
.getFormData()
!= null) {
body = new Gson()
.toJson(
newAction
.getUnpublishedAction()
.getActionConfiguration()
.getFormData(),
Map.class);
newAction
.getUnpublishedAction()
.getActionConfiguration()
.setFormData(null);
}
// This is a special case where we are handling JS actions as we don't want to commit the body of JS
// actions
if (PluginType.JS.equals(newAction.getPluginType())) {
if (newAction.getUnpublishedAction().getActionConfiguration() != null) {
newAction
.getUnpublishedAction()
.getActionConfiguration()
.setBody(null);
newAction.getUnpublishedAction().setJsonPathKeys(null);
}
} else {
// For the regular actions we save the body field to git repo
GitResourceIdentity actionDataIdentity =
new GitResourceIdentity(GitResourceType.QUERY_DATA, newAction.getGitSyncId());
resourceMap.put(actionDataIdentity, body);
}
GitResourceIdentity actionConfigIdentity =
new GitResourceIdentity(GitResourceType.QUERY_CONFIG, newAction.getGitSyncId());
resourceMap.put(actionConfigIdentity, newAction);
});
}
protected void setActionCollectionsInResourceMap(
ArtifactExchangeJson artifactExchangeJson, Map<GitResourceIdentity, Object> resourceMap) {
if (artifactExchangeJson.getActionCollectionList() == null) {
return;
}
artifactExchangeJson.getActionCollectionList().stream()
// As we are expecting the commit will happen only after the application is published, so we can safely
// assume if the unpublished version is deleted entity should not be committed to git
.filter(collection -> collection.getUnpublishedCollection() != null
&& collection.getUnpublishedCollection().getDeletedAt() == null)
.peek(actionCollection ->
actionCollectionService.generateActionCollectionByViewMode(actionCollection, false))
.forEach(actionCollection -> {
removeUnwantedFieldFromActionCollection(actionCollection);
String body = actionCollection.getUnpublishedCollection().getBody() != null
? actionCollection.getUnpublishedCollection().getBody()
: "";
actionCollection.getUnpublishedCollection().setBody(null);
GitResourceIdentity collectionConfigIdentity =
new GitResourceIdentity(GitResourceType.JSOBJECT_CONFIG, actionCollection.getGitSyncId());
resourceMap.put(collectionConfigIdentity, actionCollection);
GitResourceIdentity collectionDataIdentity =
new GitResourceIdentity(GitResourceType.JSOBJECT_DATA, actionCollection.getGitSyncId());
resourceMap.put(collectionDataIdentity, body);
});
}
private void removeUnwantedFieldFromAction(NewAction action) {
// As we are publishing the app and then committing to git we expect the published and unpublished ActionDTO
// will be same, so we only commit unpublished ActionDTO.
action.setPublishedAction(null);
action.getUnpublishedAction().sanitiseToExportDBObject();
removeUnwantedFieldsFromBaseDomain(action);
}
private void removeUnwantedFieldFromActionCollection(ActionCollection actionCollection) {
// As we are publishing the app and then committing to git we expect the published and unpublished
// ActionCollectionDTO will be same, so we only commit unpublished ActionCollectionDTO.
actionCollection.setPublishedCollection(null);
actionCollection.getUnpublishedCollection().sanitiseForExport();
removeUnwantedFieldsFromBaseDomain(actionCollection);
}
private void setDatasourcesInArtifactReference(
ArtifactExchangeJson artifactExchangeJson, ArtifactGitReference artifactGitReference) {
Map<String, Object> resourceMap = new HashMap<>();

View File

@ -0,0 +1,94 @@
package com.appsmith.server.git.resourcemap;
import com.appsmith.external.git.models.GitResourceIdentity;
import com.appsmith.external.git.models.GitResourceMap;
import com.appsmith.server.dtos.ArtifactExchangeJson;
import com.appsmith.server.git.resourcemap.templates.contexts.ExchangeJsonContext;
import com.appsmith.server.git.resourcemap.templates.providers.ExchangeJsonTestTemplateProvider;
import com.appsmith.server.helpers.CommonGitFileUtils;
import com.appsmith.server.migrations.JsonSchemaMigration;
import com.google.gson.Gson;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.ClassPathResource;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import reactor.util.function.Tuple2;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class ExchangeJsonConversionTests {
@Autowired
@RegisterExtension
public ExchangeJsonTestTemplateProvider templateProvider;
@Autowired
Gson gson;
@Autowired
JsonSchemaMigration jsonSchemaMigration;
@Autowired
CommonGitFileUtils commonGitFileUtils;
@TestTemplate
public void testConvertArtifactJsonToGitResourceMap_whenArtifactIsFullyPopulated_returnsCorrespondingResourceMap(
ExchangeJsonContext context) throws IOException {
Mono<? extends ArtifactExchangeJson> artifactJsonMono =
createArtifactJson(context).cache();
Mono<? extends Tuple2<GitResourceMap, ? extends ArtifactExchangeJson>> gitResourceMapAndArtifactJsonMono =
artifactJsonMono
.map(artifactJson -> commonGitFileUtils.createGitResourceMap(artifactJson))
.zipWith(artifactJsonMono);
StepVerifier.create(gitResourceMapAndArtifactJsonMono)
.assertNext(tuple2 -> {
GitResourceMap gitResourceMap = tuple2.getT1();
ArtifactExchangeJson exchangeJson = tuple2.getT2();
assertThat(gitResourceMap).isNotNull();
if (exchangeJson.getModifiedResources() == null) {
assertThat(gitResourceMap.getModifiedResources()).isNull();
} else {
assertThat(exchangeJson.getModifiedResources())
.isEqualTo(gitResourceMap.getModifiedResources());
}
Map<GitResourceIdentity, Object> resourceMap = gitResourceMap.getGitResourceMap();
assertThat(resourceMap).isNotNull();
assertThat(resourceMap).hasSize(context.resourceMapKeyCount());
long count = templateProvider.assertResourceComparisons(exchangeJson, resourceMap);
assertThat(count).isEqualTo(context.resourceMapKeyCount());
})
.verifyComplete();
}
private Mono<? extends ArtifactExchangeJson> createArtifactJson(ExchangeJsonContext context) throws IOException {
String filePath = "test_assets/ImportExportServiceTest/" + context.getFileName();
ClassPathResource classPathResource = new ClassPathResource(filePath);
String artifactJson = classPathResource.getContentAsString(Charset.defaultCharset());
Class<? extends ArtifactExchangeJson> exchangeJsonType = context.getArtifactExchangeJsonType();
ArtifactExchangeJson artifactExchangeJson = gson.fromJson(artifactJson, exchangeJsonType);
return jsonSchemaMigration.migrateArtifactExchangeJsonToLatestSchema(artifactExchangeJson, null, null);
}
}

View File

@ -0,0 +1,61 @@
package com.appsmith.server.git.resourcemap.templates.contexts;
import com.appsmith.server.dtos.ArtifactExchangeJson;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
import java.util.List;
public class ExchangeJsonContext implements TestTemplateInvocationContext, ParameterResolver {
private final String fileName;
private final Class<? extends ArtifactExchangeJson> artifactExchangeJsonType;
private final int resourceMapKeyCount;
public ExchangeJsonContext(
String fileName, Class<? extends ArtifactExchangeJson> artifactExchangeJsonType, int resourceMapKeyCount) {
this.fileName = fileName;
this.artifactExchangeJsonType = artifactExchangeJsonType;
this.resourceMapKeyCount = resourceMapKeyCount;
}
@Override
public String getDisplayName(int invocationIndex) {
return fileName;
}
@Override
public List<Extension> getAdditionalExtensions() {
return List.of(this);
}
public String getFileName() {
return fileName;
}
public Class<? extends ArtifactExchangeJson> getArtifactExchangeJsonType() {
return artifactExchangeJsonType;
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
return true;
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
return this;
}
public int resourceMapKeyCount() {
return this.resourceMapKeyCount;
}
}

View File

@ -0,0 +1,7 @@
package com.appsmith.server.git.resourcemap.templates.providers;
import com.appsmith.server.git.resourcemap.templates.providers.ce.ExchangeJsonTestTemplateProviderCE;
import org.springframework.stereotype.Component;
@Component
public class ExchangeJsonTestTemplateProvider extends ExchangeJsonTestTemplateProviderCE {}

View File

@ -0,0 +1,114 @@
package com.appsmith.server.git.resourcemap.templates.providers.ce;
import com.appsmith.external.git.models.GitResourceIdentity;
import com.appsmith.external.git.models.GitResourceType;
import com.appsmith.external.models.PluginType;
import com.appsmith.server.dtos.ApplicationJson;
import com.appsmith.server.dtos.ArtifactExchangeJson;
import com.appsmith.server.git.resourcemap.templates.contexts.ExchangeJsonContext;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
public class ExchangeJsonTestTemplateProviderCE implements TestTemplateInvocationContextProvider {
@Override
public boolean supportsTestTemplate(ExtensionContext extensionContext) {
return true;
}
@Override
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(
ExtensionContext extensionContext) {
ExchangeJsonContext context = new ExchangeJsonContext("valid-application.json", ApplicationJson.class, 23);
return Stream.of(context);
}
public long assertResourceComparisons(
ArtifactExchangeJson exchangeJson, Map<GitResourceIdentity, Object> resourceMap) {
List<Object> datasourceResources = getResourceListByType(resourceMap, GitResourceType.DATASOURCE_CONFIG);
long resourceMapDatasourceCount = datasourceResources.size();
int jsonDatasourceCount = exchangeJson.getDatasourceList() != null
? exchangeJson.getDatasourceList().size()
: 0;
assertThat(resourceMapDatasourceCount).isEqualTo(jsonDatasourceCount);
List<Object> rootResources = getResourceListByType(resourceMap, GitResourceType.ROOT_CONFIG);
long resourceMapRootCount = rootResources.size();
// artifact json, metadata and theme
assertThat(resourceMapRootCount).isEqualTo(3);
List<Object> jsLibResources = getResourceListByType(resourceMap, GitResourceType.JSLIB_CONFIG);
long resourceMapJsLibCount = jsLibResources.size();
int jsonJsLibCount = exchangeJson.getCustomJSLibList() != null
? exchangeJson.getCustomJSLibList().size()
: 0;
assertThat(resourceMapJsLibCount).isEqualTo(jsonJsLibCount);
List<Object> contextResources = getResourceListByType(resourceMap, GitResourceType.CONTEXT_CONFIG);
long resourceMapContextCount = contextResources.size();
int jsonContextCount = exchangeJson.getContextList() != null
? exchangeJson.getContextList().size()
: 0;
assertThat(resourceMapContextCount).isEqualTo(jsonContextCount);
List<Object> jsObjectConfigResources = getResourceListByType(resourceMap, GitResourceType.JSOBJECT_CONFIG);
long resourceMapJsObjectConfigCount = jsObjectConfigResources.size();
int jsonJsObjectCount = exchangeJson.getActionCollectionList() != null
? exchangeJson.getActionCollectionList().size()
: 0;
assertThat(resourceMapJsObjectConfigCount).isEqualTo(jsonJsObjectCount);
List<Object> jsObjectDataResources = getResourceListByType(resourceMap, GitResourceType.JSOBJECT_DATA);
long resourceMapJsObjectDataCount = jsObjectDataResources.size();
assertThat(resourceMapJsObjectDataCount).isEqualTo(jsonJsObjectCount);
List<Object> actionConfigResources = getResourceListByType(resourceMap, GitResourceType.QUERY_CONFIG);
long resourceMapActionConfigCount = actionConfigResources.size();
int jsonActionCount = exchangeJson.getActionList() != null
? exchangeJson.getActionList().size()
: 0;
assertThat(resourceMapActionConfigCount).isEqualTo(jsonActionCount);
List<Object> actionDataResources = getResourceListByType(resourceMap, GitResourceType.QUERY_DATA);
long resourceMapActionDataCount = actionDataResources.size();
long jsonActionDataCount = 0;
if (exchangeJson.getActionList() != null) {
jsonActionDataCount = exchangeJson.getActionList().stream()
.filter(action -> !PluginType.JS.equals(action.getPluginType()))
.count();
}
assertThat(resourceMapActionDataCount).isEqualTo(jsonActionDataCount);
List<Object> widgetResources = getResourceListByType(resourceMap, GitResourceType.WIDGET_CONFIG);
return resourceMapDatasourceCount
+ resourceMapRootCount
+ resourceMapJsLibCount
+ resourceMapContextCount
+ resourceMapJsObjectConfigCount
+ resourceMapJsObjectDataCount
+ resourceMapActionConfigCount
+ resourceMapActionDataCount
+ widgetResources.size();
}
protected List<Object> getResourceListByType(
Map<GitResourceIdentity, Object> resourceMap, GitResourceType resourceType) {
return resourceMap.entrySet().stream()
.filter(entry -> {
GitResourceIdentity key = entry.getKey();
return resourceType.equals(key.getResourceType());
})
.map(Map.Entry::getValue)
.collect(Collectors.toList());
}
}

View File

@ -630,7 +630,8 @@
}
]
},
"new": false
"new": false,
"gitSyncId": "jso1"
},
{
"id": "Page1_JSObject2",
@ -650,7 +651,8 @@
}
]
},
"new": false
"new": false,
"gitSyncId": "jso2"
}
],
"decryptedFields": {