chore: add postSaveHook (#39306)

## Description
> [!IMPORTANT]  
> Add a post save hook on datasources.

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

## Automation

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

### 🔍 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/13386879786>
> Commit: 260ad934fc1295d2ad5ed035ff593c9950158c74
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=13386879786&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Sanity`
> Spec:
> <hr>Tue, 18 Feb 2025 09:36:50 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

## Summary by CodeRabbit

- **New Features**
- Enhanced multi-tenant support: Datasource records now incorporate
tenant and instance details for seamless management across environments.
- Extended post-save actions: Datasource saving triggers additional
operations via plugin integrations to improve overall functionality.
- New transient metadata field added to DatasourceStorage for additional
data handling.
- New post-save hook method introduced for plugins to perform actions
after saving a datasource.
	- New constant `TENANT_ID` added for consistent field name referencing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Nilesh Sarupriya <20905988+nsarupr@users.noreply.github.com>
This commit is contained in:
Nilesh Sarupriya 2025-02-18 15:53:15 +05:30 committed by GitHub
parent e4ed590822
commit d921110658
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 79 additions and 24 deletions

View File

@ -89,6 +89,9 @@ public class DatasourceStorage extends GitSyncedDomain {
@Transient
Boolean isMock;
@Transient
Map<String, Object> metadata;
public DatasourceStorage(
String datasourceId,
String environmentId,

View File

@ -185,6 +185,13 @@ public interface PluginExecutor<C> extends ExtensionPoint, CrudTemplateService {
return Mono.just(datasourceStorage);
}
/**
* This function is being called as a hook after saving a datasource.
*/
default Mono<DatasourceStorage> postSaveHook(DatasourceStorage datasourceStorage) {
return Mono.just(datasourceStorage);
}
/**
* This function fetches the structure of the tables/collections in the datasource. It's used to make query creation
* easier for the user.

View File

@ -197,6 +197,7 @@ public class FieldNameCE {
public static final String REMOTE_PLUGINS = "remotePlugins";
public static final String INSTANCE_ID = "instanceId";
public static final String TENANT_ID = "tenantId";
public static final String IP_ADDRESS = "ipAddress";
public static final String VERSION = "version";
public static final String PUBLISHED = "published";

View File

@ -28,10 +28,12 @@ import com.appsmith.server.ratelimiting.RateLimitService;
import com.appsmith.server.repositories.DatasourceRepository;
import com.appsmith.server.repositories.NewActionRepository;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.ConfigService;
import com.appsmith.server.services.DatasourceContextService;
import com.appsmith.server.services.FeatureFlagService;
import com.appsmith.server.services.SequenceService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.services.TenantService;
import com.appsmith.server.services.WorkspaceService;
import com.appsmith.server.solutions.DatasourcePermission;
import com.appsmith.server.solutions.EnvironmentPermission;
@ -64,6 +66,8 @@ import java.util.UUID;
import static com.appsmith.external.constants.spans.DatasourceSpan.FETCH_ALL_DATASOURCES_WITH_STORAGES;
import static com.appsmith.external.constants.spans.DatasourceSpan.FETCH_ALL_PLUGINS_IN_WORKSPACE;
import static com.appsmith.external.helpers.AppsmithBeanUtils.copyNestedNonNullProperties;
import static com.appsmith.server.constants.ce.FieldNameCE.INSTANCE_ID;
import static com.appsmith.server.constants.ce.FieldNameCE.TENANT_ID;
import static com.appsmith.server.dtos.DBOpsType.SAVE;
import static com.appsmith.server.helpers.CollectionUtils.isNullOrEmpty;
import static com.appsmith.server.helpers.DatasourceAnalyticsUtils.getAnalyticsProperties;
@ -93,6 +97,8 @@ public class DatasourceServiceCEImpl implements DatasourceServiceCE {
private final RateLimitService rateLimitService;
private final FeatureFlagService featureFlagService;
private final ObservationRegistry observationRegistry;
private final TenantService tenantService;
private final ConfigService configService;
// Defines blocking duration for test as well as connection created for query execution
// This will block the creation of datasource connection for 5 minutes, in case of more than 3 failed connection
@ -119,7 +125,9 @@ public class DatasourceServiceCEImpl implements DatasourceServiceCE {
EnvironmentPermission environmentPermission,
RateLimitService rateLimitService,
FeatureFlagService featureFlagService,
ObservationRegistry observationRegistry) {
ObservationRegistry observationRegistry,
TenantService tenantService,
ConfigService configService) {
this.workspaceService = workspaceService;
this.sessionUserService = sessionUserService;
@ -138,6 +146,8 @@ public class DatasourceServiceCEImpl implements DatasourceServiceCE {
this.rateLimitService = rateLimitService;
this.featureFlagService = featureFlagService;
this.observationRegistry = observationRegistry;
this.tenantService = tenantService;
this.configService = configService;
}
@Override
@ -223,27 +233,28 @@ public class DatasourceServiceCEImpl implements DatasourceServiceCE {
}
return datasourceMono.flatMap(savedDatasource -> this.organiseDatasourceStorages(savedDatasource)
.flatMap(datasourceStorage -> {
// Make sure that we are creating entries only if the id is not already populated
if (hasText(datasourceStorage.getId())) {
return Mono.just(datasourceStorage);
}
.flatMap(datasourceStorageX -> setAdditionalMetadataInDatasourceStorage(datasourceStorageX)
.flatMap(datasourceStorage -> {
// Make sure that we are creating entries only if the id is not already populated
if (hasText(datasourceStorage.getId())) {
return Mono.just(datasourceStorage);
}
return datasourceStorageService
.create(datasourceStorage, isDryOps)
.map(datasourceStorage1 -> {
if (datasourceStorageDryRunQueries != null && isDryOps) {
List<DatasourceStorage> datasourceStorages =
datasourceStorageDryRunQueries.get(SAVE);
if (datasourceStorages == null) {
datasourceStorages = new ArrayList<>();
}
datasourceStorages.add(datasourceStorage1);
datasourceStorageDryRunQueries.put(SAVE, datasourceStorages);
}
return datasourceStorage1;
});
})
return datasourceStorageService
.create(datasourceStorage, isDryOps)
.map(datasourceStorage1 -> {
if (datasourceStorageDryRunQueries != null && isDryOps) {
List<DatasourceStorage> datasourceStorages =
datasourceStorageDryRunQueries.get(SAVE);
if (datasourceStorages == null) {
datasourceStorages = new ArrayList<>();
}
datasourceStorages.add(datasourceStorage1);
datasourceStorageDryRunQueries.put(SAVE, datasourceStorages);
}
return datasourceStorage1;
});
}))
.map(datasourceStorageService::createDatasourceStorageDTOFromDatasourceStorage)
.collectMap(DatasourceStorageDTO::getEnvironmentId)
.map(savedStorages -> {
@ -252,6 +263,20 @@ public class DatasourceServiceCEImpl implements DatasourceServiceCE {
}));
}
private Mono<DatasourceStorage> setAdditionalMetadataInDatasourceStorage(DatasourceStorage datasourceStorage) {
Mono<String> tenantIdMono = tenantService.getDefaultTenantId();
Mono<String> instanceIdMono = configService.getInstanceId();
Map<String, Object> metadata = new HashMap<>();
return tenantIdMono.zipWith(instanceIdMono).map(tuple -> {
metadata.put(TENANT_ID, tuple.getT1());
metadata.put(INSTANCE_ID, tuple.getT2());
datasourceStorage.setMetadata(metadata);
return datasourceStorage;
});
}
// this requires an EE override multiple environments
protected Flux<DatasourceStorage> organiseDatasourceStorages(@NotNull Datasource savedDatasource) {
Map<String, DatasourceStorageDTO> storages = savedDatasource.getDatasourceStorages();

View File

@ -8,10 +8,12 @@ import com.appsmith.server.ratelimiting.RateLimitService;
import com.appsmith.server.repositories.DatasourceRepository;
import com.appsmith.server.repositories.NewActionRepository;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.ConfigService;
import com.appsmith.server.services.DatasourceContextService;
import com.appsmith.server.services.FeatureFlagService;
import com.appsmith.server.services.SequenceService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.services.TenantService;
import com.appsmith.server.services.WorkspaceService;
import com.appsmith.server.solutions.DatasourcePermission;
import com.appsmith.server.solutions.EnvironmentPermission;
@ -41,7 +43,9 @@ public class DatasourceServiceImpl extends DatasourceServiceCEImpl implements Da
EnvironmentPermission environmentPermission,
RateLimitService rateLimitService,
FeatureFlagService featureFlagService,
ObservationRegistry observationRegistry) {
ObservationRegistry observationRegistry,
TenantService tenantService,
ConfigService configService) {
super(
repository,
@ -60,6 +64,8 @@ public class DatasourceServiceImpl extends DatasourceServiceCEImpl implements Da
environmentPermission,
rateLimitService,
featureFlagService,
observationRegistry);
observationRegistry,
tenantService,
configService);
}
}

View File

@ -183,6 +183,16 @@ public class DatasourceStorageServiceCEImpl implements DatasourceStorageServiceC
return pluginExecutorMono.flatMap(pluginExecutor -> pluginExecutor.preSaveHook(datasourceStorage));
}
public Mono<DatasourceStorage> executePostSaveActions(DatasourceStorage datasourceStorage) {
Mono<Plugin> pluginMono = pluginService.findById(datasourceStorage.getPluginId());
Mono<PluginExecutor> pluginExecutorMono = pluginExecutorHelper
.getPluginExecutor(pluginMono)
.switchIfEmpty(Mono.error(new AppsmithException(
AppsmithError.NO_RESOURCE_FOUND, FieldName.PLUGIN, datasourceStorage.getPluginId())));
return pluginExecutorMono.flatMap(pluginExecutor -> pluginExecutor.postSaveHook(datasourceStorage));
}
@Override
public Mono<DatasourceStorage> validateDatasourceStorage(DatasourceStorage datasourceStorage) {
@ -242,7 +252,10 @@ public class DatasourceStorageServiceCEImpl implements DatasourceStorageServiceC
unsavedDatasourceStorage.updateForBulkWriteOperation();
return Mono.just(unsavedDatasourceStorage);
}
return repository.save(unsavedDatasourceStorage).thenReturn(unsavedDatasourceStorage);
return repository
.save(unsavedDatasourceStorage)
.then(this.executePostSaveActions(unsavedDatasourceStorage))
.thenReturn(unsavedDatasourceStorage);
});
}