fix: duplicate name issue in partial import for queries and jsobjects (#30457)

## Description
When the queries and js objects are imported in the same page, the names
were not updated properly which ended up with duplicate names for
queries and jsobjects. This PR adds a fix which will append the number
in the increasing order to avoid the duplicate entries for the above
scenario.
Example - jsObject will be jsObject1

#### PR fixes following issue(s)
Fixes #30291 

#### Type of change
- Bug fix (non-breaking change which fixes an issue)

## Testing
#### How Has This Been Tested?
- [ ] Manual
- [ ] JUnit

#### Test Plan
> Add Testsmith test cases links that relate to this PR
>
>
#### Issues raised during DP testing
> Link issues raised during DP testing for better visiblity and tracking
(copy link from comments dropped on this PR)
>
>
>
## Checklist:
#### Dev activity
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag


#### QA activity:
- [ ] [Speedbreak
features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-)
have been covered
- [ ] Test plan covers all impacted features and [areas of
interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-)
- [ ] Test plan has been peer reviewed by project stakeholders and other
QA members
- [ ] Manually tested functionality on DP
- [ ] We had an implementation alignment call with stakeholders post QA
Round 2
- [ ] Cypress test cases have been added and approved by SDET/manual QA
- [ ] Added `Test Plan Approved` label after Cypress tests were reviewed
- [ ] Added `Test Plan Approved` label after JUnit tests were reviewed


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

- **New Features**
- Enhanced import functionality to support partial imports, allowing
users to selectively import components into their applications.

- **Refactoring**
- Codebase refactored to improve the clarity and efficiency of the
import services.

- **Tests**
- Expanded test coverage to include new cases for partial imports and
ensure the integrity of import operations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Anagh Hegde 2024-01-24 13:36:19 +05:30 committed by GitHub
parent 10a98f9563
commit ab64bf29af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 1871 additions and 627 deletions

View File

@ -53,8 +53,7 @@ public class ActionCollectionImportableServiceCEImpl implements ImportableServic
MappedImportableResourcesDTO mappedImportableResourcesDTO,
Mono<Workspace> workspaceMono,
Mono<Application> applicationMono,
ApplicationJson applicationJson,
boolean isPartialImport) {
ApplicationJson applicationJson) {
List<ActionCollection> importedActionCollectionList =
CollectionUtils.isEmpty(applicationJson.getActionCollectionList())
? new ArrayList<>()
@ -156,6 +155,15 @@ public class ActionCollectionImportableServiceCEImpl implements ImportableServic
actionCollectionsInBranchesMono = Mono.just(Collections.emptyMap());
}
// update the action name in the json to avoid duplicate names for the partial import
// It is page level action and hence the action name should be unique
if (Boolean.TRUE.equals(importingMetaDTO.getIsPartialImport())
&& mappedImportableResourcesDTO.getRefactoringNameReference() != null) {
updateActionCollectionNameBeforeMerge(
importedActionCollectionList,
mappedImportableResourcesDTO.getRefactoringNameReference());
}
return Mono.zip(actionCollectionsInCurrentAppMono, actionCollectionsInBranchesMono)
.flatMap(objects -> {
Map<String, ActionCollection> actionsCollectionsInCurrentApp = objects.getT1();
@ -323,6 +331,28 @@ public class ActionCollectionImportableServiceCEImpl implements ImportableServic
});
}
private void updateActionCollectionNameBeforeMerge(
List<ActionCollection> importedNewActionCollectionList, Set<String> refactoringNameSet) {
for (ActionCollection actionCollection : importedNewActionCollectionList) {
String
oldNameActionCollection =
actionCollection.getUnpublishedCollection().getName(),
newNameActionCollection =
actionCollection.getUnpublishedCollection().getName();
int i = 1;
while (refactoringNameSet.contains(newNameActionCollection)) {
newNameActionCollection = oldNameActionCollection + i++;
}
String oldId = actionCollection.getId().split("_")[1];
actionCollection.setId(newNameActionCollection + "_" + oldId);
actionCollection.getUnpublishedCollection().setName(newNameActionCollection);
if (actionCollection.getPublishedCollection() != null) {
actionCollection.getPublishedCollection().setName(newNameActionCollection);
}
}
}
protected Flux<ActionCollection> getCollectionsInCurrentAppFlux(Application importedApplication) {
return repository.findByApplicationId(importedApplication.getId());
}

View File

@ -250,8 +250,7 @@ public class ApplicationImportServiceCEImpl implements ApplicationImportServiceC
mappedImportableResourcesDTO,
workspaceMono,
importedApplicationMono,
applicationJson,
false);
applicationJson);
// Requires pageNameMap, pageNameToOldNameMap, pluginMap and actionResultDTO to be present in importable
// resources.
@ -262,8 +261,7 @@ public class ApplicationImportServiceCEImpl implements ApplicationImportServiceC
mappedImportableResourcesDTO,
workspaceMono,
importedApplicationMono,
applicationJson,
false);
applicationJson);
Mono<Void> combinedActionImportablesMono = importedNewActionsMono.then(importedActionCollectionsMono);
return List.of(combinedActionImportablesMono);
@ -443,12 +441,7 @@ public class ApplicationImportServiceCEImpl implements ApplicationImportServiceC
// Persists relevant information and updates mapped resources
return customJSLibImportableService.importEntities(
importingMetaDTO,
mappedImportableResourcesDTO,
null,
null,
(ApplicationJson) artifactExchangeJson,
false);
importingMetaDTO, mappedImportableResourcesDTO, null, null, (ApplicationJson) artifactExchangeJson);
}
@Override
@ -595,9 +588,9 @@ public class ApplicationImportServiceCEImpl implements ApplicationImportServiceC
ImportingMetaDTO importingMetaDTO) {
return Mono.just((Application) importableContext).flatMap(application -> {
return newActionImportableService
.updateImportedEntities(application, importingMetaDTO, mappedImportableResourcesDTO, false)
.updateImportedEntities(application, importingMetaDTO, mappedImportableResourcesDTO)
.then(newPageImportableService.updateImportedEntities(
application, importingMetaDTO, mappedImportableResourcesDTO, false))
application, importingMetaDTO, mappedImportableResourcesDTO))
.thenReturn(application);
});
}
@ -649,8 +642,7 @@ public class ApplicationImportServiceCEImpl implements ApplicationImportServiceC
mappedImportableResourcesDTO,
workspaceMono,
Mono.just(application),
applicationJson,
false);
applicationJson);
// Directly updates required theme information in DB
Mono<Void> importedThemesMono = themeImportableService.importEntities(
@ -659,7 +651,6 @@ public class ApplicationImportServiceCEImpl implements ApplicationImportServiceC
workspaceMono,
Mono.just(application),
applicationJson,
false,
true);
return Flux.merge(List.of(importedPagesMono, importedThemesMono));

