chore: autocommit migration for annotation and embedded datasource changes. (#36261)

## Description
- Added autocommit migration to avoid uncommited changes on some of the
applications.


Fixes #`Issue Number`  
_or_  
Fixes `Issue URL`

## Automation

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

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!IMPORTANT]
> 🟣 🟣 🟣 Your tests are running.
> Tests running at:
<https://github.com/appsmithorg/appsmith/actions/runs/10878546679>
> Commit: 0013cdec8894922d3cae386a8d8d7b8aebc3837d
> Workflow: `PR Automation test suite`
> Tags: `@tag.Git`
> Spec: ``
> <hr>Mon, 16 Sep 2024 06:20:40 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**
- Updated the server version from 10 to 11, enhancing compatibility and
functionality related to JSON schema handling.
- Improved logic for default REST datasource migrations, making it more
robust and accessible.
- Added support for SSH connection configurations in datasource
management.

- **Bug Fixes**
- Added null checks to prevent potential errors during datasource
migrations, enhancing overall reliability.

- **Refactor**
- Streamlined filtering logic for actions, improving code readability
and maintainability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Manish Kumar 2024-09-16 13:24:08 +05:30 committed by GitHub
parent 37282997dc
commit 71261b1e6e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 95 additions and 40 deletions

View File

@ -34,8 +34,10 @@ public class DatasourceConfiguration implements AppsmithDomain {
@JsonView({Views.Public.class, FromRequest.class}) @JsonView({Views.Public.class, FromRequest.class})
AuthenticationDTO authentication; AuthenticationDTO authentication;
@JsonView({Views.Public.class, FromRequest.class})
SSHConnection sshProxy; SSHConnection sshProxy;
@JsonView({Views.Public.class, FromRequest.class})
Boolean sshProxyEnabled; Boolean sshProxyEnabled;
@JsonView({Views.Public.class, FromRequest.class, Git.class}) @JsonView({Views.Public.class, FromRequest.class, Git.class})

View File

@ -67,28 +67,38 @@ public class JsonSchemaMigration {
// TODO: make import flow migration reactive // TODO: make import flow migration reactive
return Mono.just(migrateServerSchema(appJson)) return Mono.just(migrateServerSchema(appJson))
.flatMap(migratedApplicationJson -> { .flatMap(migratedApplicationJson -> {
if (migratedApplicationJson.getServerSchemaVersion() == 9 // In Server version 9, there was a bug where the Embedded REST API datasource URL
&& Boolean.TRUE.equals(MigrationHelperMethods.doesRestApiRequireMigration( // was not being persisted correctly. Once the bug was fixed,
// any previously uncommitted changes started appearing as uncommitted modifications
// in the apps. To automatically commit these changes
// (which were now appearing as uncommitted), a migration process was needed.
// This migration fetches the datasource URL from the database
// and serializes it in Git if the URL exists.
// If the URL is missing, it copies the empty datasource configuration
// if the configuration is present in the database.
// Otherwise, it leaves the configuration unchanged.
// Due to an update in the migration logic after version 10 was shipped,
// the entire migration process was moved to version 11.
// This adjustment ensures that the same operation can be
// performed again for the changes introduced in version 10.
if (migratedApplicationJson.getServerSchemaVersion() == 9) {
migratedApplicationJson.setServerSchemaVersion(10);
}
if (migratedApplicationJson.getServerSchemaVersion() == 10) {
if (Boolean.TRUE.equals(MigrationHelperMethods.doesRestApiRequireMigration(
migratedApplicationJson))) { migratedApplicationJson))) {
return jsonSchemaMigrationHelper return jsonSchemaMigrationHelper
.addDatasourceConfigurationToDefaultRestApiActions( .addDatasourceConfigurationToDefaultRestApiActions(
baseApplicationId, branchName, migratedApplicationJson) baseApplicationId, branchName, migratedApplicationJson);
.map(applicationJsonWithMigration10 -> { }
applicationJsonWithMigration10.setServerSchemaVersion(10);
return applicationJsonWithMigration10; migratedApplicationJson.setServerSchemaVersion(11);
});
} }
migratedApplicationJson.setServerSchemaVersion(10);
return Mono.just(migratedApplicationJson); return Mono.just(migratedApplicationJson);
}) })
.map(migratedAppJson -> { .map(migratedAppJson -> {
if (applicationJson
.getServerSchemaVersion()
.equals(jsonSchemaVersions.getServerVersion())) {
return applicationJson;
}
applicationJson.setServerSchemaVersion(jsonSchemaVersions.getServerVersion()); applicationJson.setServerSchemaVersion(jsonSchemaVersions.getServerVersion());
return applicationJson; return applicationJson;
}); });
@ -193,16 +203,14 @@ public class JsonSchemaMigration {
switch (applicationJson.getServerSchemaVersion()) { switch (applicationJson.getServerSchemaVersion()) {
case 9: case 9:
applicationJson.setServerSchemaVersion(10);
case 10:
// this if for cases where we have empty datasource configs // this if for cases where we have empty datasource configs
MigrationHelperMethods.migrateApplicationJsonToVersionTen(applicationJson, Map.of()); MigrationHelperMethods.migrateApplicationJsonToVersionTen(applicationJson, Map.of());
applicationJson.setServerSchemaVersion(10); applicationJson.setServerSchemaVersion(11);
default: default:
} }
if (applicationJson.getServerSchemaVersion().equals(jsonSchemaVersions.getServerVersion())) {
return applicationJson;
}
applicationJson.setServerSchemaVersion(jsonSchemaVersions.getServerVersion()); applicationJson.setServerSchemaVersion(jsonSchemaVersions.getServerVersion());
return applicationJson; return applicationJson;
} }

View File

@ -4,7 +4,7 @@ import org.springframework.stereotype.Component;
@Component @Component
public class JsonSchemaVersionsFallback { public class JsonSchemaVersionsFallback {
private static final Integer serverVersion = 10; private static final Integer serverVersion = 11;
public static final Integer clientVersion = 1; public static final Integer clientVersion = 1;
public Integer getServerVersion() { public Integer getServerVersion() {

View File

@ -44,6 +44,8 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.appsmith.external.constants.PluginConstants.PackageName.GRAPHQL_PLUGIN;
import static com.appsmith.external.constants.PluginConstants.PackageName.REST_API_PLUGIN;
import static com.appsmith.server.constants.ResourceModes.EDIT; import static com.appsmith.server.constants.ResourceModes.EDIT;
import static com.appsmith.server.constants.ResourceModes.VIEW; import static com.appsmith.server.constants.ResourceModes.VIEW;
import static org.springframework.data.mongodb.core.query.Criteria.where; import static org.springframework.data.mongodb.core.query.Criteria.where;
@ -1235,14 +1237,35 @@ public class MigrationHelperMethods {
} }
} }
private static boolean conditionForDefaultRestDatasourceMigration(NewAction action) { public static boolean conditionForDefaultRestDatasource(NewAction action) {
if (action.getUnpublishedAction() == null
|| action.getUnpublishedAction().getDatasource() == null) {
return false;
}
Datasource actionDatasource = action.getUnpublishedAction().getDatasource(); Datasource actionDatasource = action.getUnpublishedAction().getDatasource();
// probable check for the default rest datasource action is.
// it has no datasource id and action's plugin id is either rest-api or graphql plugin.
boolean probableCheckForDefaultRestDatasource = !org.springframework.util.StringUtils.hasText(
actionDatasource.getId())
&& (REST_API_PLUGIN.equals(action.getPluginId()) || GRAPHQL_PLUGIN.equals(action.getPluginId()));
// condition to check if the action is default rest datasource. // condition to check if the action is default rest datasource.
// it has no datasource id and name is equal to DEFAULT_REST_DATASOURCE // it has no datasource id and name is equal to DEFAULT_REST_DATASOURCE
boolean isActionDefaultRestDatasource = !org.springframework.util.StringUtils.hasText(actionDatasource.getId()) boolean certainCheckForDefaultRestDatasource =
!org.springframework.util.StringUtils.hasText(actionDatasource.getId())
&& PluginConstants.DEFAULT_REST_DATASOURCE.equals(actionDatasource.getName()); && PluginConstants.DEFAULT_REST_DATASOURCE.equals(actionDatasource.getName());
// Two separate types of checks over here, it's either the obvious certain way to identify or
// the likely chance that the datasource is present.
return certainCheckForDefaultRestDatasource || probableCheckForDefaultRestDatasource;
}
private static boolean conditionForDefaultRestDatasourceMigration(NewAction action) {
boolean isActionDefaultRestDatasource = conditionForDefaultRestDatasource(action);
Datasource actionDatasource = action.getUnpublishedAction().getDatasource();
// condition to check if the action has missing url or has no config at all // condition to check if the action has missing url or has no config at all
boolean isDatasourceConfigurationOrUrlMissing = actionDatasource.getDatasourceConfiguration() == null boolean isDatasourceConfigurationOrUrlMissing = actionDatasource.getDatasourceConfiguration() == null
|| !org.springframework.util.StringUtils.hasText( || !org.springframework.util.StringUtils.hasText(
@ -1322,18 +1345,25 @@ public class MigrationHelperMethods {
if (defaultDatasourceActionMap.containsKey(action.getGitSyncId())) { if (defaultDatasourceActionMap.containsKey(action.getGitSyncId())) {
NewAction actionFromMap = defaultDatasourceActionMap.get(action.getGitSyncId()); NewAction actionFromMap = defaultDatasourceActionMap.get(action.getGitSyncId());
// NPE check to avoid migration failures
if (actionFromMap.getUnpublishedAction() == null
|| actionFromMap.getUnpublishedAction().getDatasource() == null
|| actionFromMap.getUnpublishedAction().getDatasource().getDatasourceConfiguration() == null) {
return;
}
// set the datasource config in the json action only if the datasource config from db is not null,
// else it'll start to show as uncommited changes.
DatasourceConfiguration datasourceConfigurationFromDBAction = DatasourceConfiguration datasourceConfigurationFromDBAction =
actionFromMap.getUnpublishedAction().getDatasource().getDatasourceConfiguration(); actionFromMap.getUnpublishedAction().getDatasource().getDatasourceConfiguration();
if (datasourceConfigurationFromDBAction != null) { // At this point it's established that datasource config of db action is not null.
datasourceConfiguration.setUrl(datasourceConfigurationFromDBAction.getUrl()); datasourceConfiguration.setUrl(datasourceConfigurationFromDBAction.getUrl());
} actionDatasource.setDatasourceConfiguration(datasourceConfiguration);
}
if (!org.springframework.util.StringUtils.hasText(datasourceConfiguration.getUrl())) { } else {
datasourceConfiguration.setUrl(""); datasourceConfiguration.setUrl("");
}
actionDatasource.setDatasourceConfiguration(datasourceConfiguration); actionDatasource.setDatasourceConfiguration(datasourceConfiguration);
} }
} }
}

View File

@ -1,6 +1,8 @@
package com.appsmith.server.migrations.utils; package com.appsmith.server.migrations.utils;
import com.appsmith.external.constants.PluginConstants; import com.appsmith.external.constants.PluginConstants;
import com.appsmith.external.models.Datasource;
import com.appsmith.external.models.PluginType;
import com.appsmith.server.applications.base.ApplicationService; import com.appsmith.server.applications.base.ApplicationService;
import com.appsmith.server.domains.Application; import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.NewAction; import com.appsmith.server.domains.NewAction;
@ -51,14 +53,27 @@ public class JsonSchemaMigrationHelper {
return false; return false;
} }
boolean reverseFlag = StringUtils.hasText(action.getUnpublishedAction() Datasource actionDatasource =
.getDatasource() action.getUnpublishedAction().getDatasource();
.getId())
|| !PluginConstants.DEFAULT_REST_DATASOURCE.equals(action.getUnpublishedAction()
.getDatasource()
.getName());
return !reverseFlag; // lenient probable check for the default rest datasource action is.
// As we don't have any harm in the allowing API actions present in db.
// it has no datasource id and action's plugin type is API
boolean probableCheckForDefaultRestDatasource =
!org.springframework.util.StringUtils.hasText(actionDatasource.getId())
&& PluginType.API.equals(action.getPluginType());
// condition to check if the action is default rest datasource.
// it has no datasource id and name is equal to DEFAULT_REST_DATASOURCE
boolean certainCheckForDefaultRestDatasource =
!org.springframework.util.StringUtils.hasText(actionDatasource.getId())
&& PluginConstants.DEFAULT_REST_DATASOURCE.equals(
actionDatasource.getName());
// Two separate types of checks over here, it's either the obvious certain way to
// identify or
// the likely chance that the datasource is present.
return certainCheckForDefaultRestDatasource || probableCheckForDefaultRestDatasource;
}) })
.collectMap(NewAction::getGitSyncId); .collectMap(NewAction::getGitSyncId);
}) })