diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCE.java index 68473742e46a..a084fbc51ff5 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCE.java @@ -7,9 +7,12 @@ import com.appsmith.server.constants.ArtifactType; import com.appsmith.server.domains.Artifact; import com.appsmith.server.dtos.ArtifactImportDTO; +import com.appsmith.server.dtos.AutoCommitResponseDTO; import com.appsmith.server.dtos.GitConnectDTO; import reactor.core.publisher.Mono; +import java.util.List; + public interface CentralGitServiceCE { Mono importArtifactFromGit( @@ -52,4 +55,14 @@ Mono createReference( Mono deleteGitReference( String baseArtifactId, GitRefDTO gitRefDTO, ArtifactType artifactType, GitType gitType); + + Mono> updateProtectedBranches( + String baseArtifactId, List branchNames, ArtifactType artifactType); + + Mono> getProtectedBranches(String baseArtifactId, ArtifactType artifactType); + + Mono toggleAutoCommitEnabled(String baseArtifactId, ArtifactType artifactType); + + Mono getAutoCommitProgress( + String baseArtifactId, String branchName, ArtifactType artifactType); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCECompatibleImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCECompatibleImpl.java index f33fa75b1286..9c80cfa47cd9 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCECompatibleImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCECompatibleImpl.java @@ -3,6 +3,7 @@ import com.appsmith.server.datasources.base.DatasourceService; import com.appsmith.server.exports.internal.ExportService; import com.appsmith.server.git.GitRedisUtils; +import com.appsmith.server.git.autocommit.helpers.GitAutoCommitHelper; import com.appsmith.server.git.resolver.GitArtifactHelperResolver; import com.appsmith.server.git.resolver.GitHandlingServiceResolver; import com.appsmith.server.git.utils.GitAnalyticsUtils; @@ -17,6 +18,7 @@ import io.micrometer.observation.ObservationRegistry; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.reactive.TransactionalOperator; @Slf4j @Service @@ -24,6 +26,7 @@ public class CentralGitServiceCECompatibleImpl extends CentralGitServiceCEImpl implements CentralGitServiceCECompatible { public CentralGitServiceCECompatibleImpl( + GitRedisUtils gitRedisUtils, GitProfileUtils gitProfileUtils, GitAnalyticsUtils gitAnalyticsUtils, UserDataService userDataService, @@ -37,9 +40,11 @@ public CentralGitServiceCECompatibleImpl( PluginService pluginService, ImportService importService, ExportService exportService, - GitRedisUtils gitRedisUtils, + GitAutoCommitHelper gitAutoCommitHelper, + TransactionalOperator transactionalOperator, ObservationRegistry observationRegistry) { super( + gitRedisUtils, gitProfileUtils, gitAnalyticsUtils, userDataService, @@ -53,7 +58,8 @@ public CentralGitServiceCECompatibleImpl( pluginService, importService, exportService, - gitRedisUtils, + gitAutoCommitHelper, + transactionalOperator, observationRegistry); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCEImpl.java index 96b0b2747f24..d235ed7611d7 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceCEImpl.java @@ -16,6 +16,7 @@ import com.appsmith.server.constants.GitDefaultCommitMessage; import com.appsmith.server.datasources.base.DatasourceService; import com.appsmith.server.domains.Artifact; +import com.appsmith.server.domains.AutoCommitConfig; import com.appsmith.server.domains.GitArtifactMetadata; import com.appsmith.server.domains.GitAuth; import com.appsmith.server.domains.GitProfile; @@ -25,11 +26,13 @@ import com.appsmith.server.domains.Workspace; import com.appsmith.server.dtos.ArtifactExchangeJson; import com.appsmith.server.dtos.ArtifactImportDTO; +import com.appsmith.server.dtos.AutoCommitResponseDTO; import com.appsmith.server.dtos.GitConnectDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.exports.internal.ExportService; import com.appsmith.server.git.GitRedisUtils; +import com.appsmith.server.git.autocommit.helpers.GitAutoCommitHelper; import com.appsmith.server.git.dtos.ArtifactJsonTransformationDTO; import com.appsmith.server.git.resolver.GitArtifactHelperResolver; import com.appsmith.server.git.resolver.GitHandlingServiceResolver; @@ -50,6 +53,7 @@ import org.eclipse.jgit.api.errors.TransportException; import org.eclipse.jgit.lib.BranchTrackingStatus; import org.springframework.stereotype.Service; +import org.springframework.transaction.reactive.TransactionalOperator; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import reactor.core.observability.micrometer.Micrometer; @@ -60,6 +64,7 @@ import java.io.IOException; import java.time.Instant; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; @@ -85,6 +90,7 @@ @RequiredArgsConstructor public class CentralGitServiceCEImpl implements CentralGitServiceCE { + private final GitRedisUtils gitRedisUtils; private final GitProfileUtils gitProfileUtils; private final GitAnalyticsUtils gitAnalyticsUtils; private final UserDataService userDataService; @@ -104,7 +110,8 @@ public class CentralGitServiceCEImpl implements CentralGitServiceCE { private final ImportService importService; private final ExportService exportService; - private final GitRedisUtils gitRedisUtils; + private final GitAutoCommitHelper gitAutoCommitHelper; + private final TransactionalOperator transactionalOperator; private final ObservationRegistry observationRegistry; protected static final String ORIGIN = "origin/"; @@ -1765,4 +1772,127 @@ protected Mono discardChanges(Artifact branchedArtifact, Git return Mono.create(sink -> recreatedArtifactFromLastCommit.subscribe(sink::success, sink::error, null, sink.currentContext())); } + + @Override + public Mono> updateProtectedBranches( + String baseArtifactId, List branchNames, ArtifactType artifactType) { + + GitArtifactHelper gitArtifactHelper = gitArtifactHelperResolver.getArtifactHelper(artifactType); + AclPermission artifactManageProtectedBranchPermission = + gitArtifactHelper.getArtifactManageProtectedBranchPermission(); + + Mono baseArtifactMono = + gitArtifactHelper.getArtifactById(baseArtifactId, artifactManageProtectedBranchPermission); + + return baseArtifactMono + .flatMap(baseArtifact -> { + GitArtifactMetadata baseGitData = baseArtifact.getGitArtifactMetadata(); + final String defaultBranchName = baseGitData.getDefaultBranchName(); + final List incomingProtectedBranches = + CollectionUtils.isEmpty(branchNames) ? new ArrayList<>() : branchNames; + + // user cannot protect multiple branches + if (incomingProtectedBranches.size() > 1) { + return Mono.error(new AppsmithException(AppsmithError.UNSUPPORTED_OPERATION)); + } + + // user cannot protect a branch which is not default + if (incomingProtectedBranches.size() == 1 + && !defaultBranchName.equals(incomingProtectedBranches.get(0))) { + return Mono.error(new AppsmithException(AppsmithError.UNSUPPORTED_OPERATION)); + } + + return updateProtectedBranchesInArtifactAfterVerification(baseArtifact, incomingProtectedBranches); + }) + .as(transactionalOperator::transactional); + } + + protected Mono> updateProtectedBranchesInArtifactAfterVerification( + Artifact baseArtifact, List branchNames) { + GitArtifactHelper gitArtifactHelper = + gitArtifactHelperResolver.getArtifactHelper(baseArtifact.getArtifactType()); + GitArtifactMetadata baseGitData = baseArtifact.getGitArtifactMetadata(); + + // keep a copy of old protected branches as it's required to send analytics event later + List oldProtectedBranches = baseGitData.getBranchProtectionRules() == null + ? new ArrayList<>() + : baseGitData.getBranchProtectionRules(); + + baseGitData.setBranchProtectionRules(branchNames); + return gitArtifactHelper + .saveArtifact(baseArtifact) + .then(gitArtifactHelper.updateArtifactWithProtectedBranches(baseArtifact.getId(), branchNames)) + .then(gitAnalyticsUtils.sendBranchProtectionAnalytics(baseArtifact, oldProtectedBranches, branchNames)) + .thenReturn(branchNames); + } + + @Override + public Mono> getProtectedBranches(String baseArtifactId, ArtifactType artifactType) { + + GitArtifactHelper gitArtifactHelper = gitArtifactHelperResolver.getArtifactHelper(artifactType); + AclPermission artifactEditPermission = gitArtifactHelper.getArtifactEditPermission(); + + Mono baseArtifactMono = + gitArtifactHelper.getArtifactById(baseArtifactId, artifactEditPermission); + + return baseArtifactMono.map(defaultArtifact -> { + GitArtifactMetadata gitArtifactMetadata = defaultArtifact.getGitArtifactMetadata(); + /* + user may have multiple branches as protected, but we only return the default branch + as protected branch if it's present in the list of protected branches + */ + List protectedBranches = gitArtifactMetadata.getBranchProtectionRules(); + String defaultBranchName = gitArtifactMetadata.getDefaultBranchName(); + + if (CollectionUtils.isEmpty(protectedBranches) || !protectedBranches.contains(defaultBranchName)) { + return List.of(); + } + + return List.of(defaultBranchName); + }); + } + + @Override + public Mono toggleAutoCommitEnabled(String baseArtifactId, ArtifactType artifactType) { + + GitArtifactHelper gitArtifactHelper = gitArtifactHelperResolver.getArtifactHelper(artifactType); + AclPermission artifactAutoCommitPermission = gitArtifactHelper.getArtifactAutoCommitPermission(); + + Mono baseArtifactMono = + gitArtifactHelper.getArtifactById(baseArtifactId, artifactAutoCommitPermission); + // get base app + + return baseArtifactMono + .map(baseArtifact -> { + GitArtifactMetadata baseGitMetadata = baseArtifact.getGitArtifactMetadata(); + if (!baseArtifact.getId().equals(baseGitMetadata.getDefaultArtifactId())) { + log.error( + "failed to toggle auto commit. reason: {} is not the id of the base Artifact", + baseArtifactId); + throw new AppsmithException(AppsmithError.INVALID_PARAMETER, "default baseArtifact id"); + } + + AutoCommitConfig autoCommitConfig = baseGitMetadata.getAutoCommitConfig(); + if (autoCommitConfig.getEnabled()) { + autoCommitConfig.setEnabled(FALSE); + } else { + autoCommitConfig.setEnabled(TRUE); + } + // need to call the setter because getter returns a default config if attribute is null + baseArtifact.getGitArtifactMetadata().setAutoCommitConfig(autoCommitConfig); + return baseArtifact; + }) + .flatMap(baseArtifact -> gitArtifactHelper + .saveArtifact(baseArtifact) + .thenReturn(baseArtifact + .getGitArtifactMetadata() + .getAutoCommitConfig() + .getEnabled())); + } + + @Override + public Mono getAutoCommitProgress( + String artifactId, String branchName, ArtifactType artifactType) { + return gitAutoCommitHelper.getAutoCommitProgress(artifactId, branchName); + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceImpl.java index 0a85050f1ffa..7e1843a4b0ba 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/central/CentralGitServiceImpl.java @@ -3,6 +3,7 @@ import com.appsmith.server.datasources.base.DatasourceService; import com.appsmith.server.exports.internal.ExportService; import com.appsmith.server.git.GitRedisUtils; +import com.appsmith.server.git.autocommit.helpers.GitAutoCommitHelper; import com.appsmith.server.git.resolver.GitArtifactHelperResolver; import com.appsmith.server.git.resolver.GitHandlingServiceResolver; import com.appsmith.server.git.utils.GitAnalyticsUtils; @@ -17,12 +18,14 @@ import io.micrometer.observation.ObservationRegistry; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.reactive.TransactionalOperator; @Slf4j @Service public class CentralGitServiceImpl extends CentralGitServiceCECompatibleImpl implements CentralGitService { public CentralGitServiceImpl( + GitRedisUtils gitRedisUtils, GitProfileUtils gitProfileUtils, GitAnalyticsUtils gitAnalyticsUtils, UserDataService userDataService, @@ -36,9 +39,11 @@ public CentralGitServiceImpl( PluginService pluginService, ImportService importService, ExportService exportService, - GitRedisUtils gitRedisUtils, + GitAutoCommitHelper gitAutoCommitHelper, + TransactionalOperator transactionalOperator, ObservationRegistry observationRegistry) { super( + gitRedisUtils, gitProfileUtils, gitAnalyticsUtils, userDataService, @@ -52,7 +57,8 @@ public CentralGitServiceImpl( pluginService, importService, exportService, - gitRedisUtils, + gitAutoCommitHelper, + transactionalOperator, observationRegistry); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/utils/GitAnalyticsUtils.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/utils/GitAnalyticsUtils.java index 1cc0f71d61f5..a75e8f6a1ded 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/git/utils/GitAnalyticsUtils.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/git/utils/GitAnalyticsUtils.java @@ -13,11 +13,16 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import static com.appsmith.external.constants.AnalyticsEvents.GIT_ADD_PROTECTED_BRANCH; +import static com.appsmith.external.constants.AnalyticsEvents.GIT_REMOVE_PROTECTED_BRANCH; import static org.apache.commons.lang.ObjectUtils.defaultIfNull; @Slf4j @@ -146,4 +151,40 @@ public Mono sendUnitExecutionTimeAnalyticsEvent( return analyticsService.sendEvent( AnalyticsEvents.UNIT_EXECUTION_TIME.getEventName(), currentUser.getUsername(), data); } + + /** + * Sends one or more analytics events when there's a change in protected branches. + * If n number of branches are un-protected and m number of branches are protected, it'll send m+n number of + * events. It receives the list of branches before and after the action. + * For example, if user has "main" and "develop" branches as protected and wants to include "staging" branch as + * protected as well, then oldProtectedBranches will be ["main", "develop"] and newProtectedBranches will be + * ["main", "develop", "staging"] + * + * @param artifact Application object of the root artifact + * @param oldProtectedBranches List of branches that were protected before this action. + * @param newProtectedBranches List of branches that are going to be protected. + * @return An empty Mono + */ + public Mono sendBranchProtectionAnalytics( + Artifact artifact, List oldProtectedBranches, List newProtectedBranches) { + List itemsAdded = new ArrayList<>(newProtectedBranches); // add all new items + itemsAdded.removeAll(oldProtectedBranches); // remove the items that were present earlier + + List itemsRemoved = new ArrayList<>(oldProtectedBranches); // add all old items + itemsRemoved.removeAll(newProtectedBranches); // remove the items that are also present in new list + + List> eventSenderMonos = new ArrayList<>(); + + // send an analytics event for each removed branch + for (String branchName : itemsRemoved) { + eventSenderMonos.add(addAnalyticsForGitOperation(GIT_REMOVE_PROTECTED_BRANCH, branchName, artifact)); + } + + // send an analytics event for each newly protected branch + for (String branchName : itemsAdded) { + eventSenderMonos.add(addAnalyticsForGitOperation(GIT_ADD_PROTECTED_BRANCH, branchName, artifact)); + } + + return Flux.merge(eventSenderMonos).then(); + } }