View File

@ -63,7 +63,6 @@ public class DatasourceImportableServiceCEImpl implements ImportableServiceCE<Da
Mono<Workspace> workspaceMono,
Mono<? extends ImportableArtifact> importContextMono,
ArtifactExchangeJson importableContextJson,
boolean isPartialImport,
boolean isContextAgnostic) {
return importContextMono.flatMap(importableContext -> {
Application application = (Application) importableContext;
@ -73,8 +72,7 @@ public class DatasourceImportableServiceCEImpl implements ImportableServiceCE<Da
mappedImportableResourcesDTO,
workspaceMono,
Mono.just(application),
applicationJson,
isPartialImport);
applicationJson);
});
}
@ -87,8 +85,7 @@ public class DatasourceImportableServiceCEImpl implements ImportableServiceCE<Da
MappedImportableResourcesDTO mappedImportableResourcesDTO,
Mono<Workspace> workspaceMono,
Mono<Application> applicationMono,
ApplicationJson applicationJson,
boolean isPartialImport) {
ApplicationJson applicationJson) {
return workspaceMono.flatMap(workspace -> {
final Flux<Datasource> existingDatasourceFlux = datasourceService
.getAllByWorkspaceIdWithStorages(workspace.getId(), Optional.empty())

View File

@ -27,6 +27,8 @@ public class ImportingMetaDTO {
*/
Boolean appendToArtifact;
Boolean isPartialImport;
ImportArtifactPermissionProvider permissionProvider;
Set<String> currentUserPermissionGroups;
}

View File

@ -11,6 +11,7 @@ import lombok.NoArgsConstructor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@NoArgsConstructor
@Data
@ -24,6 +25,10 @@ public class MappedImportableResourcesCE_DTO {
// This attribute is re-usable across artifacts according to the needs
Map<String, String> pageOrModuleNewNameToOldName;
// Artifact independent, used in PartialImport
// This attribute contain set of names used/existing in page such as widgetName, action and actionCollection names
Set<String> refactoringNameReference;
/**
* Attribute used to carry objects specific to the context of the Artifacts.
* In case of application it carries the NewPage entity

View File

@ -17,14 +17,12 @@ public interface ImportableServiceCE<T extends BaseDomain> {
MappedImportableResourcesDTO mappedImportableResourcesDTO,
Mono<Workspace> workspaceMono,
Mono<Application> applicationMono,
ApplicationJson applicationJson,
boolean isPartialImport);
ApplicationJson applicationJson);
default Mono<Void> updateImportedEntities(
Application application,
ImportingMetaDTO importingMetaDTO,
MappedImportableResourcesDTO mappedImportableResourcesDTO,
boolean isPartialImport) {
MappedImportableResourcesDTO mappedImportableResourcesDTO) {
return null;
}
@ -34,7 +32,6 @@ public interface ImportableServiceCE<T extends BaseDomain> {
Mono<Workspace> workspaceMono,
Mono<? extends ImportableArtifact> importContextMono,
ArtifactExchangeJson importableContextJson,
boolean isPartialImport,
boolean isContextAgnostic) {
return null;
}

View File

@ -529,7 +529,7 @@ public class ImportApplicationServiceCEImpl implements ImportApplicationServiceC
}
ImportingMetaDTO importingMetaDTO = new ImportingMetaDTO(
workspaceId, applicationId, branchName, appendToApp, permissionProvider, permissionGroups);
workspaceId, applicationId, branchName, appendToApp, false, permissionProvider, permissionGroups);
MappedImportableResourcesDTO mappedImportableResourcesDTO = new MappedImportableResourcesDTO();
@ -578,9 +578,9 @@ public class ImportApplicationServiceCEImpl implements ImportApplicationServiceC
.then(importedApplicationMono)
.flatMap(application -> {
return newActionImportableService
.updateImportedEntities(application, importingMetaDTO, mappedImportableResourcesDTO, false)
.updateImportedEntities(application, importingMetaDTO, mappedImportableResourcesDTO)
.then(newPageImportableService.updateImportedEntities(
application, importingMetaDTO, mappedImportableResourcesDTO, false))
application, importingMetaDTO, mappedImportableResourcesDTO))
.thenReturn(application);
})
.flatMap(application -> {
@ -713,8 +713,7 @@ public class ImportApplicationServiceCEImpl implements ImportApplicationServiceC
mappedImportableResourcesDTO,
workspaceMono,
importedApplicationMono,
applicationJson,
false);
applicationJson);
// Requires pluginMap to be present in importable resources.
// Updates datasourceNameToIdMap in importable resources.
@ -746,8 +745,7 @@ public class ImportApplicationServiceCEImpl implements ImportApplicationServiceC
mappedImportableResourcesDTO,
workspaceMono,
importedApplicationMono,
applicationJson,
false);
applicationJson);
// Requires pageNameMap, pageNameToOldNameMap, pluginMap and actionResultDTO to be present in importable
// resources.
@ -758,8 +756,7 @@ public class ImportApplicationServiceCEImpl implements ImportApplicationServiceC
mappedImportableResourcesDTO,
workspaceMono,
importedApplicationMono,
applicationJson,
false);
applicationJson);
Mono<Void> combinedActionImportablesMono = importedNewActionsMono.then(importedActionCollectionsMono);
return List.of(combinedActionImportablesMono);
@ -771,7 +768,7 @@ public class ImportApplicationServiceCEImpl implements ImportApplicationServiceC
MappedImportableResourcesDTO mappedImportableResourcesDTO) {
// Persists relevant information and updates mapped resources
Mono<Void> installedJsLibsMono = customJSLibImportableService.importEntities(
importingMetaDTO, mappedImportableResourcesDTO, null, null, applicationJson, false);
importingMetaDTO, mappedImportableResourcesDTO, null, null, applicationJson);
return installedJsLibsMono;
}

View File

@ -446,7 +446,7 @@ public class ImportServiceCEImpl implements ImportServiceCE {
}
ImportingMetaDTO importingMetaDTO = new ImportingMetaDTO(
workspaceId, artifactId, branchName, appendToArtifact, permissionProvider, permissionGroups);
workspaceId, artifactId, branchName, appendToArtifact, false, permissionProvider, permissionGroups);
MappedImportableResourcesDTO mappedImportableResourcesDTO = new MappedImportableResourcesDTO();
contextBasedImportService.syncClientAndSchemaVersion(importedDoc);
@ -655,7 +655,6 @@ public class ImportServiceCEImpl implements ImportServiceCE {
workspaceMono,
importedArtifactMono,
artifactExchangeJson,
false,
true);
// Requires pluginMap to be present in importable resources.
@ -667,7 +666,6 @@ public class ImportServiceCEImpl implements ImportServiceCE {
workspaceMono,
importedArtifactMono,
artifactExchangeJson,
false,
true));
// Directly updates required theme information in DB
@ -677,7 +675,6 @@ public class ImportServiceCEImpl implements ImportServiceCE {
workspaceMono,
importedArtifactMono,
artifactExchangeJson,
false,
true);
return Flux.merge(List.of(importedDatasourcesMono, importedThemesMono));

View File

@ -1,6 +1,7 @@
package com.appsmith.server.imports.internal;
import com.appsmith.external.constants.AnalyticsEvents;
import com.appsmith.external.models.CreatorContextType;
import com.appsmith.external.models.Datasource;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.applications.base.ApplicationService;
@ -8,6 +9,7 @@ import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.ActionCollection;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.CustomJSLib;
import com.appsmith.server.domains.Layout;
import com.appsmith.server.domains.NewAction;
import com.appsmith.server.domains.NewPage;
import com.appsmith.server.domains.Plugin;
@ -21,6 +23,7 @@ import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.helpers.ce.ImportArtifactPermissionProvider;
import com.appsmith.server.imports.importable.ImportableService;
import com.appsmith.server.newpages.base.NewPageService;
import com.appsmith.server.refactors.applications.RefactoringService;
import com.appsmith.server.repositories.PermissionGroupRepository;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.SessionUserService;
@ -64,6 +67,7 @@ public class PartialImportServiceCEImpl implements PartialImportServiceCE {
private final ImportableService<NewAction> newActionImportableService;
private final ImportableService<ActionCollection> actionCollectionImportableService;
private final NewPageService newPageService;
private final RefactoringService refactoringService;
@Override
public Mono<Application> importResourceInPage(
@ -101,7 +105,7 @@ public class PartialImportServiceCEImpl implements PartialImportServiceCE {
.cache();
ImportingMetaDTO importingMetaDTO = new ImportingMetaDTO(
workspaceId, applicationId, branchName, false, permissionProvider, null);
workspaceId, applicationId, branchName, false, true, permissionProvider, null);
// Get the Application from DB
Mono<Application> importedApplicationMono = applicationService
@ -111,7 +115,19 @@ public class PartialImportServiceCEImpl implements PartialImportServiceCE {
permissionProvider.getRequiredPermissionOnTargetApplication())
.cache();
return importedApplicationMono
return newPageService
.findByBranchNameAndDefaultPageId(branchName, pageId, AclPermission.MANAGE_PAGES)
.flatMap(page -> {
Layout layout =
page.getUnpublishedPage().getLayouts().get(0);
return refactoringService.getAllExistingEntitiesMono(
page.getId(), CreatorContextType.PAGE, layout.getId(), false);
})
.flatMap(nameSet -> {
// Fetch name of the existing resources in the page to avoid name clashing
mappedImportableResourcesDTO.setRefactoringNameReference(nameSet);
return importedApplicationMono;
})
.flatMap(application -> {
applicationJson.setExportedApplication(application);
return Mono.just(applicationJson);
@ -147,9 +163,9 @@ public class PartialImportServiceCEImpl implements PartialImportServiceCE {
}
return newActionImportableService
.updateImportedEntities(
application, importingMetaDTO, mappedImportableResourcesDTO, true)
application, importingMetaDTO, mappedImportableResourcesDTO)
.then(newPageImportableService.updateImportedEntities(
application, importingMetaDTO, mappedImportableResourcesDTO, true))
application, importingMetaDTO, mappedImportableResourcesDTO))
.thenReturn(application);
});
})
@ -221,7 +237,7 @@ public class PartialImportServiceCEImpl implements PartialImportServiceCE {
true);
Mono<Void> customJsLibMono = customJSLibImportableService.importEntities(
importingMetaDTO, mappedImportableResourcesDTO, null, null, applicationJson, true);
importingMetaDTO, mappedImportableResourcesDTO, null, null, applicationJson);
return pluginMono.then(datasourceMono).then(customJsLibMono).then();
}
@ -237,16 +253,14 @@ public class PartialImportServiceCEImpl implements PartialImportServiceCE {
mappedImportableResourcesDTO,
workspaceMono,
importedApplicationMono,
applicationJson,
true);
applicationJson);
Mono<Void> actionCollectionMono = actionCollectionImportableService.importEntities(
importingMetaDTO,
mappedImportableResourcesDTO,
workspaceMono,
importedApplicationMono,
applicationJson,
true);
applicationJson);
return actionMono.then(actionCollectionMono).then();
}

View File

@ -9,6 +9,7 @@ import com.appsmith.server.domains.NewPage;
import com.appsmith.server.domains.Plugin;
import com.appsmith.server.imports.importable.ImportableService;
import com.appsmith.server.newpages.base.NewPageService;
import com.appsmith.server.refactors.applications.RefactoringService;
import com.appsmith.server.repositories.PermissionGroupRepository;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.SessionUserService;
@ -47,7 +48,8 @@ public class PartialImportServiceImpl extends PartialImportServiceCEImpl impleme
ImportableService<Datasource> datasourceImportableService,
ImportableService<NewAction> newActionImportableService,
ImportableService<ActionCollection> actionCollectionImportableService,
NewPageService newPageService) {
NewPageService newPageService,
RefactoringService refactoringService) {
super(
importApplicationService,
workspaceService,
@ -67,6 +69,7 @@ public class PartialImportServiceImpl extends PartialImportServiceCEImpl impleme
datasourceImportableService,
newActionImportableService,
actionCollectionImportableService,
newPageService);
newPageService,
refactoringService);
}
}

View File

@ -30,8 +30,7 @@ public class CustomJSLibImportableServiceCEImpl implements ImportableServiceCE<C
MappedImportableResourcesDTO mappedImportableResourcesDTO,
Mono<Workspace> workspaceMono,
Mono<Application> applicationMono,
ApplicationJson applicationJson,
boolean isPartialImport) {
ApplicationJson applicationJson) {
List<CustomJSLib> customJSLibs = applicationJson.getCustomJSLibList();
if (customJSLibs == null) {
customJSLibs = new ArrayList<>();

View File

@ -63,8 +63,7 @@ public class NewActionImportableServiceCEImpl implements ImportableServiceCE<New
MappedImportableResourcesDTO mappedImportableResourcesDTO,
Mono<Workspace> workspaceMono,
Mono<Application> applicationMono,
ApplicationJson applicationJson,
boolean isPartialImport) {
ApplicationJson applicationJson) {
List<NewAction> importedNewActionList = applicationJson.getActionList();
@ -104,7 +103,7 @@ public class NewActionImportableServiceCEImpl implements ImportableServiceCE<New
&& CollectionUtils.isNotEmpty(importActionResultDTO.getExistingActions())) {
// Remove unwanted actions
Set<String> invalidActionIds = new HashSet<>();
if (Boolean.FALSE.equals(isPartialImport)) {
if (Boolean.FALSE.equals(importingMetaDTO.getIsPartialImport())) {
for (NewAction action : importActionResultDTO.getExistingActions()) {
if (!importActionResultDTO
.getImportedActionIds()
@ -139,8 +138,7 @@ public class NewActionImportableServiceCEImpl implements ImportableServiceCE<New
public Mono<Void> updateImportedEntities(
Application application,
ImportingMetaDTO importingMetaDTO,
MappedImportableResourcesDTO mappedImportableResourcesDTO,
boolean isPartialImport) {
MappedImportableResourcesDTO mappedImportableResourcesDTO) {
ImportActionResultDTO importActionResultDTO = mappedImportableResourcesDTO.getActionResultDTO();
ImportActionCollectionResultDTO importActionCollectionResultDTO =
@ -162,7 +160,7 @@ public class NewActionImportableServiceCEImpl implements ImportableServiceCE<New
// the git flow only
if (StringUtils.hasText(importingMetaDTO.getArtifactId())
&& !TRUE.equals(importingMetaDTO.getAppendToArtifact())
&& Boolean.FALSE.equals(isPartialImport)) {
&& Boolean.FALSE.equals(importingMetaDTO.getIsPartialImport())) {
// Remove unwanted action collections
Set<String> invalidCollectionIds = new HashSet<>();
for (ActionCollection collection :
@ -240,6 +238,14 @@ public class NewActionImportableServiceCEImpl implements ImportableServiceCE<New
actionsInOtherBranchesMono = Mono.just(Collections.emptyMap());
}
// update the action name in the json to avoid duplicate names for the partial import
// It is page level action and hence the action name should be unique
if (Boolean.TRUE.equals(importingMetaDTO.getIsPartialImport())
&& mappedImportableResourcesDTO.getRefactoringNameReference() != null) {
updateActionNameBeforeMerge(
importedNewActionList, mappedImportableResourcesDTO.getRefactoringNameReference());
}
return Mono.zip(actionsInCurrentAppMono, actionsInOtherBranchesMono)
.flatMap(objects -> {
Map<String, NewAction> actionsInCurrentApp = objects.getT1();
@ -422,6 +428,26 @@ public class NewActionImportableServiceCEImpl implements ImportableServiceCE<New
});
}
private void updateActionNameBeforeMerge(List<NewAction> importedNewActionList, Set<String> refactoringNames) {
for (NewAction newAction : importedNewActionList) {
String oldNameAction = newAction.getUnpublishedAction().getName(),
newNameAction = newAction.getUnpublishedAction().getName();
int i = 1;
while (refactoringNames.contains(newNameAction)) {
newNameAction = oldNameAction + i++;
}
String oldId = newAction.getId().split("_")[1];
newAction.setId(newNameAction + "_" + oldId);
newAction.getUnpublishedAction().setName(newNameAction);
newAction.getUnpublishedAction().setFullyQualifiedName(newNameAction);
if (newAction.getPublishedAction() != null) {
newAction.getPublishedAction().setName(newNameAction);
newAction.getPublishedAction().setFullyQualifiedName(newNameAction);
}
}
}
private void populateNewAction(
ImportingMetaDTO importingMetaDTO,
MappedImportableResourcesDTO mappedImportableResourcesDTO,

View File

@ -68,8 +68,7 @@ public class NewPageImportableServiceCEImpl implements ImportableServiceCE<NewPa
MappedImportableResourcesDTO mappedImportableResourcesDTO,
Mono<Workspace> workspaceMono,
Mono<Application> applicationMono,
ApplicationJson applicationJson,
boolean isPartialImport) {
ApplicationJson applicationJson) {
List<NewPage> importedNewPageList = applicationJson.getPageList();
@ -114,8 +113,7 @@ public class NewPageImportableServiceCEImpl implements ImportableServiceCE<NewPa
public Mono<Void> updateImportedEntities(
Application application,
ImportingMetaDTO importingMetaDTO,
MappedImportableResourcesDTO mappedImportableResourcesDTO,
boolean isPartialImport) {
MappedImportableResourcesDTO mappedImportableResourcesDTO) {
ImportedActionAndCollectionMapsDTO actionAndCollectionMapsDTO =
mappedImportableResourcesDTO.getActionAndCollectionMapsDTO();

View File

@ -35,8 +35,7 @@ public class PluginImportableServiceCEImpl implements ImportableServiceCE<Plugin
MappedImportableResourcesDTO mappedImportableResourcesDTO,
Mono<Workspace> workspaceMono,
Mono<Application> applicationMono,
ApplicationJson applicationJson,
boolean isPartialImport) {
ApplicationJson applicationJson) {
return workspaceMono
.map(workspace -> workspace.getPlugins().stream()
.map(WorkspacePlugin::getPluginId)
@ -65,7 +64,6 @@ public class PluginImportableServiceCEImpl implements ImportableServiceCE<Plugin
Mono<Workspace> workspaceMono,
Mono<? extends ImportableArtifact> importContextMono,
ArtifactExchangeJson importableContextJson,
boolean isPartialImport,
boolean isContextAgnostic) {
return importContextMono.flatMap(importableContext -> {
Application application = (Application) importableContext;
@ -75,8 +73,7 @@ public class PluginImportableServiceCEImpl implements ImportableServiceCE<Plugin
mappedImportableResourcesDTO,
workspaceMono,
Mono.just(application),
applicationJson,
isPartialImport);
applicationJson);
});
}
}

View File

@ -55,8 +55,7 @@ public class ThemeImportableServiceCEImpl implements ImportableServiceCE<Theme>
MappedImportableResourcesDTO mappedImportableResourcesDTO,
Mono<Workspace> workspaceMono,
Mono<Application> applicationMono,
ApplicationJson applicationJson,
boolean isPartialImport) {
ApplicationJson applicationJson) {
if (Boolean.TRUE.equals(importingMetaDTO.getAppendToArtifact())) {
// appending to existing app, theme should not change
return Mono.empty().then();
@ -123,7 +122,6 @@ public class ThemeImportableServiceCEImpl implements ImportableServiceCE<Theme>
Mono<Workspace> workspaceMono,
Mono<? extends ImportableArtifact> importContextMono,
ArtifactExchangeJson importableContextJson,
boolean isPartialImport,
boolean isContextAgnostic) {
return importContextMono.flatMap(importableContext -> {
Application application = (Application) importableContext;
@ -133,8 +131,7 @@ public class ThemeImportableServiceCEImpl implements ImportableServiceCE<Theme>
mappedImportableResourcesDTO,
workspaceMono,
Mono.just(application),
applicationJson,
isPartialImport);
applicationJson);
});
}
}

View File

@ -1,4 +1,4 @@
package com.appsmith.server.services;
package com.appsmith.server.solutions;
import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.ActionDTO;
@ -30,8 +30,12 @@ import com.appsmith.server.repositories.CacheableRepositoryHelper;
import com.appsmith.server.repositories.PermissionGroupRepository;
import com.appsmith.server.repositories.PluginRepository;
import com.appsmith.server.repositories.ThemeRepository;
import com.appsmith.server.solutions.EnvironmentPermission;
import com.appsmith.server.solutions.PagePermission;
import com.appsmith.server.services.ApplicationPageService;
import com.appsmith.server.services.LayoutActionService;
import com.appsmith.server.services.LayoutCollectionService;
import com.appsmith.server.services.PermissionGroupService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.services.WorkspaceService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

View File

@ -0,0 +1,446 @@
package com.appsmith.server.solutions;
import com.appsmith.external.models.DBAuth;
import com.appsmith.external.models.Datasource;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.DatasourceStorageDTO;
import com.appsmith.external.models.DefaultResources;
import com.appsmith.external.models.Property;
import com.appsmith.server.actioncollections.base.ActionCollectionService;
import com.appsmith.server.applications.base.ApplicationService;
import com.appsmith.server.datasources.base.DatasourceService;
import com.appsmith.server.domains.ActionCollection;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.GitApplicationMetadata;
import com.appsmith.server.domains.NewAction;
import com.appsmith.server.domains.Plugin;
import com.appsmith.server.domains.User;
import com.appsmith.server.domains.Workspace;
import com.appsmith.server.dtos.PageDTO;
import com.appsmith.server.helpers.MockPluginExecutor;
import com.appsmith.server.helpers.PluginExecutorHelper;
import com.appsmith.server.imports.internal.PartialImportService;
import com.appsmith.server.newactions.base.NewActionService;
import com.appsmith.server.newpages.base.NewPageService;
import com.appsmith.server.plugins.base.PluginService;
import com.appsmith.server.repositories.ApplicationRepository;
import com.appsmith.server.repositories.CacheableRepositoryHelper;
import com.appsmith.server.repositories.PermissionGroupRepository;
import com.appsmith.server.repositories.PluginRepository;
import com.appsmith.server.repositories.ThemeRepository;
import com.appsmith.server.services.ApplicationPageService;
import com.appsmith.server.services.PermissionGroupService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.services.WorkspaceService;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.MediaType;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.http.codec.multipart.Part;
import org.springframework.security.test.context.support.WithUserDetails;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import reactor.util.function.Tuple3;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
@Slf4j
@ExtendWith(SpringExtension.class)
@SpringBootTest
public class PartialImportServiceTest {
private static final Map<String, Datasource> datasourceMap = new HashMap<>();
private static Plugin installedPlugin;
private static String workspaceId;
private static String defaultEnvironmentId;
private static String testAppId;
private static Datasource jsDatasource;
private static Plugin installedJsPlugin;
private static Boolean isSetupDone = false;
@Autowired
private ApplicationService applicationService;
@Autowired
private NewPageService newPageService;
@Autowired
private SessionUserService sessionUserService;
@Autowired
ApplicationPageService applicationPageService;
@Autowired
PluginRepository pluginRepository;
@Autowired
ApplicationRepository applicationRepository;
@Autowired
DatasourceService datasourceService;
@Autowired
WorkspaceService workspaceService;
@MockBean
PluginExecutorHelper pluginExecutorHelper;
@Autowired
ThemeRepository themeRepository;
@Autowired
PermissionGroupRepository permissionGroupRepository;
@Autowired
PermissionGroupService permissionGroupService;
@Autowired
EnvironmentPermission environmentPermission;
@Autowired
PagePermission pagePermission;
@SpyBean
PluginService pluginService;
@Autowired
CacheableRepositoryHelper cacheableRepositoryHelper;
@Autowired
Gson gson;
@Autowired
PartialImportService partialImportService;
@Autowired
NewActionService newActionService;
@Autowired
ActionCollectionService actionCollectionService;
@BeforeEach
public void setup() {
Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any()))
.thenReturn(Mono.just(new MockPluginExecutor()));
if (Boolean.TRUE.equals(isSetupDone)) {
return;
}
User currentUser = sessionUserService.getCurrentUser().block();
Set<String> beforeCreatingWorkspace =
cacheableRepositoryHelper.getPermissionGroupsOfUser(currentUser).block();
log.info("Permission Groups for User before creating workspace: {}", beforeCreatingWorkspace);
installedPlugin = pluginRepository.findByPackageName("installed-plugin").block();
Workspace workspace = new Workspace();
workspace.setName("Import-Export-Test-Workspace");
Workspace savedWorkspace = workspaceService.create(workspace).block();
workspaceId = savedWorkspace.getId();
defaultEnvironmentId = workspaceService
.getDefaultEnvironmentId(workspaceId, environmentPermission.getExecutePermission())
.block();
Set<String> afterCreatingWorkspace =
cacheableRepositoryHelper.getPermissionGroupsOfUser(currentUser).block();
log.info("Permission Groups for User after creating workspace: {}", afterCreatingWorkspace);
log.info("Workspace ID: {}", workspaceId);
log.info("Workspace Role Ids: {}", workspace.getDefaultPermissionGroups());
log.info("Policy for created Workspace: {}", workspace.getPolicies());
log.info("Current User ID: {}", currentUser.getId());
Application testApplication = new Application();
testApplication.setName("Export-Application-Test-Application");
testApplication.setWorkspaceId(workspaceId);
testApplication.setUpdatedAt(Instant.now());
testApplication.setLastDeployedAt(Instant.now());
testApplication.setModifiedBy("some-user");
testApplication.setGitApplicationMetadata(new GitApplicationMetadata());
Application savedApplication = applicationPageService
.createApplication(testApplication, workspaceId)
.block();
testAppId = savedApplication.getId();
Datasource ds1 = new Datasource();
ds1.setName("DS1");
ds1.setWorkspaceId(workspaceId);
ds1.setPluginId(installedPlugin.getId());
final DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
datasourceConfiguration.setUrl("http://example.org/get");
datasourceConfiguration.setHeaders(List.of(new Property("X-Answer", "42")));
HashMap<String, DatasourceStorageDTO> storages1 = new HashMap<>();
storages1.put(
defaultEnvironmentId, new DatasourceStorageDTO(null, defaultEnvironmentId, datasourceConfiguration));
ds1.setDatasourceStorages(storages1);
Datasource ds2 = new Datasource();
ds2.setName("DS2");
ds2.setPluginId(installedPlugin.getId());
ds2.setWorkspaceId(workspaceId);
DatasourceConfiguration datasourceConfiguration2 = new DatasourceConfiguration();
DBAuth auth = new DBAuth();
auth.setPassword("awesome-password");
datasourceConfiguration2.setAuthentication(auth);
HashMap<String, DatasourceStorageDTO> storages2 = new HashMap<>();
storages2.put(
defaultEnvironmentId, new DatasourceStorageDTO(null, defaultEnvironmentId, datasourceConfiguration2));
ds2.setDatasourceStorages(storages2);
jsDatasource = new Datasource();
jsDatasource.setName("Default JS datasource");
jsDatasource.setWorkspaceId(workspaceId);
installedJsPlugin =
pluginRepository.findByPackageName("installed-js-plugin").block();
assert installedJsPlugin != null;
jsDatasource.setPluginId(installedJsPlugin.getId());
ds1 = datasourceService.create(ds1).block();
ds2 = datasourceService.create(ds2).block();
datasourceMap.put("DS1", ds1);
datasourceMap.put("DS2", ds2);
isSetupDone = true;
}
private Application createGitConnectedApp(String applicationName) {
// Create application connected to git
Application testApplication = new Application();
testApplication.setName(applicationName);
testApplication.setWorkspaceId(workspaceId);
testApplication.setUpdatedAt(Instant.now());
testApplication.setLastDeployedAt(Instant.now());
testApplication.setModifiedBy("some-user");
testApplication.setGitApplicationMetadata(new GitApplicationMetadata());
GitApplicationMetadata gitData = new GitApplicationMetadata();
gitData.setBranchName("master");
gitData.setDefaultBranchName("master");
testApplication.setGitApplicationMetadata(gitData);
return applicationPageService
.createApplication(testApplication, workspaceId)
.flatMap(application1 -> {
application1.getGitApplicationMetadata().setDefaultApplicationId(application1.getId());
return applicationService.save(application1);
})
.block();
}
private FilePart createFilePart(String filePath) {
FilePart filepart = Mockito.mock(FilePart.class, Mockito.RETURNS_DEEP_STUBS);
Flux<DataBuffer> dataBufferFlux = DataBufferUtils.read(
new ClassPathResource(filePath), new DefaultDataBufferFactory(), 4096)
.cache();
Mockito.when(filepart.content()).thenReturn(dataBufferFlux);
Mockito.when(filepart.headers().getContentType()).thenReturn(MediaType.APPLICATION_JSON);
return filepart;
}
@Test
@WithUserDetails(value = "api_user")
public void testPartialImport_nonGitConnectedApp_success() {
// Create an application with all resources
Application testApplication = new Application();
testApplication.setName("testPartialImport_nonGitConnectedApp_success");
testApplication.setWorkspaceId(workspaceId);
testApplication = applicationPageService
.createApplication(testApplication, workspaceId)
.block();
String pageId = newPageService
.findById(testApplication.getPages().get(0).getId(), Optional.empty())
.block()
.getId();
Part filePart = createFilePart("test_assets/ImportExportServiceTest/partial-export-resource.json");
Mono<Tuple3<Application, List<NewAction>, List<ActionCollection>>> result = partialImportService
.importResourceInPage(workspaceId, testApplication.getId(), pageId, null, filePart)
.flatMap(application -> {
Mono<List<NewAction>> actionList = newActionService
.findByPageId(pageId, Optional.empty())
.collectList();
Mono<List<ActionCollection>> actionCollectionList =
actionCollectionService.findByPageId(pageId).collectList();
return Mono.zip(Mono.just(application), actionList, actionCollectionList);
});
StepVerifier.create(result)
.assertNext(object -> {
Application application = object.getT1();
List<NewAction> actionList = object.getT2();
List<ActionCollection> actionCollectionList = object.getT3();
// Verify that the application has the imported resource
assertThat(application.getPages().size()).isEqualTo(1);
assertThat(actionCollectionList.size()).isEqualTo(1);
assertThat(actionCollectionList
.get(0)
.getUnpublishedCollection()
.getName())
.isEqualTo("utils");
assertThat(actionList.size()).isEqualTo(4);
Set<String> actionNames = Set.of("DeleteQuery", "UpdateQuery", "SelectQuery", "InsertQuery");
actionList.forEach(action -> {
assertThat(actionNames.contains(
action.getUnpublishedAction().getName()))
.isTrue();
});
})
.verifyComplete();
}
@Test
@WithUserDetails(value = "api_user")
public void testPartialImport_gitConnectedAppDefaultBranch_success() {
Application application = createGitConnectedApp("testPartialImport_gitConnectedAppDefaultBranch_success");
// update git branch name for page
PageDTO savedPage = new PageDTO();
savedPage.setName("Page 2");
savedPage.setApplicationId(application.getId());
DefaultResources defaultResources = new DefaultResources();
defaultResources.setApplicationId(application.getId());
defaultResources.setBranchName("master");
savedPage.setDefaultResources(defaultResources);
savedPage = applicationPageService
.createPageWithBranchName(savedPage, "master")
.block();
Part filePart = createFilePart("test_assets/ImportExportServiceTest/partial-export-valid-without-widget.json");
PageDTO finalSavedPage = savedPage;
Mono<Tuple3<Application, List<NewAction>, List<ActionCollection>>> result = partialImportService
.importResourceInPage(workspaceId, application.getId(), savedPage.getId(), "master", filePart)
.flatMap(application1 -> {
Mono<List<NewAction>> actionList = newActionService
.findByPageId(finalSavedPage.getId(), Optional.empty())
.collectList();
Mono<List<ActionCollection>> actionCollectionList = actionCollectionService
.findByPageId(finalSavedPage.getId())
.collectList();
return Mono.zip(Mono.just(application1), actionList, actionCollectionList);
});
StepVerifier.create(result)
.assertNext(object -> {
Application application1 = object.getT1();
List<NewAction> actionList = object.getT2();
List<ActionCollection> actionCollectionList = object.getT3();
// Verify that the application has the imported resource
assertThat(application1.getPages().size()).isEqualTo(2);
assertThat(application1.getUnpublishedCustomJSLibs().size()).isEqualTo(1);
assertThat(actionCollectionList.size()).isEqualTo(1);
assertThat(actionCollectionList
.get(0)
.getUnpublishedCollection()
.getName())
.isEqualTo("Github_Transformer");
assertThat(actionList.size()).isEqualTo(1);
Set<String> actionNames = Set.of("get_force_roster");
actionList.forEach(action -> {
assertThat(actionNames.contains(
action.getUnpublishedAction().getName()))
.isTrue();
});
})
.verifyComplete();
}
@Test
@WithUserDetails(value = "api_user")
public void testPartialImport_nameClashInAction_successWithNoNameDuplicates() {
// Create an application with all resources
Application testApplication = new Application();
testApplication.setName("testPartialImport_nameClashInAction_successWithNoNameDuplicates");
testApplication.setWorkspaceId(workspaceId);
testApplication = applicationPageService
.createApplication(testApplication, workspaceId)
.block();
String pageId = newPageService
.findById(testApplication.getPages().get(0).getId(), Optional.empty())
.block()
.getId();
Part filePart = createFilePart("test_assets/ImportExportServiceTest/partial-export-resource.json");
Mono<Tuple3<Application, List<NewAction>, List<ActionCollection>>> result = partialImportService
.importResourceInPage(workspaceId, testApplication.getId(), pageId, null, filePart)
.then(partialImportService.importResourceInPage(
workspaceId, testApplication.getId(), pageId, null, filePart))
.flatMap(application -> {
Mono<List<NewAction>> actionList = newActionService
.findByPageId(pageId, Optional.empty())
.collectList();
Mono<List<ActionCollection>> actionCollectionList =
actionCollectionService.findByPageId(pageId).collectList();
return Mono.zip(Mono.just(application), actionList, actionCollectionList);
});
StepVerifier.create(result)
.assertNext(object -> {
Application application = object.getT1();
List<NewAction> actionList = object.getT2();
List<ActionCollection> actionCollectionList = object.getT3();
// Verify that the application has the imported resource
assertThat(application.getPages().size()).isEqualTo(1);
assertThat(actionCollectionList.size()).isEqualTo(2);
Set<String> nameList = Set.of("utils", "utils1");
actionCollectionList.forEach(collection -> {
assertThat(nameList.contains(
collection.getUnpublishedCollection().getName()))
.isTrue();
});
assertThat(actionList.size()).isEqualTo(8);
Set<String> actionNames = Set.of(
"DeleteQuery",
"UpdateQuery",
"SelectQuery",
"InsertQuery",
"DeleteQuery1",
"UpdateQuery1",
"SelectQuery1",
"InsertQuery1");
actionList.forEach(action -> {
assertThat(actionNames.contains(
action.getUnpublishedAction().getName()))
.isTrue();
});
})
.verifyComplete();
}
}

View File

@ -45,7 +45,6 @@ import reactor.test.StepVerifier;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
// All the test case are for failure or exception. Test cases for valid json file is already present in
// ImportExportApplicationServiceTest class
@ -138,7 +137,7 @@ public class ImportApplicationTransactionServiceTest {
Workspace newWorkspace = new Workspace();
newWorkspace.setName("Template Workspace");
Mockito.when(newActionImportableService.importEntities(any(), any(), any(), any(), any(), anyBoolean()))
Mockito.when(newActionImportableService.importEntities(any(), any(), any(), any(), any()))
.thenReturn(Mono.error(new AppsmithException(AppsmithError.GENERIC_BAD_REQUEST)));
Workspace createdWorkspace = workspaceService.create(newWorkspace).block();
@ -169,7 +168,7 @@ public class ImportApplicationTransactionServiceTest {
Workspace newWorkspace = new Workspace();
newWorkspace.setName("Template Workspace");
Mockito.when(newActionImportableService.importEntities(any(), any(), any(), any(), any(), anyBoolean()))
Mockito.when(newActionImportableService.importEntities(any(), any(), any(), any(), any()))
.thenReturn(Mono.error(new MongoTransactionException(
"Command failed with error 251 (NoSuchTransaction): 'Transaction 1 has been aborted.'")));

View File

@ -0,0 +1,189 @@
{
"clientSchemaVersion": 1,
"serverSchemaVersion": 7,
"datasourceList": [
{
"datasourceConfiguration": {},
"name": "A-force Airtable",
"pluginId": "restapi-plugin",
"messages": [],
"isAutoGenerated": false,
"isValid": true,
"embedded": false,
"new": true
}
],
"customJSLibList": [
{
"name": "xmlParser",
"accessor": [
"xmlParser"
],
"url": "https://cdnjs.cloudflare.com/ajax/libs/fast-xml-parser/3.17.5/parser.min.js",
"version": "3.17.5",
"defs": "{\"!name\":\"LIB/xmlParser\",\"xmlParser\":{\"parse\":{\"!type\":\"fn()\",\"prototype\":{}},\"convertTonimn\":{\"!type\":\"fn()\",\"prototype\":{}},\"getTraversalObj\":{\"!type\":\"fn()\",\"prototype\":{}},\"convertToJson\":{\"!type\":\"fn()\",\"prototype\":{}},\"convertToJsonString\":{\"!type\":\"fn()\",\"prototype\":{}},\"validate\":{\"!type\":\"fn()\",\"prototype\":{}},\"j2xParser\":{\"!type\":\"fn()\",\"prototype\":{\"parse\":{\"!type\":\"fn()\",\"prototype\":{}},\"j2x\":{\"!type\":\"fn()\",\"prototype\":{}}}},\"parseToNimn\":{\"!type\":\"fn()\",\"prototype\":{}}}}",
"userPermissions": [],
"uidString": "xmlParser_https://cdnjs.cloudflare.com/ajax/libs/fast-xml-parser/3.17.5/parser.min.js",
"new": true
}
],
"actionList": [
{
"id": "Todays Updates_get_force_roster",
"pluginType": "API",
"pluginId": "restapi-plugin",
"unpublishedAction": {
"name": "get_force_roster",
"datasource": {
"id": "A-force Airtable",
"userPermissions": [],
"name": "A-force Airtable",
"pluginId": "restapi-plugin",
"messages": [],
"isValid": true,
"new": false
},
"pageId": "Todays Updates",
"actionConfiguration": {
"timeoutInMillisecond": 10000,
"paginationType": "NONE",
"path": "/v0/appgSZSXDttNUK57V/Roster",
"headers": [],
"autoGeneratedHeaders": [],
"encodeParamsToggle": true,
"queryParameters": [
{
"key": "view",
"value": "Grid view"
},
{
"key": "sort[0][field]",
"value": "StartDate"
},
{
"key": "sort[0][direction]",
"value": "desc"
},
{
"key": "maxRecords",
"value": "20"
}
],
"httpMethod": "GET",
"selfReferencingDataPaths": [],
"pluginSpecifiedTemplates": [
{
"value": false
}
]
},
"executeOnLoad": true,
"dynamicBindingPathList": [],
"isValid": true,
"invalids": [],
"messages": [],
"jsonPathKeys": [],
"confirmBeforeExecute": false,
"userPermissions": [],
"validName": "get_force_roster",
"selfReferencingDataPaths": []
},
"publishedAction": {
"name": "get_force_roster",
"datasource": {
"id": "A-force Airtable",
"userPermissions": [],
"name": "A-force Airtable",
"pluginId": "restapi-plugin",
"messages": [],
"isValid": true,
"new": false
},
"pageId": "Todays Updates",
"actionConfiguration": {
"timeoutInMillisecond": 10000,
"paginationType": "NONE",
"path": "/v0/appgSZSXDttNUK57V/Roster",
"headers": [],
"autoGeneratedHeaders": [],
"encodeParamsToggle": true,
"queryParameters": [
{
"key": "view",
"value": "Grid view"
},
{
"key": "sort[0][field]",
"value": "StartDate"
},
{
"key": "sort[0][direction]",
"value": "desc"
},
{
"key": "maxRecords",
"value": "20"
}
],
"httpMethod": "GET",
"selfReferencingDataPaths": [],
"pluginSpecifiedTemplates": [
{
"value": false
}
]
},
"executeOnLoad": true,
"dynamicBindingPathList": [],
"isValid": true,
"invalids": [],
"messages": [],
"jsonPathKeys": [],
"confirmBeforeExecute": false,
"userPermissions": [],
"validName": "get_force_roster",
"selfReferencingDataPaths": []
},
"new": false
}
],
"actionCollectionList": [
{
"id": "Todays Updates_Github_Transformer",
"unpublishedCollection": {
"name": "Github_Transformer",
"pageId": "Todays Updates",
"pluginId": "js-plugin",
"pluginType": "JS",
"actions": [],
"archivedActions": [],
"body": "export default {\n\tresults: [],\n\tgetCriticalIssues: () => {\n\t\treturn fetchCriticalIssues.data.map((issue) => { \n\t\t\treturn { \n\t\t\t\ttitle: issue.title, \n\t\t\t\tuser: issue.user.login, \n\t\t\t\tage: moment(issue.created_at).fromNow(), \n\t\t\t\tlabels: issue.labels.map((label) => label.name).join(', '),\n\t\t\t\tassignees: issue.assignees.map((label) => label.login).join(', '), \n\t\t\t\tlink: issue.html_url, \n\t\t\t\tbody: issue.body, \n\t\t\t\tnumber: issue.number,\n\t\t\t}\n\t\t})\n\t}\n}",
"variables": [
{
"name": "results",
"value": "[]"
}
],
"userPermissions": []
},
"publishedCollection": {
"name": "Github_Transformer",
"pageId": "Todays Updates",
"pluginId": "js-plugin",
"pluginType": "JS",
"actions": [],
"archivedActions": [],
"body": "export default {\n\tresults: [],\n\tgetCriticalIssues: () => {\n\t\treturn fetchCriticalIssues.data.map((issue) => { \n\t\t\treturn { \n\t\t\t\ttitle: issue.title, \n\t\t\t\tuser: issue.user.login, \n\t\t\t\tage: moment(issue.created_at).fromNow(), \n\t\t\t\tlabels: issue.labels.map((label) => label.name).join(', '),\n\t\t\t\tassignees: issue.assignees.map((label) => label.login).join(', '), \n\t\t\t\tlink: issue.html_url, \n\t\t\t\tbody: issue.body, \n\t\t\t\tnumber: issue.number,\n\t\t\t}\n\t\t})\n\t}\n}",
"variables": [
{
"name": "results",
"value": "[]"
}
],
"userPermissions": []
},
"new": false
}
],
"widgets": ""
}