chore: Add metrics to newRelic for update JSobject Collection (#35947)

## Description

- Add newRelic spans to track performance of process involved in update
JSObject collection

Fixes #36049 

## Automation

/test js

### 🔍 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/10704346206>
> Commit: 5bf63bb0c04957493d40373b40f6ab46ed447103
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=10704346206&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.JS`
> Spec:
> <hr>Wed, 04 Sep 2024 15:29:57 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [x] No


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

## Summary by CodeRabbit

- **New Features**
- Introduced enhanced observability for action collections and layout
updates, allowing better tracking and monitoring of actions within the
application.
- Added new constants related to action collections and layout updates
for improved functionality and clarity.

- **Bug Fixes**
- Improved the reliability of action collection retrieval and layout
updates through enhanced observability features.

- **Tests**
- Updated tests to incorporate observability metrics, ensuring better
monitoring of actions during testing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Rishabh Rathod 2024-09-05 16:46:25 +05:30 committed by GitHub
parent 440ff139e9
commit b447b0f29e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 209 additions and 37 deletions

View File

@ -0,0 +1,5 @@
package com.appsmith.external.constants.spans;
import com.appsmith.external.constants.spans.ce.ActionCollectionSpanCE;
public class ActionCollectionSpan extends ActionCollectionSpanCE {}

View File

@ -0,0 +1,5 @@
package com.appsmith.external.constants.spans;
import com.appsmith.external.constants.spans.ce.LayoutSpanCE;
public class LayoutSpan extends LayoutSpanCE {}

View File

@ -0,0 +1,21 @@
package com.appsmith.external.constants.spans.ce;
import static com.appsmith.external.constants.spans.BaseSpan.APPSMITH_SPAN_PREFIX;
/**
* Please make sure that all span names start with `appsmith.` because span with any other naming format would get
* dropped / ignored as defined in TracingConfig.java
*/
public class ActionCollectionSpanCE {
// Action Collection spans
public static final String ACTION_COLLECTION_UPDATE = APPSMITH_SPAN_PREFIX + "update.actionCollection";
public static final String GENERATE_ACTION_COLLECTION_BY_VIEW_MODE =
APPSMITH_SPAN_PREFIX + "generate.actionCollectionByViewMode";
public static final String POPULATE_ACTION_COLLECTION_BY_VIEW_MODE =
APPSMITH_SPAN_PREFIX + "populate.actionCollectionByViewMode";
public static final String SAVE_ACTION_COLLECTION_LAST_EDIT_INFO =
APPSMITH_SPAN_PREFIX + "save.actionCollectionLastEditInfo";
// Getter spans
public static final String GET_ACTION_COLLECTION_BY_ID = APPSMITH_SPAN_PREFIX + "get.actionCollectionById";
}

View File

@ -29,4 +29,12 @@ public class ActionSpanCE {
public static final String VIEW_MODE_SET_PLUGIN_ID_AND_TYPE_ACTION = ACTIONS_VIEW_MODE_PREFIX + "set_action";
public static final String VIEW_MODE_FETCH_PLUGIN_FROM_DB = ACTIONS_VIEW_MODE_PREFIX + "plugindb";
public static final String VIEW_MODE_FETCH_ACTIONS_FROM_DB = ACTIONS_VIEW_MODE_PREFIX + "fetchactions";
public static final String GET_ACTION_BY_ID = APPSMITH_SPAN_PREFIX + "get.actionById";
// Action creation, update and delete spans
public static final String UPDATE_SINGLE_ACTION = APPSMITH_SPAN_PREFIX + "update.single.action";
public static final String UPDATE_ACTION_BASED_ON_CONTEXT = APPSMITH_SPAN_PREFIX + "update.action.context";
public static final String CREATE_ACTION = APPSMITH_SPAN_PREFIX + "create.action";
public static final String UPDATE_ACTION = APPSMITH_SPAN_PREFIX + "update.action";
public static final String DELETE_ACTION = APPSMITH_SPAN_PREFIX + "delete.action";
}

View File

@ -0,0 +1,18 @@
package com.appsmith.external.constants.spans.ce;
import static com.appsmith.external.constants.spans.BaseSpan.APPSMITH_SPAN_PREFIX;
public class LayoutSpanCE {
public static final String UPDATE_LAYOUT = "updateLayout.";
public static final String UPDATE_PAGE_LAYOUT_BY_PAGE_ID = APPSMITH_SPAN_PREFIX + UPDATE_LAYOUT + "pageId";
public static final String UPDATE_LAYOUT_METHOD = APPSMITH_SPAN_PREFIX + UPDATE_LAYOUT + "method";
public static final String UPDATE_LAYOUT_DSL_METHOD = APPSMITH_SPAN_PREFIX + UPDATE_LAYOUT + "dsl.method";
public static final String UPDATE_LAYOUT_BASED_ON_CONTEXT = APPSMITH_SPAN_PREFIX + UPDATE_LAYOUT + "context";
public static final String FIND_ALL_ON_LOAD_EXECUTABLES =
APPSMITH_SPAN_PREFIX + "onLoadExecutablesUtil.findAllOnLoadExecutables";
public static final String UPDATE_EXECUTABLES_EXECUTE_ONLOAD =
APPSMITH_SPAN_PREFIX + "onLoadExecutablesUtil.updateExecutablesExecuteOnLoad";
public static final String FIND_AND_UPDATE_LAYOUT =
APPSMITH_SPAN_PREFIX + "onLoadExecutablesUtil.findAndUpdateLayout";
}

View File

@ -1,5 +1,7 @@
package com.appsmith.external.constants.spans.ce;
import static com.appsmith.external.constants.spans.BaseSpan.APPSMITH_SPAN_PREFIX;
public class PageSpanCE {
public static final String PAGES = "pages.";
public static final String GET_PAGE = PAGES + "getpage";
@ -11,4 +13,6 @@ public class PageSpanCE {
public static final String MARK_RECENTLY_ACCESSED_RESOURCES_PAGES = PAGES + "update_recently_accessed_pages";
public static final String PREPARE_APPLICATION_PAGES_DTO_FROM_PAGES = PAGES + "generate_app_pages_dto";
public static final String MIGRATE_DSL = PAGES + "migrate_dsl";
public static final String GET_PAGE_BY_ID = APPSMITH_SPAN_PREFIX + "get.page.unpublished";
}

View File

@ -20,6 +20,7 @@ import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.BaseService;
import com.appsmith.server.solutions.ActionPermission;
import com.appsmith.server.solutions.ApplicationPermission;
import io.micrometer.observation.ObservationRegistry;
import jakarta.validation.Validator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
@ -28,6 +29,7 @@ import org.springframework.data.domain.Sort;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import reactor.core.observability.micrometer.Micrometer;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@ -41,6 +43,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import static com.appsmith.external.constants.spans.ActionCollectionSpan.GET_ACTION_COLLECTION_BY_ID;
import static com.appsmith.external.helpers.AppsmithBeanUtils.copyNewFieldValuesIntoOldObject;
import static java.lang.Boolean.TRUE;
@ -53,6 +56,7 @@ public class ActionCollectionServiceCEImpl extends BaseService<ActionCollectionR
private final ApplicationService applicationService;
private final ApplicationPermission applicationPermission;
private final ActionPermission actionPermission;
private final ObservationRegistry observationRegistry;
@Autowired
public ActionCollectionServiceCEImpl(
@ -63,7 +67,8 @@ public class ActionCollectionServiceCEImpl extends BaseService<ActionCollectionR
PolicyGenerator policyGenerator,
ApplicationService applicationService,
ApplicationPermission applicationPermission,
ActionPermission actionPermission) {
ActionPermission actionPermission,
ObservationRegistry observationRegistry) {
super(validator, repository, analyticsService);
this.newActionService = newActionService;
@ -71,6 +76,7 @@ public class ActionCollectionServiceCEImpl extends BaseService<ActionCollectionR
this.applicationService = applicationService;
this.applicationPermission = applicationPermission;
this.actionPermission = actionPermission;
this.observationRegistry = observationRegistry;
}
@Override
@ -372,7 +378,10 @@ public class ActionCollectionServiceCEImpl extends BaseService<ActionCollectionR
@Override
public Mono<ActionCollection> findById(String id, AclPermission aclPermission) {
return repository.findById(id, aclPermission);
return repository
.findById(id, aclPermission)
.name(GET_ACTION_COLLECTION_BY_ID)
.tap(Micrometer.observation(observationRegistry));
}
@Override

View File

@ -7,6 +7,7 @@ import com.appsmith.server.repositories.ActionCollectionRepository;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.solutions.ActionPermission;
import com.appsmith.server.solutions.ApplicationPermission;
import io.micrometer.observation.ObservationRegistry;
import jakarta.validation.Validator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -22,7 +23,8 @@ public class ActionCollectionServiceImpl extends ActionCollectionServiceCEImpl i
PolicyGenerator policyGenerator,
ApplicationService applicationService,
ApplicationPermission applicationPermission,
ActionPermission actionPermission) {
ActionPermission actionPermission,
ObservationRegistry observationRegistry) {
super(
validator,
repository,
@ -31,6 +33,7 @@ public class ActionCollectionServiceImpl extends ActionCollectionServiceCEImpl i
policyGenerator,
applicationService,
applicationPermission,
actionPermission);
actionPermission,
observationRegistry);
}
}

View File

@ -25,12 +25,14 @@ import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.solutions.PagePermission;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.micrometer.observation.ObservationRegistry;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONObject;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import reactor.core.observability.micrometer.Micrometer;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@ -46,6 +48,12 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.appsmith.external.constants.spans.LayoutSpan.FIND_ALL_ON_LOAD_EXECUTABLES;
import static com.appsmith.external.constants.spans.LayoutSpan.FIND_AND_UPDATE_LAYOUT;
import static com.appsmith.external.constants.spans.LayoutSpan.UPDATE_EXECUTABLES_EXECUTE_ONLOAD;
import static com.appsmith.external.constants.spans.LayoutSpan.UPDATE_LAYOUT_DSL_METHOD;
import static com.appsmith.external.constants.spans.LayoutSpan.UPDATE_LAYOUT_METHOD;
import static com.appsmith.external.constants.spans.PageSpan.GET_PAGE_BY_ID;
import static com.appsmith.server.constants.CommonConstants.EVALUATION_VERSION;
import static java.lang.Boolean.FALSE;
@ -60,8 +68,8 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
private final AnalyticsService analyticsService;
private final PagePermission pagePermission;
private final ApplicationService applicationService;
private final ObjectMapper objectMapper;
private final ObservationRegistry observationRegistry;
private final String layoutOnLoadActionErrorToastMessage =
"A cyclic dependency error has been encountered on current page, \nqueries on page load will not run. \n Please check debugger and Appsmith documentation for more information";
@ -122,6 +130,7 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
try {
dsl = extractAllWidgetNamesAndDynamicBindingsFromDSL(
dsl, widgetNames, widgetDynamicBindingsMap, creatorId, layoutId, escapedWidgetNames, creatorType);
} catch (Throwable t) {
return sendUpdateLayoutAnalyticsEvent(creatorId, layoutId, dsl, false, t, creatorType)
.then(Mono.error(t));
@ -155,6 +164,8 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
flatmapOnLoadExecutables,
executablesUsedInDSL,
creatorType)
.name(FIND_ALL_ON_LOAD_EXECUTABLES)
.tap(Micrometer.observation(observationRegistry))
.onErrorResume(AppsmithException.class, error -> {
log.info(error.getMessage());
validOnLoadExecutables.set(FALSE);
@ -181,6 +192,8 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
return onLoadExecutablesUtil
.updateExecutablesExecuteOnLoad(
flatmapOnLoadExecutables, creatorId, executableUpdatesRef, messagesRef, creatorType)
.name(UPDATE_EXECUTABLES_EXECUTE_ONLOAD)
.tap(Micrometer.observation(observationRegistry))
.thenReturn(allOnLoadExecutables);
})
// Now update the page layout with the page load executables and the graph.
@ -192,7 +205,10 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
// valid when last stored in the database.
layout.setValidOnPageLoadActions(validOnLoadExecutables.get());
return onLoadExecutablesUtil.findAndUpdateLayout(creatorId, creatorType, layoutId, layout);
return onLoadExecutablesUtil
.findAndUpdateLayout(creatorId, creatorType, layoutId, layout)
.name(FIND_AND_UPDATE_LAYOUT)
.tap(Micrometer.observation(observationRegistry));
})
.map(savedLayout -> {
savedLayout.setDsl(this.unescapeMongoSpecialCharacters(savedLayout));
@ -219,7 +235,8 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
if (evaluationVersion == null) {
evaluationVersion = EVALUATION_VERSION;
}
return updateLayoutDsl(pageId, layoutId, layout, evaluationVersion, CreatorContextType.PAGE);
return updateLayoutDsl(pageId, layoutId, layout, evaluationVersion, CreatorContextType.PAGE)
.name(UPDATE_LAYOUT_DSL_METHOD);
});
}
@ -276,13 +293,17 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
return Mono.justOrEmpty(pageId)
// fetch the unpublished page
.flatMap(id -> newPageService.findPageById(id, pagePermission.getEditPermission(), false))
.name(GET_PAGE_BY_ID)
.tap(Micrometer.observation(observationRegistry))
.flatMapMany(page -> {
if (page.getLayouts() == null) {
return Mono.empty();
}
return Flux.fromIterable(page.getLayouts()).flatMap(layout -> {
layout.setDsl(this.unescapeMongoSpecialCharacters(layout));
return this.updateLayout(page.getId(), page.getApplicationId(), layout.getId(), layout);
return this.updateLayout(page.getId(), page.getApplicationId(), layout.getId(), layout)
.name(UPDATE_LAYOUT_METHOD)
.tap(Micrometer.observation(observationRegistry));
});
})
.collectList()
@ -324,7 +345,8 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
AtomicReference<Boolean> validOnLoadExecutables = new AtomicReference<>(Boolean.TRUE);
// setting the layoutOnLoadActionActionErrors to empty to remove the existing errors before new DAG calculation.
// setting the layoutOnLoadActionActionErrors to empty to remove the existing
// errors before new DAG calculation.
layout.setLayoutOnLoadActionErrors(new ArrayList<>());
return onLoadExecutablesUtil
@ -364,13 +386,15 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
String widgetType = dsl.getAsString(FieldName.WIDGET_TYPE);
if (widgetType.equals(FieldName.TABLE_WIDGET)) {
// UnEscape Table widget keys
// Since this is a table widget, it wouldnt have children. We can safely return from here with updated
// Since this is a table widget, it wouldnt have children. We can safely return
// from here with updated
// dsl
return WidgetSpecificUtils.unEscapeTableWidgetPrimaryColumns(dsl);
}
}
// Fetch the children of the current node in the DSL and recursively iterate over them to extract bindings
// Fetch the children of the current node in the DSL and recursively iterate
// over them to extract bindings
ArrayList<Object> children = (ArrayList<Object>) dsl.get(FieldName.CHILDREN);
ArrayList<Object> newChildren = new ArrayList<>();
if (children != null) {
@ -392,9 +416,11 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
/**
* Walks the DSL and extracts all the widget names from it.
* A widget is expected to have a few properties defining its own behaviour, with any mustache bindings present
* A widget is expected to have a few properties defining its own behaviour,
* with any mustache bindings present
* in them aggregated in the field dynamicBindingsPathList.
* A widget may also have other widgets as children, each of which will follow the same structure
* A widget may also have other widgets as children, each of which will follow
* the same structure
* Refer to FieldName.DEFAULT_PAGE_LAYOUT for a template
*
* @param dsl
@ -423,27 +449,33 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
String widgetId = dsl.getAsString(FieldName.WIDGET_ID);
String widgetType = dsl.getAsString(FieldName.WIDGET_TYPE);
// Since we are parsing this widget in this, add it to the global set of widgets found so far in the DSL.
// Since we are parsing this widget in this, add it to the global set of widgets
// found so far in the DSL.
widgetNames.add(widgetName);
// Start by picking all fields where we expect to find dynamic bindings for this particular widget
// Start by picking all fields where we expect to find dynamic bindings for this
// particular widget
ArrayList<Object> dynamicallyBoundedPathList = (ArrayList<Object>) dsl.get(FieldName.DYNAMIC_BINDING_PATH_LIST);
// Widgets will not have FieldName.DYNAMIC_BINDING_PATH_LIST if there are no bindings in that widget.
// Widgets will not have FieldName.DYNAMIC_BINDING_PATH_LIST if there are no
// bindings in that widget.
// Hence we skip over the extraction of the bindings from that widget.
if (dynamicallyBoundedPathList != null) {
// Each of these might have nested structures, so we iterate through them to find the leaf node for each
// Each of these might have nested structures, so we iterate through them to
// find the leaf node for each
for (Object x : dynamicallyBoundedPathList) {
final String fieldPath = String.valueOf(((Map) x).get(FieldName.KEY));
String[] fields = fieldPath.split("[].\\[]");
// For nested fields, the parent dsl to search in would shift by one level every iteration
// For nested fields, the parent dsl to search in would shift by one level every
// iteration
Object parent = dsl;
Iterator<String> fieldsIterator = Arrays.stream(fields)
.filter(fieldToken -> !fieldToken.isBlank())
.iterator();
boolean isLeafNode = false;
Object oldParent;
// This loop will end at either a leaf node, or the last identified JSON field (by throwing an
// This loop will end at either a leaf node, or the last identified JSON field
// (by throwing an
// exception)
// Valid forms of the fieldPath for this search could be:
// root.field.list[index].childField.anotherList.indexWithDotOperator.multidimensionalList[index1][index2]
@ -510,7 +542,8 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
// Only extract mustache keys from leaf nodes
if (isLeafNode) {
// We found the path. But if the path does not have any mustache bindings, throw the error
// We found the path. But if the path does not have any mustache bindings, throw
// the error
if (!MustacheHelper.laxIsBindingPresentInString((String) parent)) {
try {
String bindingAsString = objectMapper.writeValueAsString(parent);
@ -551,7 +584,8 @@ public class UpdateLayoutServiceCEImpl implements UpdateLayoutServiceCE {
// Escape the widget keys if required and update dsl and escapedWidgetNames
removeSpecialCharactersFromKeys(dsl, escapedWidgetNames);
// Fetch the children of the current node in the DSL and recursively iterate over them to extract bindings
// Fetch the children of the current node in the DSL and recursively iterate
// over them to extract bindings
ArrayList<Object> children = (ArrayList<Object>) dsl.get(FieldName.CHILDREN);
ArrayList<Object> newChildren = new ArrayList<>();
if (children != null) {

View File

@ -7,6 +7,7 @@ import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.solutions.PagePermission;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.stereotype.Service;
@Service
@ -19,7 +20,8 @@ public class UpdateLayoutServiceImpl extends UpdateLayoutServiceCEImpl implement
AnalyticsService analyticsService,
PagePermission pagePermission,
ApplicationService applicationService,
ObjectMapper objectMapper) {
ObjectMapper objectMapper,
ObservationRegistry observationRegistry) {
super(
onLoadExecutablesUtil,
sessionUserService,
@ -27,6 +29,7 @@ public class UpdateLayoutServiceImpl extends UpdateLayoutServiceCEImpl implement
analyticsService,
pagePermission,
applicationService,
objectMapper);
objectMapper,
observationRegistry);
}
}

View File

@ -8,6 +8,7 @@ import com.appsmith.server.refactors.applications.RefactoringService;
import com.appsmith.server.services.ce.LayoutActionServiceCEImpl;
import com.appsmith.server.solutions.ActionPermission;
import com.appsmith.server.solutions.PagePermission;
import io.micrometer.observation.ObservationRegistry;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -24,7 +25,9 @@ public class LayoutActionServiceImpl extends LayoutActionServiceCEImpl implement
UpdateLayoutService updateLayoutService,
DatasourceService datasourceService,
PagePermission pagePermission,
ActionPermission actionPermission) {
ActionPermission actionPermission,
ObservationRegistry observationRegistry) {
super(
analyticsService,
newPageService,
@ -34,6 +37,7 @@ public class LayoutActionServiceImpl extends LayoutActionServiceCEImpl implement
updateLayoutService,
datasourceService,
pagePermission,
actionPermission);
actionPermission,
observationRegistry);
}
}

View File

@ -9,6 +9,7 @@ import com.appsmith.server.repositories.ActionCollectionRepository;
import com.appsmith.server.services.ce.LayoutCollectionServiceCEImpl;
import com.appsmith.server.solutions.ActionPermission;
import com.appsmith.server.solutions.PagePermission;
import io.micrometer.observation.ObservationRegistry;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -26,7 +27,8 @@ public class LayoutCollectionServiceImpl extends LayoutCollectionServiceCEImpl i
AnalyticsService analyticsService,
ActionCollectionRepository actionCollectionRepository,
PagePermission pagePermission,
ActionPermission actionPermission) {
ActionPermission actionPermission,
ObservationRegistry observationRegistry) {
super(
newPageService,
layoutActionService,
@ -37,6 +39,7 @@ public class LayoutCollectionServiceImpl extends LayoutCollectionServiceCEImpl i
analyticsService,
actionCollectionRepository,
pagePermission,
actionPermission);
actionPermission,
observationRegistry);
}
}

View File

@ -23,14 +23,20 @@ import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.CollectionService;
import com.appsmith.server.solutions.ActionPermission;
import com.appsmith.server.solutions.PagePermission;
import io.micrometer.observation.ObservationRegistry;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import reactor.core.observability.micrometer.Micrometer;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import static com.appsmith.external.constants.spans.ActionSpan.GET_ACTION_BY_ID;
import static com.appsmith.external.constants.spans.ActionSpan.UPDATE_ACTION_BASED_ON_CONTEXT;
import static com.appsmith.external.constants.spans.ActionSpan.UPDATE_SINGLE_ACTION;
import static com.appsmith.external.constants.spans.LayoutSpan.UPDATE_PAGE_LAYOUT_BY_PAGE_ID;
import static java.util.stream.Collectors.toSet;
@Slf4j
@ -47,6 +53,7 @@ public class LayoutActionServiceCEImpl implements LayoutActionServiceCE {
private final DatasourceService datasourceService;
private final PagePermission pagePermission;
private final ActionPermission actionPermission;
private final ObservationRegistry observationRegistry;
/**
* Called by Action controller to create Action
@ -214,7 +221,11 @@ public class LayoutActionServiceCEImpl implements LayoutActionServiceCE {
public Mono<ActionDTO> updateNewActionByBranchedId(String branchedId, ActionDTO actionDTO) {
return newActionService
.findById(branchedId, actionPermission.getEditPermission())
.flatMap(newAction -> updateActionBasedOnContextType(newAction, actionDTO));
.name(GET_ACTION_BY_ID)
.tap(Micrometer.observation(observationRegistry))
.flatMap(newAction -> updateActionBasedOnContextType(newAction, actionDTO)
.name(UPDATE_ACTION_BASED_ON_CONTEXT)
.tap(Micrometer.observation(observationRegistry)));
}
/**
@ -227,8 +238,13 @@ public class LayoutActionServiceCEImpl implements LayoutActionServiceCE {
action.setApplicationId(null);
action.setPageId(null);
return updateSingleAction(newAction.getId(), action)
.flatMap(updatedAction ->
updateLayoutService.updatePageLayoutsByPageId(pageId).thenReturn(updatedAction))
.name(UPDATE_SINGLE_ACTION)
.tap(Micrometer.observation(observationRegistry))
.flatMap(updatedAction -> updateLayoutService
.updatePageLayoutsByPageId(pageId)
.name(UPDATE_PAGE_LAYOUT_BY_PAGE_ID)
.tap(Micrometer.observation(observationRegistry))
.thenReturn(updatedAction))
.zipWhen(actionDTO -> newPageService.findPageById(pageId, pagePermission.getEditPermission(), false))
.map(tuple2 -> {
ActionDTO actionDTO = tuple2.getT1();

View File

@ -23,9 +23,11 @@ import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.LayoutActionService;
import com.appsmith.server.solutions.ActionPermission;
import com.appsmith.server.solutions.PagePermission;
import io.micrometer.observation.ObservationRegistry;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import reactor.core.observability.micrometer.Micrometer;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@ -36,6 +38,14 @@ import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static com.appsmith.external.constants.spans.ActionCollectionSpan.ACTION_COLLECTION_UPDATE;
import static com.appsmith.external.constants.spans.ActionCollectionSpan.GENERATE_ACTION_COLLECTION_BY_VIEW_MODE;
import static com.appsmith.external.constants.spans.ActionCollectionSpan.POPULATE_ACTION_COLLECTION_BY_VIEW_MODE;
import static com.appsmith.external.constants.spans.ActionCollectionSpan.SAVE_ACTION_COLLECTION_LAST_EDIT_INFO;
import static com.appsmith.external.constants.spans.ActionSpan.CREATE_ACTION;
import static com.appsmith.external.constants.spans.ActionSpan.DELETE_ACTION;
import static com.appsmith.external.constants.spans.ActionSpan.UPDATE_ACTION;
import static com.appsmith.external.constants.spans.LayoutSpan.UPDATE_LAYOUT_BASED_ON_CONTEXT;
import static com.appsmith.external.helpers.AppsmithBeanUtils.copyNewFieldValuesIntoOldObject;
import static com.appsmith.server.helpers.ContextTypeUtils.isPageContext;
import static java.util.stream.Collectors.toMap;
@ -55,6 +65,7 @@ public class LayoutCollectionServiceCEImpl implements LayoutCollectionServiceCE
private final ActionCollectionRepository actionCollectionRepository;
private final PagePermission pagePermission;
private final ActionPermission actionPermission;
private final ObservationRegistry observationRegistry;
@Override
public Mono<ActionCollectionDTO> createCollection(ActionCollection actionCollection) {
@ -325,7 +336,10 @@ public class LayoutCollectionServiceCEImpl implements LayoutCollectionServiceCE
actionDTO.setPluginId(actionCollectionDTO.getPluginId());
actionDTO.setBranchName(branchedActionCollection.getBranchName());
// actionCollectionService is a new action, we need to create one
return layoutActionService.createSingleAction(actionDTO, Boolean.TRUE);
return layoutActionService
.createSingleAction(actionDTO, Boolean.TRUE)
.name(CREATE_ACTION)
.tap(Micrometer.observation(observationRegistry));
} else {
actionDTO.setCollectionId(null);
// Client only knows about the default action ID, fetch branched action id to update the
@ -333,7 +347,10 @@ public class LayoutCollectionServiceCEImpl implements LayoutCollectionServiceCE
String branchedActionId = actionDTO.getId();
actionDTO.setId(null);
actionDTO.setBaseId(null);
return layoutActionService.updateNewActionByBranchedId(branchedActionId, actionDTO);
return layoutActionService
.updateNewActionByBranchedId(branchedActionId, actionDTO)
.name(UPDATE_ACTION)
.tap(Micrometer.observation(observationRegistry));
}
})
.collect(toMap(actionDTO -> actionDTO.getBaseId(), ActionDTO::getId)));
@ -354,7 +371,9 @@ public class LayoutCollectionServiceCEImpl implements LayoutCollectionServiceCE
log.error(throwable.getMessage());
return Mono.empty();
}))
.collectList();
.collectList()
.name(DELETE_ACTION)
.tap(Micrometer.observation(observationRegistry));
return deleteNonExistingActionMono
.then(newValidActionIdsMono)
@ -373,17 +392,27 @@ public class LayoutCollectionServiceCEImpl implements LayoutCollectionServiceCE
});
})
.flatMap(actionCollection -> actionCollectionService.update(actionCollection.getId(), actionCollection))
.name(ACTION_COLLECTION_UPDATE)
.tap(Micrometer.observation(observationRegistry))
.flatMap(actionCollectionRepository::setUserPermissionsInObject)
.flatMap(savedActionCollection ->
updateLayoutBasedOnContext(savedActionCollection).thenReturn(savedActionCollection))
.flatMap(savedActionCollection -> updateLayoutBasedOnContext(savedActionCollection)
.name(UPDATE_LAYOUT_BASED_ON_CONTEXT)
.tap(Micrometer.observation(observationRegistry))
.thenReturn(savedActionCollection))
.flatMap(savedActionCollection -> analyticsService.sendUpdateEvent(
savedActionCollection, actionCollectionService.getAnalyticsProperties(savedActionCollection)))
.flatMap(actionCollection -> actionCollectionService
.generateActionCollectionByViewMode(actionCollection, false)
.name(GENERATE_ACTION_COLLECTION_BY_VIEW_MODE)
.tap(Micrometer.observation(observationRegistry))
.flatMap(actionCollectionDTO1 -> actionCollectionService
.populateActionCollectionByViewMode(actionCollection.getUnpublishedCollection(), false)
.name(POPULATE_ACTION_COLLECTION_BY_VIEW_MODE)
.tap(Micrometer.observation(observationRegistry))
.flatMap(actionCollectionDTO2 -> actionCollectionService
.saveLastEditInformationInParent(actionCollectionDTO2)
.name(SAVE_ACTION_COLLECTION_LAST_EDIT_INFO)
.tap(Micrometer.observation(observationRegistry))
.thenReturn(actionCollectionDTO2))))
.flatMap(branchedActionCollection -> sendErrorReportsFromPageToCollection(branchedActionCollection));
}

View File

@ -28,6 +28,7 @@ import com.appsmith.server.solutions.PagePermission;
import com.appsmith.server.solutions.PagePermissionImpl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.micrometer.observation.ObservationRegistry;
import jakarta.validation.Validator;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
@ -86,6 +87,9 @@ public class ActionCollectionServiceImplTest {
PagePermission pagePermission;
ActionPermission actionPermission;
@MockBean
ObservationRegistry observationRegistry;
@MockBean
private Validator validator;
@ -108,7 +112,8 @@ public class ActionCollectionServiceImplTest {
policyGenerator,
applicationService,
applicationPermission,
actionPermission);
actionPermission,
observationRegistry);
layoutCollectionService = new LayoutCollectionServiceImpl(
newPageService,
@ -120,7 +125,8 @@ public class ActionCollectionServiceImplTest {
analyticsService,
actionCollectionRepository,
pagePermission,
actionPermission);
actionPermission,
observationRegistry);
Mockito.when(analyticsService.sendCreateEvent(Mockito.any()))
.thenAnswer(
@ -149,6 +155,10 @@ public class ActionCollectionServiceImplTest {
Mockito.when(analyticsService.sendArchiveEvent(Mockito.any(), Mockito.any()))
.thenAnswer(
invocationOnMock -> Mono.justOrEmpty(invocationOnMock.getArguments()[0]));
ObservationRegistry.ObservationConfig mockObservationConfig =
Mockito.mock(ObservationRegistry.ObservationConfig.class);
Mockito.when(observationRegistry.observationConfig()).thenReturn(mockObservationConfig);
}
@Test