chore: complimentary pr for CD migration changes (#35876)

## Description

> CE changes for CD migration on EE pr:
https://github.com/appsmithorg/appsmith-ee/pull/4927

## 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/10599263403>
> Commit: ef94bff3b0d12db1a5003a78ce8ddfeabbb3c87d
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=10599263403&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Git`
> Spec:
> <hr>Wed, 28 Aug 2024 15:23:46 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


- **New Features**
- Introduced a new field for tracking the continuous deployment
migration version in application metadata.
- Enhanced Git artifact management with additional metadata for improved
deployment processes.

- **Bug Fixes**
- Resolved issues related to application versioning and deployment
tracking.

- **Refactor**
- Substantial restructuring of the Application class for improved
maintainability and extensibility.
- Simplified structure of GitArtifactMetadata, enhancing functionality
and clarity.
  
- **Tests**
- Expanded equality tests for new entities related to the Application
class, ensuring proper validation of equality logic.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Manish Kumar 2024-08-29 15:01:51 +05:30 committed by GitHub
parent db3f35691d
commit a36b45de60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 729 additions and 581 deletions

View File

@ -1,36 +1,16 @@
package com.appsmith.server.domains;
import com.appsmith.external.models.BaseDomain;
import com.appsmith.external.views.Git;
import com.appsmith.external.views.Views;
import com.appsmith.server.constants.ArtifactType;
import com.appsmith.server.dtos.CustomJSLibContextDTO;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonView;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import com.appsmith.server.domains.ce.ApplicationCE;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.FieldNameConstants;
import org.springframework.data.annotation.Transient;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.util.StringUtils;
import java.io.Serializable;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.appsmith.external.helpers.StringUtils.dotted;
import static com.appsmith.server.constants.ResourceModes.EDIT;
import static com.appsmith.server.constants.ResourceModes.VIEW;
import static com.appsmith.server.helpers.DateUtils.ISO_FORMATTER;
@Getter
@Setter
@ -38,349 +18,20 @@ import static com.appsmith.server.helpers.DateUtils.ISO_FORMATTER;
@NoArgsConstructor
@Document
@FieldNameConstants
public class Application extends BaseDomain implements Artifact {
@NotNull @JsonView(Views.Public.class)
String name;
@JsonView(Views.Public.class)
String workspaceId;
// TODO: remove default values from application
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Deprecated(forRemoval = true)
@JsonView(Views.Public.class)
Boolean isPublic = false;
@JsonView({Views.Public.class, Git.class})
List<ApplicationPage> pages;
@JsonView(Views.Internal.class)
List<ApplicationPage> publishedPages;
@JsonView(Views.Internal.class)
@Transient
Boolean viewMode = false;
@Transient
@JsonView({Views.Public.class, Git.class})
boolean appIsExample = false;
@Transient
@JsonView(Views.Public.class)
long unreadCommentThreads;
@JsonView(Views.Internal.class)
String clonedFromApplicationId;
@JsonView({Views.Internal.class, Git.class})
ApplicationDetail unpublishedApplicationDetail;
@JsonView(Views.Internal.class)
ApplicationDetail publishedApplicationDetail;
@JsonView({Views.Public.class, Git.class})
String color;
@JsonView({Views.Public.class, Git.class})
String icon;
@JsonView(Views.Public.class)
private String slug;
@JsonView({Views.Internal.class, Git.class})
AppLayout unpublishedAppLayout;
@JsonView(Views.Internal.class)
AppLayout publishedAppLayout;
@JsonView(Views.Public.class)
Set<CustomJSLibContextDTO> unpublishedCustomJSLibs;
@JsonView(Views.Public.class)
Set<CustomJSLibContextDTO> publishedCustomJSLibs;
@JsonView(Views.Public.class)
GitArtifactMetadata gitApplicationMetadata;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@JsonView(Views.Public.class)
Instant lastDeployedAt; // when this application was last deployed
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@JsonView({Views.Public.class, Git.class})
Integer evaluationVersion;
/**
* applicationVersion will be used when we've a breaking change in application, and it's not possible to write a
* migration. User need to update the application manually.
* In such cases, we can use this field to determine whether we need to notify user about that breaking change
* so that they can update their application.
* Once updated, we should set applicationVersion to latest version as well.
*/
@JsonView({Views.Public.class, Git.class})
Integer applicationVersion;
/**
* Changing name, change in pages, widgets and datasources will set lastEditedAt.
* Other activities e.g. changing policy will not change this property.
* We're adding JsonIgnore here because it'll be exposed as modifiedAt to keep it backward compatible
*/
@JsonView(Views.Internal.class)
Instant lastEditedAt;
@JsonView({Views.Public.class, Git.class})
EmbedSetting embedSetting;
@JsonView({Views.Public.class, Git.class})
Boolean collapseInvisibleWidgets;
/**
* Earlier this was returning value of the updatedAt property in the base domain.
* As this property is modified by the framework when there is any change in domain,
* a new property lastEditedAt has been added to track the edit actions from users.
* This method exposes that property.
*
* @return updated time as a string
*/
@JsonProperty(value = "modifiedAt", access = JsonProperty.Access.READ_ONLY)
@JsonView(Views.Public.class)
public String getLastUpdateTime() {
if (lastEditedAt != null) {
return ISO_FORMATTER.format(lastEditedAt);
}
return null;
}
@JsonView(Views.Public.class)
public String getLastDeployedAt() {
if (lastDeployedAt != null) {
return ISO_FORMATTER.format(lastDeployedAt);
}
return null;
}
@JsonView(Views.Public.class)
Boolean forkingEnabled;
// Field to convey if the application is updated by the user
@JsonView(Views.Public.class)
Boolean isManualUpdate;
// Field to convey if the application is modified from the DB migration
@Transient
@JsonView(Views.Public.class)
Boolean isAutoUpdate;
// To convey current schema version for client and server. This will be used to check if we run the migration
// between 2 commits if the application is connected to git
@JsonView({Views.Internal.class, Git.class})
Integer clientSchemaVersion;
@JsonView({Views.Internal.class, Git.class})
Integer serverSchemaVersion;
@JsonView(Views.Internal.class)
String publishedModeThemeId;
@JsonView(Views.Internal.class)
String editModeThemeId;
// TODO Temporary provision for exporting the application with datasource configuration for the sample/template apps
@JsonView(Views.Public.class)
Boolean exportWithConfiguration;
// forkWithConfiguration represents whether credentials are shared or not while forking an app
@JsonView(Views.Public.class)
Boolean forkWithConfiguration;
// isCommunityTemplate represents whether this application has been published as a community template
@JsonView(Views.Public.class)
Boolean isCommunityTemplate;
/* Template title of the template from which this app was forked, if any */
@JsonView(Views.Public.class)
String forkedFromTemplateTitle;
public class Application extends ApplicationCE implements Artifact {
// This constructor is used during clone application. It only deeply copies selected fields. The rest are either
// initialized newly or is left up to the calling function to set.
public Application(Application application) {
super();
this.workspaceId = application.getWorkspaceId();
this.pages = new ArrayList<>();
this.publishedPages = new ArrayList<>();
this.clonedFromApplicationId = application.getId();
this.color = application.getColor();
this.icon = application.getIcon();
this.unpublishedAppLayout = application.getUnpublishedAppLayout() == null
? null
: new AppLayout(application.getUnpublishedAppLayout().type);
this.publishedAppLayout = application.getPublishedAppLayout() == null
? null
: new AppLayout(application.getPublishedAppLayout().type);
this.setUnpublishedApplicationDetail(new ApplicationDetail());
this.setPublishedApplicationDetail(new ApplicationDetail());
if (application.getUnpublishedApplicationDetail() == null) {
application.setUnpublishedApplicationDetail(new ApplicationDetail());
}
if (application.getPublishedApplicationDetail() == null) {
application.setPublishedApplicationDetail(new ApplicationDetail());
}
AppPositioning unpublishedAppPositioning =
application.getUnpublishedApplicationDetail().getAppPositioning() == null
? null
: new AppPositioning(
application.getUnpublishedApplicationDetail().getAppPositioning().type);
this.getUnpublishedApplicationDetail().setAppPositioning(unpublishedAppPositioning);
AppPositioning publishedAppPositioning =
application.getPublishedApplicationDetail().getAppPositioning() == null
? null
: new AppPositioning(
application.getPublishedApplicationDetail().getAppPositioning().type);
this.getPublishedApplicationDetail().setAppPositioning(publishedAppPositioning);
this.getUnpublishedApplicationDetail()
.setNavigationSetting(
application.getUnpublishedApplicationDetail().getNavigationSetting() == null
? null
: new NavigationSetting());
this.getPublishedApplicationDetail()
.setNavigationSetting(
application.getPublishedApplicationDetail().getNavigationSetting() == null
? null
: new NavigationSetting());
this.getUnpublishedApplicationDetail()
.setThemeSetting(
application.getUnpublishedApplicationDetail().getThemeSetting() == null
? null
: new ThemeSetting());
this.getPublishedApplicationDetail()
.setThemeSetting(
application.getPublishedApplicationDetail().getThemeSetting() == null
? null
: new ThemeSetting());
this.unpublishedCustomJSLibs = application.getUnpublishedCustomJSLibs();
this.collapseInvisibleWidgets = application.getCollapseInvisibleWidgets();
}
public void exportApplicationPages(final Map<String, String> pageIdToNameMap) {
for (ApplicationPage applicationPage : this.getPages()) {
applicationPage.setId(pageIdToNameMap.get(applicationPage.getId() + EDIT));
applicationPage.setDefaultPageId(null);
}
for (ApplicationPage applicationPage : this.getPublishedPages()) {
applicationPage.setId(pageIdToNameMap.get(applicationPage.getId() + VIEW));
applicationPage.setDefaultPageId(null);
}
}
@Override
public String getBaseId() {
if (this.getGitArtifactMetadata() != null
&& StringUtils.hasLength(this.getGitArtifactMetadata().getDefaultArtifactId())) {
return this.getGitArtifactMetadata().getDefaultArtifactId();
}
return Artifact.super.getBaseId();
}
@JsonView(Views.Internal.class)
@Override
public GitArtifactMetadata getGitArtifactMetadata() {
return this.gitApplicationMetadata;
}
@JsonView(Views.Internal.class)
@Override
public void setGitArtifactMetadata(GitArtifactMetadata gitArtifactMetadata) {
this.gitApplicationMetadata = gitArtifactMetadata;
}
@Override
public String getUnpublishedThemeId() {
return this.getEditModeThemeId();
}
@Override
public void setUnpublishedThemeId(String themeId) {
this.setEditModeThemeId(themeId);
}
@Override
public String getPublishedThemeId() {
return this.getPublishedModeThemeId();
}
@Override
public void setPublishedThemeId(String themeId) {
this.setPublishedModeThemeId(themeId);
}
@Override
public void sanitiseToExportDBObject() {
this.setWorkspaceId(null);
this.setModifiedBy(null);
this.setCreatedBy(null);
this.setLastDeployedAt(null);
this.setLastEditedAt(null);
this.setGitApplicationMetadata(null);
this.setEditModeThemeId(null);
this.setPublishedModeThemeId(null);
this.setClientSchemaVersion(null);
this.setServerSchemaVersion(null);
this.setIsManualUpdate(false);
this.setPublishedCustomJSLibs(new HashSet<>());
this.setExportWithConfiguration(null);
this.setForkWithConfiguration(null);
this.setForkingEnabled(null);
super.sanitiseToExportDBObject();
}
public List<ApplicationPage> getPages() {
return Boolean.TRUE.equals(viewMode) ? publishedPages : pages;
}
public AppLayout getAppLayout() {
return Boolean.TRUE.equals(viewMode) ? publishedAppLayout : unpublishedAppLayout;
}
public void setAppLayout(AppLayout appLayout) {
if (Boolean.TRUE.equals(viewMode)) {
publishedAppLayout = appLayout;
} else {
unpublishedAppLayout = appLayout;
}
}
public ApplicationDetail getApplicationDetail() {
return Boolean.TRUE.equals(viewMode) ? publishedApplicationDetail : unpublishedApplicationDetail;
}
public void setApplicationDetail(ApplicationDetail applicationDetail) {
if (Boolean.TRUE.equals(viewMode)) {
publishedApplicationDetail = applicationDetail;
} else {
unpublishedApplicationDetail = applicationDetail;
}
}
@Override
@JsonView({Views.Internal.class})
public ArtifactType getArtifactType() {
return ArtifactType.APPLICATION;
super(application);
}
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public static class AppLayout implements Serializable {
@JsonView({Views.Public.class, Git.class})
Type type;
public enum Type {
DESKTOP,
TABLET_LARGE,
TABLET,
MOBILE,
FLUID,
public static class AppLayout extends AppLayoutCE implements Serializable {
public AppLayout(AppLayout.Type type) {
super(type);
}
}
@ -388,136 +39,40 @@ public class Application extends BaseDomain implements Artifact {
* EmbedSetting is used for embedding Appsmith apps on other platforms
*/
@Data
public static class EmbedSetting {
@JsonView({Views.Public.class, Git.class})
private String height;
@JsonView({Views.Public.class, Git.class})
private String width;
@JsonView({Views.Public.class, Git.class})
private Boolean showNavigationBar;
}
@EqualsAndHashCode(callSuper = true)
public static class EmbedSetting extends EmbedSettingCE {}
/**
* NavigationSetting stores the navigation configuration for the app
*/
@Data
public static class NavigationSetting {
@JsonView({Views.Public.class, Git.class})
private Boolean showNavbar;
@JsonView({Views.Public.class, Git.class})
private String orientation;
@JsonView({Views.Public.class, Git.class})
private String navStyle;
@JsonView({Views.Public.class, Git.class})
private String position;
@JsonView({Views.Public.class, Git.class})
private String itemStyle;
@JsonView({Views.Public.class, Git.class})
private String colorStyle;
@JsonView({Views.Public.class, Git.class})
private String logoAssetId;
@JsonView({Views.Public.class, Git.class})
private String logoConfiguration;
@JsonView({Views.Public.class, Git.class})
private Boolean showSignIn;
}
@EqualsAndHashCode(callSuper = true)
public static class NavigationSetting extends NavigationSettingCE {}
/**
* AppPositioning captures widget positioning Mode of the application
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public static class AppPositioning {
@JsonView({Views.Public.class, Git.class})
Type type;
public static class AppPositioning extends AppPositioningCE {
public AppPositioning(String type) {
setType(Type.valueOf(type));
super(type);
}
public enum Type {
FIXED,
AUTO,
ANVIL
public AppPositioning(AppPositioning.Type type) {
super(type);
}
}
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public static class ThemeSetting {
@JsonView({Views.Public.class, Git.class})
private String accentColor;
@JsonView({Views.Public.class, Git.class})
private String borderRadius;
@JsonView({Views.Public.class, Git.class})
private float sizing = 1;
@JsonView({Views.Public.class, Git.class})
private float density = 1;
@JsonView({Views.Public.class, Git.class})
private String fontFamily;
@JsonView({Views.Public.class, Git.class})
Type colorMode;
@JsonView({Views.Public.class, Git.class})
IconStyle iconStyle;
@JsonView({Views.Public.class, Git.class})
AppMaxWidth appMaxWidth = AppMaxWidth.LARGE;
public static class ThemeSetting extends ThemeSettingCE {
public ThemeSetting(Type colorMode) {
this.colorMode = colorMode;
}
public enum Type {
LIGHT,
DARK
}
public enum IconStyle {
OUTLINED,
FILLED
}
public enum AppMaxWidth {
UNLIMITED,
LARGE,
MEDIUM,
super(colorMode);
}
}
public static class Fields extends BaseDomain.Fields {
public static final String gitApplicationMetadata_gitAuth =
dotted(gitApplicationMetadata, GitArtifactMetadata.Fields.gitAuth);
public static final String gitApplicationMetadata_defaultApplicationId =
dotted(gitApplicationMetadata, GitArtifactMetadata.Fields.defaultApplicationId);
public static final String gitApplicationMetadata_defaultArtifactId =
dotted(gitApplicationMetadata, GitArtifactMetadata.Fields.defaultArtifactId);
public static final String gitApplicationMetadata_isAutoDeploymentEnabled =
dotted(gitApplicationMetadata, GitArtifactMetadata.Fields.isAutoDeploymentEnabled);
public static final String gitApplicationMetadata_branchName =
dotted(gitApplicationMetadata, GitArtifactMetadata.Fields.branchName);
public static final String gitApplicationMetadata_isRepoPrivate =
dotted(gitApplicationMetadata, GitArtifactMetadata.Fields.isRepoPrivate);
public static final String gitApplicationMetadata_isProtectedBranch =
dotted(gitApplicationMetadata, GitArtifactMetadata.Fields.isProtectedBranch);
}
public static class Fields extends ApplicationCE.Fields {}
}

View File

@ -1,124 +1,15 @@
package com.appsmith.server.domains;
import com.appsmith.external.models.AppsmithDomain;
import com.appsmith.external.views.Views;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonView;
import com.appsmith.server.domains.ce.GitArtifactMetadataCE;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.FieldNameConstants;
import org.springframework.data.annotation.Transient;
import org.springframework.util.StringUtils;
import java.time.Instant;
import java.util.List;
import java.util.Map;
// This class will be used for one-to-one mapping for the DB application and the application present in the git repo.
@Data
@FieldNameConstants
public class GitArtifactMetadata implements AppsmithDomain {
// Git branch corresponding to this application, we have one to one mapping for application in DB with git-branch
@JsonView(Views.Public.class)
String branchName;
// Git default branch corresponding to the remote git repo to which the application is connected to
@JsonView(Views.Public.class)
String defaultBranchName;
// Git remote url will be used while pushing and pulling changes
@JsonView(Views.Public.class)
String remoteUrl;
// Git remote https url will be used while checking if the repo is public or private
@JsonView(Views.Public.class)
String browserSupportedRemoteUrl;
// If remote repo is private and will be stored only with default application
@JsonView(Views.Public.class)
Boolean isRepoPrivate;
// The name of git repo
@JsonView(Views.Public.class)
String repoName;
// Default application id used for storing the application files in local volume :
// container-volumes/git_repo/workspaceId/defaultApplicationId/branchName/applicationDirectoryStructure...
@JsonView(Views.Public.class)
String defaultApplicationId;
// We are maintaining this attribute separately from defaultApplicationId to maintain backward compatibility with
// the directory structure that folks might on their file systems
// Default artifact id used for storing the artifact files in local volume :
// container-volumes/git_repo/workspaceId/artifactType/defaultArtifactId/branchName/artifactDirectoryStructure...
@JsonView(Views.Public.class)
String defaultArtifactId;
// Git credentials used to push changes to remote repo and will be stored with default application only to optimise
// space requirement and update operation
@JsonView(Views.Internal.class)
GitAuth gitAuth;
@Transient
@JsonView(Views.Public.class)
Map<String, GitProfile> gitProfiles;
@Transient
@JsonView(Views.Public.class)
String publicKey;
// Deploy key documentation url
@Transient
@JsonView(Views.Public.class)
String docUrl;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssX", timezone = "UTC")
@JsonView(Views.Public.class)
Instant lastCommittedAt;
@JsonView(Views.Metadata.class)
List<String> branchProtectionRules;
/**
* This field is no more used and will be removed in future version.
* Please use the branchProtectionRules field to know whether a branch is protected or not.
*/
@Deprecated
@JsonView(Views.Internal.class)
Boolean isProtectedBranch;
@JsonView(Views.Metadata.class)
AutoCommitConfig autoCommitConfig;
/**
* Boolean flag to store whether auto deployment is enabled for any branch of this application.
* If true, any branch of this application can be automatically deployed using git web hook.
*/
@JsonView(Views.Metadata.class)
boolean isAutoDeploymentEnabled;
public AutoCommitConfig getAutoCommitConfig() {
// by default, the auto commit should be enabled.
// new AutoCommitConfig will have enabled=true so we're returning a new object when field is null
if (autoCommitConfig == null) {
autoCommitConfig = new AutoCommitConfig();
}
return autoCommitConfig;
}
@JsonView(Views.Public.class)
public String getDefaultArtifactId() {
if (StringUtils.hasText(defaultArtifactId)) {
return defaultArtifactId;
} else return defaultApplicationId;
}
// TODO : Set to private to prevent direct access unless migration is performed
private void setDefaultArtifactId(String defaultArtifactId) {
this.defaultArtifactId = defaultArtifactId;
}
public void setDefaultApplicationId(String defaultApplicationId) {
this.defaultApplicationId = defaultApplicationId;
this.defaultArtifactId = defaultApplicationId;
}
@EqualsAndHashCode(callSuper = true)
public class GitArtifactMetadata extends GitArtifactMetadataCE implements AppsmithDomain {
public static class Fields extends GitArtifactMetadataCE.Fields {}
}

View File

@ -0,0 +1,525 @@
package com.appsmith.server.domains.ce;
import com.appsmith.external.models.BaseDomain;
import com.appsmith.external.views.Git;
import com.appsmith.external.views.Views;
import com.appsmith.server.constants.ArtifactType;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.ApplicationDetail;
import com.appsmith.server.domains.ApplicationPage;
import com.appsmith.server.domains.GitArtifactMetadata;
import com.appsmith.server.dtos.CustomJSLibContextDTO;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonView;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.FieldNameConstants;
import org.springframework.data.annotation.Transient;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.util.StringUtils;
import java.io.Serializable;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.appsmith.external.helpers.StringUtils.dotted;
import static com.appsmith.server.constants.ResourceModes.EDIT;
import static com.appsmith.server.constants.ResourceModes.VIEW;
import static com.appsmith.server.helpers.DateUtils.ISO_FORMATTER;
@Getter
@Setter
@ToString
@NoArgsConstructor
@Document
@FieldNameConstants
public class ApplicationCE extends BaseDomain implements ArtifactCE {
@NotNull @JsonView(Views.Public.class)
String name;
@JsonView(Views.Public.class)
String workspaceId;
// TODO: remove default values from application
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Deprecated(forRemoval = true)
@JsonView(Views.Public.class)
Boolean isPublic = false;
@JsonView({Views.Public.class, Git.class})
List<ApplicationPage> pages;
@JsonView(Views.Internal.class)
List<ApplicationPage> publishedPages;
@JsonView(Views.Internal.class)
@Transient
Boolean viewMode = false;
@Transient
@JsonView({Views.Public.class, Git.class})
boolean appIsExample = false;
@Transient
@JsonView(Views.Public.class)
long unreadCommentThreads;
@JsonView(Views.Internal.class)
String clonedFromApplicationId;
@JsonView({Views.Internal.class, Git.class})
ApplicationDetail unpublishedApplicationDetail;
@JsonView(Views.Internal.class)
ApplicationDetail publishedApplicationDetail;
@JsonView({Views.Public.class, Git.class})
String color;
@JsonView({Views.Public.class, Git.class})
String icon;
@JsonView(Views.Public.class)
private String slug;
@JsonView({Views.Internal.class, Git.class})
Application.AppLayout unpublishedAppLayout;
@JsonView(Views.Internal.class)
Application.AppLayout publishedAppLayout;
@JsonView(Views.Public.class)
Set<CustomJSLibContextDTO> unpublishedCustomJSLibs;
@JsonView(Views.Public.class)
Set<CustomJSLibContextDTO> publishedCustomJSLibs;
@JsonView(Views.Public.class)
GitArtifactMetadata gitApplicationMetadata;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@JsonView(Views.Public.class)
Instant lastDeployedAt; // when this application was last deployed
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@JsonView({Views.Public.class, Git.class})
Integer evaluationVersion;
/**
* applicationVersion will be used when we've a breaking change in application, and it's not possible to write a
* migration. User need to update the application manually.
* In such cases, we can use this field to determine whether we need to notify user about that breaking change
* so that they can update their application.
* Once updated, we should set applicationVersion to latest version as well.
*/
@JsonView({Views.Public.class, Git.class})
Integer applicationVersion;
/**
* Changing name, change in pages, widgets and datasources will set lastEditedAt.
* Other activities e.g. changing policy will not change this property.
* We're adding JsonIgnore here because it'll be exposed as modifiedAt to keep it backward compatible
*/
@JsonView(Views.Internal.class)
Instant lastEditedAt;
@JsonView({Views.Public.class, Git.class})
Application.EmbedSetting embedSetting;
@JsonView({Views.Public.class, Git.class})
Boolean collapseInvisibleWidgets;
/**
* Earlier this was returning value of the updatedAt property in the base domain.
* As this property is modified by the framework when there is any change in domain,
* a new property lastEditedAt has been added to track the edit actions from users.
* This method exposes that property.
*
* @return updated time as a string
*/
@JsonProperty(value = "modifiedAt", access = JsonProperty.Access.READ_ONLY)
@JsonView(Views.Public.class)
public String getLastUpdateTime() {
if (lastEditedAt != null) {
return ISO_FORMATTER.format(lastEditedAt);
}
return null;
}
@JsonView(Views.Public.class)
public String getLastDeployedAt() {
if (lastDeployedAt != null) {
return ISO_FORMATTER.format(lastDeployedAt);
}
return null;
}
@JsonView(Views.Public.class)
Boolean forkingEnabled;
// Field to convey if the application is updated by the user
@JsonView(Views.Public.class)
Boolean isManualUpdate;
// Field to convey if the application is modified from the DB migration
@Transient
@JsonView(Views.Public.class)
Boolean isAutoUpdate;
// To convey current schema version for client and server. This will be used to check if we run the migration
// between 2 commits if the application is connected to git
@JsonView({Views.Internal.class, Git.class})
Integer clientSchemaVersion;
@JsonView({Views.Internal.class, Git.class})
Integer serverSchemaVersion;
@JsonView(Views.Internal.class)
String publishedModeThemeId;
@JsonView(Views.Internal.class)
String editModeThemeId;
// TODO Temporary provision for exporting the application with datasource configuration for the sample/template apps
@JsonView(Views.Public.class)
Boolean exportWithConfiguration;
// forkWithConfiguration represents whether credentials are shared or not while forking an app
@JsonView(Views.Public.class)
Boolean forkWithConfiguration;
// isCommunityTemplate represents whether this application has been published as a community template
@JsonView(Views.Public.class)
Boolean isCommunityTemplate;
/* Template title of the template from which this app was forked, if any */
@JsonView(Views.Public.class)
String forkedFromTemplateTitle;
// This constructor is used during clone application. It only deeply copies selected fields. The rest are either
// initialized newly or is left up to the calling function to set.
public ApplicationCE(ApplicationCE application) {
super();
this.workspaceId = application.getWorkspaceId();
this.pages = new ArrayList<>();
this.publishedPages = new ArrayList<>();
this.clonedFromApplicationId = application.getId();
this.color = application.getColor();
this.icon = application.getIcon();
this.unpublishedAppLayout = application.getUnpublishedAppLayout() == null
? null
: new Application.AppLayout(application.getUnpublishedAppLayout().type);
this.publishedAppLayout = application.getPublishedAppLayout() == null
? null
: new Application.AppLayout(application.getPublishedAppLayout().type);
this.setUnpublishedApplicationDetail(new ApplicationDetail());
this.setPublishedApplicationDetail(new ApplicationDetail());
if (application.getUnpublishedApplicationDetail() == null) {
application.setUnpublishedApplicationDetail(new ApplicationDetail());
}
if (application.getPublishedApplicationDetail() == null) {
application.setPublishedApplicationDetail(new ApplicationDetail());
}
Application.AppPositioning unpublishedAppPositioning =
application.getUnpublishedApplicationDetail().getAppPositioning() == null
? null
: new Application.AppPositioning(
application.getUnpublishedApplicationDetail().getAppPositioning().type);
this.getUnpublishedApplicationDetail().setAppPositioning(unpublishedAppPositioning);
Application.AppPositioning publishedAppPositioning =
application.getPublishedApplicationDetail().getAppPositioning() == null
? null
: new Application.AppPositioning(
application.getPublishedApplicationDetail().getAppPositioning().type);
this.getPublishedApplicationDetail().setAppPositioning(publishedAppPositioning);
this.getUnpublishedApplicationDetail()
.setNavigationSetting(
application.getUnpublishedApplicationDetail().getNavigationSetting() == null
? null
: new Application.NavigationSetting());
this.getPublishedApplicationDetail()
.setNavigationSetting(
application.getPublishedApplicationDetail().getNavigationSetting() == null
? null
: new Application.NavigationSetting());
this.getUnpublishedApplicationDetail()
.setThemeSetting(
application.getUnpublishedApplicationDetail().getThemeSetting() == null
? null
: new Application.ThemeSetting());
this.getPublishedApplicationDetail()
.setThemeSetting(
application.getPublishedApplicationDetail().getThemeSetting() == null
? null
: new Application.ThemeSetting());
this.unpublishedCustomJSLibs = application.getUnpublishedCustomJSLibs();
this.collapseInvisibleWidgets = application.getCollapseInvisibleWidgets();
}
public void exportApplicationPages(final Map<String, String> pageIdToNameMap) {
for (ApplicationPage applicationPage : this.getPages()) {
applicationPage.setId(pageIdToNameMap.get(applicationPage.getId() + EDIT));
applicationPage.setDefaultPageId(null);
}
for (ApplicationPage applicationPage : this.getPublishedPages()) {
applicationPage.setId(pageIdToNameMap.get(applicationPage.getId() + VIEW));
applicationPage.setDefaultPageId(null);
}
}
@Override
public String getBaseId() {
if (this.getGitArtifactMetadata() != null
&& StringUtils.hasLength(this.getGitArtifactMetadata().getDefaultArtifactId())) {
return this.getGitArtifactMetadata().getDefaultArtifactId();
}
return ArtifactCE.super.getBaseId();
}
@JsonView(Views.Internal.class)
@Override
public GitArtifactMetadata getGitArtifactMetadata() {
return this.gitApplicationMetadata;
}
@JsonView(Views.Internal.class)
@Override
public void setGitArtifactMetadata(GitArtifactMetadata gitArtifactMetadata) {
this.gitApplicationMetadata = gitArtifactMetadata;
}
@Override
public String getUnpublishedThemeId() {
return this.getEditModeThemeId();
}
@Override
public void setUnpublishedThemeId(String themeId) {
this.setEditModeThemeId(themeId);
}
@Override
public String getPublishedThemeId() {
return this.getPublishedModeThemeId();
}
@Override
public void setPublishedThemeId(String themeId) {
this.setPublishedModeThemeId(themeId);
}
@Override
public void sanitiseToExportDBObject() {
this.setWorkspaceId(null);
this.setModifiedBy(null);
this.setCreatedBy(null);
this.setLastDeployedAt(null);
this.setLastEditedAt(null);
this.setGitApplicationMetadata(null);
this.setEditModeThemeId(null);
this.setPublishedModeThemeId(null);
this.setClientSchemaVersion(null);
this.setServerSchemaVersion(null);
this.setIsManualUpdate(false);
this.setPublishedCustomJSLibs(new HashSet<>());
this.setExportWithConfiguration(null);
this.setForkWithConfiguration(null);
this.setForkingEnabled(null);
super.sanitiseToExportDBObject();
}
public List<ApplicationPage> getPages() {
return Boolean.TRUE.equals(viewMode) ? publishedPages : pages;
}
public Application.AppLayout getAppLayout() {
return Boolean.TRUE.equals(viewMode) ? publishedAppLayout : unpublishedAppLayout;
}
public void setAppLayout(Application.AppLayout appLayout) {
if (Boolean.TRUE.equals(viewMode)) {
publishedAppLayout = appLayout;
} else {
unpublishedAppLayout = appLayout;
}
}
public ApplicationDetail getApplicationDetail() {
return Boolean.TRUE.equals(viewMode) ? publishedApplicationDetail : unpublishedApplicationDetail;
}
public void setApplicationDetail(ApplicationDetail applicationDetail) {
if (Boolean.TRUE.equals(viewMode)) {
publishedApplicationDetail = applicationDetail;
} else {
unpublishedApplicationDetail = applicationDetail;
}
}
@Override
@JsonView({Views.Internal.class})
public ArtifactType getArtifactType() {
return ArtifactType.APPLICATION;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class AppLayoutCE implements Serializable {
@JsonView({Views.Public.class, Git.class})
protected Type type;
public enum Type {
DESKTOP,
TABLET_LARGE,
TABLET,
MOBILE,
FLUID,
}
}
/**
* EmbedSetting is used for embedding Appsmith apps on other platforms
*/
@Data
public static class EmbedSettingCE {
@JsonView({Views.Public.class, Git.class})
private String height;
@JsonView({Views.Public.class, Git.class})
private String width;
@JsonView({Views.Public.class, Git.class})
private Boolean showNavigationBar;
}
/**
* NavigationSetting stores the navigation configuration for the app
*/
@Data
public static class NavigationSettingCE {
@JsonView({Views.Public.class, Git.class})
private Boolean showNavbar;
@JsonView({Views.Public.class, Git.class})
private String orientation;
@JsonView({Views.Public.class, Git.class})
private String navStyle;
@JsonView({Views.Public.class, Git.class})
private String position;
@JsonView({Views.Public.class, Git.class})
private String itemStyle;
@JsonView({Views.Public.class, Git.class})
private String colorStyle;
@JsonView({Views.Public.class, Git.class})
private String logoAssetId;
@JsonView({Views.Public.class, Git.class})
private String logoConfiguration;
@JsonView({Views.Public.class, Git.class})
private Boolean showSignIn;
}
/**
* AppPositioning captures widget positioning Mode of the application
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class AppPositioningCE {
@JsonView({Views.Public.class, Git.class})
protected Type type;
public AppPositioningCE(String type) {
setType(Type.valueOf(type));
}
public enum Type {
FIXED,
AUTO,
ANVIL
}
}
@Data
@NoArgsConstructor
public static class ThemeSettingCE {
@JsonView({Views.Public.class, Git.class})
private String accentColor;
@JsonView({Views.Public.class, Git.class})
private String borderRadius;
@JsonView({Views.Public.class, Git.class})
private float sizing = 1;
@JsonView({Views.Public.class, Git.class})
private float density = 1;
@JsonView({Views.Public.class, Git.class})
private String fontFamily;
@JsonView({Views.Public.class, Git.class})
protected Type colorMode;
@JsonView({Views.Public.class, Git.class})
IconStyle iconStyle;
@JsonView({Views.Public.class, Git.class})
AppMaxWidth appMaxWidth = AppMaxWidth.LARGE;
public ThemeSettingCE(Type colorMode) {
this.colorMode = colorMode;
}
public enum Type {
LIGHT,
DARK
}
public enum IconStyle {
OUTLINED,
FILLED
}
public enum AppMaxWidth {
UNLIMITED,
LARGE,
MEDIUM,
}
}
public static class Fields extends BaseDomain.Fields {
public static final String gitApplicationMetadata_gitAuth =
dotted(gitApplicationMetadata, GitArtifactMetadata.Fields.gitAuth);
public static final String gitApplicationMetadata_defaultApplicationId =
dotted(gitApplicationMetadata, GitArtifactMetadata.Fields.defaultApplicationId);
public static final String gitApplicationMetadata_defaultArtifactId =
dotted(gitApplicationMetadata, GitArtifactMetadata.Fields.defaultArtifactId);
public static final String gitApplicationMetadata_branchName =
dotted(gitApplicationMetadata, GitArtifactMetadata.Fields.branchName);
public static final String gitApplicationMetadata_isRepoPrivate =
dotted(gitApplicationMetadata, GitArtifactMetadata.Fields.isRepoPrivate);
public static final String gitApplicationMetadata_isProtectedBranch =
dotted(gitApplicationMetadata, GitArtifactMetadata.Fields.isProtectedBranch);
}
}

View File

@ -0,0 +1,122 @@
package com.appsmith.server.domains.ce;
import com.appsmith.external.models.AppsmithDomain;
import com.appsmith.external.views.Views;
import com.appsmith.server.domains.AutoCommitConfig;
import com.appsmith.server.domains.GitAuth;
import com.appsmith.server.domains.GitProfile;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonView;
import lombok.Data;
import lombok.experimental.FieldNameConstants;
import org.springframework.data.annotation.Transient;
import org.springframework.util.StringUtils;
import java.time.Instant;
import java.util.List;
import java.util.Map;
// This class will be used for one-to-one mapping for the DB application and the application present in the git repo.
@Data
@FieldNameConstants
public class GitArtifactMetadataCE implements AppsmithDomain {
// Git branch corresponding to this application, we have one to one mapping for application in DB with git-branch
@JsonView(Views.Public.class)
String branchName;
// Git default branch corresponding to the remote git repo to which the application is connected to
@JsonView(Views.Public.class)
String defaultBranchName;
// Git remote url will be used while pushing and pulling changes
@JsonView(Views.Public.class)
String remoteUrl;
// Git remote https url will be used while checking if the repo is public or private
@JsonView(Views.Public.class)
String browserSupportedRemoteUrl;
// If remote repo is private and will be stored only with default application
@JsonView(Views.Public.class)
Boolean isRepoPrivate;
// The name of git repo
@JsonView(Views.Public.class)
String repoName;
// Default application id used for storing the application files in local volume :
// container-volumes/git_repo/workspaceId/defaultApplicationId/branchName/applicationDirectoryStructure...
@JsonView(Views.Public.class)
String defaultApplicationId;
// We are maintaining this attribute separately from defaultApplicationId to maintain backward compatibility with
// the directory structure that folks might on their file systems
// Default artifact id used for storing the artifact files in local volume :
// container-volumes/git_repo/workspaceId/artifactType/defaultArtifactId/branchName/artifactDirectoryStructure...
@JsonView(Views.Public.class)
String defaultArtifactId;
// Git credentials used to push changes to remote repo and will be stored with default application only to optimise
// space requirement and update operation
@JsonView(Views.Internal.class)
GitAuth gitAuth;
@Transient
@JsonView(Views.Public.class)
Map<String, GitProfile> gitProfiles;
@Transient
@JsonView(Views.Public.class)
String publicKey;
// Deploy key documentation url
@Transient
@JsonView(Views.Public.class)
String docUrl;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssX", timezone = "UTC")
@JsonView(Views.Public.class)
Instant lastCommittedAt;
@JsonView(Views.Metadata.class)
List<String> branchProtectionRules;
/**
* This field is no more used and will be removed in future version.
* Please use the branchProtectionRules field to know whether a branch is protected or not.
*/
@Deprecated
@JsonView(Views.Internal.class)
Boolean isProtectedBranch;
@JsonView(Views.Metadata.class)
AutoCommitConfig autoCommitConfig;
public AutoCommitConfig getAutoCommitConfig() {
// by default, the auto commit should be enabled.
// new AutoCommitConfig will have enabled=true so we're returning a new object when field is null
if (autoCommitConfig == null) {
autoCommitConfig = new AutoCommitConfig();
}
return autoCommitConfig;
}
@JsonView(Views.Public.class)
public String getDefaultArtifactId() {
if (StringUtils.hasText(defaultArtifactId)) {
return defaultArtifactId;
} else return defaultApplicationId;
}
// TODO : Set to private to prevent direct access unless migration is performed
private void setDefaultArtifactId(String defaultArtifactId) {
this.defaultArtifactId = defaultArtifactId;
}
public void setDefaultApplicationId(String defaultApplicationId) {
this.defaultApplicationId = defaultApplicationId;
this.defaultArtifactId = defaultApplicationId;
}
public static class Fields {}
}

View File

@ -18,7 +18,11 @@ public class EqualityTest {
private final Set<Class<?>> TESTED_CLASSES = Set.of(
// Note: Adding a class here means that we have a test for its equality in this file.
ApplicationDetail.class, TenantConfiguration.class);
ApplicationDetail.class,
TenantConfiguration.class,
Application.AppLayout.class,
Application.EmbedSetting.class,
GitArtifactMetadata.class);
@SneakyThrows
@Test
@ -72,4 +76,55 @@ public class EqualityTest {
assertThat(d1).isEqualTo(d2);
assertThat(d1).isNotEqualTo(d3);
}
@Test
void testAppLayout() {
Application.AppLayout a1 = new Application.AppLayout(Application.AppLayout.Type.DESKTOP);
Application.AppLayout a2 = new Application.AppLayout(Application.AppLayout.Type.DESKTOP);
Application.AppLayout a3 = new Application.AppLayout(Application.AppLayout.Type.MOBILE);
assertThat(a1).isEqualTo(a2).isNotEqualTo(a3);
}
@Test
void testAppEmbedSetting() {
Application.EmbedSetting a1 = new Application.EmbedSetting();
a1.setHeight("5");
a1.setWidth("5");
a1.setShowNavigationBar(Boolean.TRUE);
Application.EmbedSetting a2 = new Application.EmbedSetting();
a2.setHeight("5");
a2.setWidth("5");
a2.setShowNavigationBar(Boolean.TRUE);
Application.EmbedSetting a3 = new Application.EmbedSetting();
a3.setHeight("5");
a3.setWidth("5");
a3.setShowNavigationBar(Boolean.FALSE);
assertThat(a1).isEqualTo(a2).isNotEqualTo(a3);
}
@Test
void testArtifactEquality() {
String remoteUrl1 = "protocol://domain.superdomain";
String remoteUrl2 = "protocol://domain.superdomain2";
GitArtifactMetadata a1 = new GitArtifactMetadata();
a1.setRemoteUrl(remoteUrl1);
GitArtifactMetadata a2 = new GitArtifactMetadata();
a2.setRemoteUrl(remoteUrl1);
GitArtifactMetadata a3 = new GitArtifactMetadata();
a3.setRemoteUrl(remoteUrl2);
assertThat(a1).isEqualTo(a2).isNotEqualTo(a3);
a1.setAutoCommitConfig(new AutoCommitConfig());
a2.setAutoCommitConfig(new AutoCommitConfig());
a3.setAutoCommitConfig(new AutoCommitConfig());
assertThat(a1).isEqualTo(a2).isNotEqualTo(a3);
a1.getAutoCommitConfig().setEnabled(Boolean.TRUE);
a2.getAutoCommitConfig().setEnabled(Boolean.FALSE);
assertThat(a1).isNotEqualTo(a2).isNotEqualTo(a3);
}
}