Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3193] Add variableManagerInitializer in FormDescription #3194

Merged
merged 1 commit into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ image:doc/screenshots/showDeletionConfirmation.png[Deletion Confirmation Dialog,
- https://github.com/eclipse-sirius/sirius-web/issues/3133[#3133] [deck] Add OKR and Kanban samples in the Task template and Deck view stereotype.
- https://github.com/eclipse-sirius/sirius-web/issues/3145[#3145] [diagram] Add support for helper lines to facilitate the alignment of nodes with each other.
- https://github.com/eclipse-sirius/sirius-web/issues/3084[#3084] [form] Add SplitButton Widget.
- https://github.com/eclipse-sirius/sirius-web/issues/3193[#3193] [form] Allow the use of custom variables in the `VariableManager` initialized by the FormDescription.
- https://github.com/eclipse-sirius/sirius-web/issues/3193[#3193] [core] Added the methods `hasVariable(String name)` and `getParent()` in VariableManager to, respectively, detect if a variable manager defines a variable (it returns `false`, if the variable is defined by a parent `VariableManager`) and to navigate to a parent `VariableManager`.
Thanks to this capability, a specifier can easily retrieve a variable overriden by a child `VariableManager`.

=== Improvements

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2019, 2022 Obeo.
* Copyright (c) 2019, 2024 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
Expand Down Expand Up @@ -82,6 +82,14 @@ private Object get(String name) {
return value;
}

public VariableManager getParent() {
return this.parent;
}

public boolean hasVariable(String name) {
return this.variables.containsKey(name);
}

public VariableManager createChild() {
return new VariableManager(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.eclipse.sirius.components.forms.Form;
import org.eclipse.sirius.components.forms.components.FormComponent;
import org.eclipse.sirius.components.forms.components.FormComponentProps;
import org.eclipse.sirius.components.forms.description.FormDescription;
import org.eclipse.sirius.components.forms.renderer.FormRenderer;
import org.eclipse.sirius.components.forms.renderer.IWidgetDescriptor;
import org.eclipse.sirius.components.representations.Element;
Expand Down Expand Up @@ -91,6 +92,8 @@ public class FormEventProcessor implements IFormEventProcessor {

private final IFormPostProcessor formPostProcessor;

private final VariableManager variableManager;

public FormEventProcessor(FormEventProcessorConfiguration configuration,
ISubscriptionManager subscriptionManager, IWidgetSubscriptionManager widgetSubscriptionManager,
IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry, IFormPostProcessor formPostProcessor) {
Expand All @@ -105,9 +108,26 @@ public FormEventProcessor(FormEventProcessorConfiguration configuration,
this.representationRefreshPolicyRegistry = Objects.requireNonNull(representationRefreshPolicyRegistry);
this.formPostProcessor = Objects.requireNonNull(formPostProcessor);

this.variableManager = this.initializeVariableManager(this.formCreationParameters.getFormDescription());

Form form = this.refreshForm();
this.currentForm.set(form);
}

private VariableManager initializeVariableManager(FormDescription formDescription) {
var self = this.formCreationParameters.getObject();
if (this.currentForm.get() != null) {
self = this.objectService.getObject(this.editingContext, this.currentForm.get().getTargetObjectId()).orElse(self);
}

VariableManager initialVariableManager = new VariableManager();
initialVariableManager.put(VariableManager.SELF, self);
initialVariableManager.put(FormVariableProvider.SELECTION.name(), this.formCreationParameters.getSelection());
initialVariableManager.put(GetOrCreateRandomIdProvider.PREVIOUS_REPRESENTATION_ID, this.formCreationParameters.getId());
initialVariableManager.put(IEditingContext.EDITING_CONTEXT, this.formCreationParameters.getEditingContext());

var initializer = formDescription.getVariableManagerInitializer();
return initializer.apply(initialVariableManager);
}

@Override
Expand Down Expand Up @@ -176,15 +196,11 @@ private IRepresentationRefreshPolicy getDefaultRefreshPolicy() {
}

private Form refreshForm() {
VariableManager variableManager = new VariableManager();
var self = this.formCreationParameters.getObject();
if (this.currentForm.get() != null) {
self = this.objectService.getObject(this.editingContext, this.currentForm.get().getTargetObjectId()).orElse(self);
}
variableManager.put(VariableManager.SELF, self);
variableManager.put(FormVariableProvider.SELECTION.name(), this.formCreationParameters.getSelection());
variableManager.put(GetOrCreateRandomIdProvider.PREVIOUS_REPRESENTATION_ID, this.formCreationParameters.getId());
variableManager.put(IEditingContext.EDITING_CONTEXT, this.formCreationParameters.getEditingContext());

FormComponentProps formComponentProps = new FormComponentProps(variableManager, this.formCreationParameters.getFormDescription(), this.widgetDescriptors);
Element element = new Element(FormComponent.class, formComponentProps);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ public final class FormDescription implements IRepresentationDescription {

private List<PageDescription> pageDescriptions;

private Function<VariableManager, VariableManager> variableManagerInitializer;

private FormDescription() {
// Prevent instantiation
}
Expand Down Expand Up @@ -82,6 +84,10 @@ public List<PageDescription> getPageDescriptions() {
return this.pageDescriptions;
}

public Function<VariableManager, VariableManager> getVariableManagerInitializer() {
return this.variableManagerInitializer;
}

public static Builder newFormDescription(String id) {
return new Builder(id);
}
Expand Down Expand Up @@ -118,6 +124,8 @@ public static final class Builder {

private List<PageDescription> pageDescriptions;

private Function<VariableManager, VariableManager> variableManagerInitializer = Function.identity();

private Builder(String id) {
this.id = Objects.requireNonNull(id);
}
Expand Down Expand Up @@ -167,6 +175,11 @@ public Builder pageDescriptions(List<PageDescription> pageDescriptions) {
return this;
}

public Builder variableManagerInitializer(Function<VariableManager, VariableManager> variableManagerInitializer) {
this.variableManagerInitializer = Objects.requireNonNull(variableManagerInitializer);
return this;
}

public FormDescription build() {
FormDescription formDescription = new FormDescription();
formDescription.id = Objects.requireNonNull(this.id);
Expand All @@ -176,6 +189,7 @@ public FormDescription build() {
formDescription.labelProvider = Objects.requireNonNull(this.labelProvider);
formDescription.targetObjectIdProvider = Objects.requireNonNull(this.targetObjectIdProvider);
formDescription.pageDescriptions = Objects.requireNonNull(this.pageDescriptions);
formDescription.variableManagerInitializer = Objects.requireNonNull(this.variableManagerInitializer);
return formDescription;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,31 @@
*******************************************************************************/
package org.eclipse.sirius.web.application.controllers;

import static org.assertj.core.api.Assertions.assertThat;

import com.jayway.jsonpath.JsonPath;

import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;

import org.eclipse.sirius.components.collaborative.api.IEditingContextEventProcessor;
import org.eclipse.sirius.components.collaborative.api.IEditingContextEventProcessorRegistry;
import org.eclipse.sirius.components.collaborative.dto.CreateRepresentationInput;
import org.eclipse.sirius.components.collaborative.dto.CreateRepresentationSuccessPayload;
import org.eclipse.sirius.components.collaborative.forms.dto.EditSelectInput;
import org.eclipse.sirius.components.collaborative.forms.dto.FormEventInput;
import org.eclipse.sirius.components.collaborative.forms.dto.FormRefreshedEventPayload;
import org.eclipse.sirius.components.collaborative.forms.dto.PropertiesEventInput;
import org.eclipse.sirius.components.core.api.SuccessPayload;
import org.eclipse.sirius.components.forms.RichText;
import org.eclipse.sirius.components.forms.Select;
import org.eclipse.sirius.web.AbstractIntegrationTests;
import org.eclipse.sirius.web.TestIdentifiers;
import org.eclipse.sirius.web.services.MasterDetailsFormDescriptionProvider;
import org.eclipse.sirius.web.services.api.IGraphQLRequestor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
Expand All @@ -32,6 +45,7 @@
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.context.transaction.TestTransaction;
import org.springframework.transaction.annotation.Transactional;

import graphql.execution.DataFetcherResult;
Expand All @@ -54,6 +68,35 @@ subscription propertiesEvent($input: PropertiesEventInput!) {
}
""";

private static final String GET_FORM_EVENT_SUBSCRIPTION = """
subscription formEvent($input: FormEventInput!) {
formEvent(input: $input) {
__typename
}
}
""";

private static final String CREATE_REPRESENTATION_MUTATION = """
mutation createRepresentation($input: CreateRepresentationInput!) {
createRepresentation(input: $input) {
__typename
... on CreateRepresentationSuccessPayload {
representation {
id
}
}
}
}
""";

private static final String EDIT_SELECT_MUTATION = """
mutation editSelect($input: EditSelectInput!) {
editSelect(input: $input) {
__typename
}
}
""";

@Autowired
private IGraphQLRequestor graphQLRequestor;

Expand Down Expand Up @@ -87,4 +130,116 @@ public void givenSemanticObjectWhenWeSubscribeToItsPropertiesEventsThenTheFormIs
.thenCancel()
.verify(Duration.ofSeconds(10));
}

@Test
@DisplayName("Given a master / details based form representation, when we edit the master part, then the details part is updated")
@Sql(scripts = {"/scripts/initialize.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = {"/scripts/cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
public void givenMasterDetailsBasedFormRepresentationWhenWeEditTheMasterPartThenTheDetailsPartIsUpdated() {
this.commitInitializeStateBeforeThreadSwitching();

var input = new CreateRepresentationInput(
UUID.randomUUID(),
TestIdentifiers.ECORE_SAMPLE_PROJECT.toString(),
MasterDetailsFormDescriptionProvider.DESCRIPTION_ID,
TestIdentifiers.EPACKAGE_OBJECT.toString(),
"Master Details Form"
);
var result = this.graphQLRequestor.execute(CREATE_REPRESENTATION_MUTATION, input);

TestTransaction.flagForCommit();
TestTransaction.end();
TestTransaction.start();

String typename = JsonPath.read(result, "$.data.createRepresentation.__typename");
assertThat(typename).isEqualTo(CreateRepresentationSuccessPayload.class.getSimpleName());

String representationId = JsonPath.read(result, "$.data.createRepresentation.representation.id");
assertThat(representationId).isNotNull();

var formEventInput = new FormEventInput(UUID.randomUUID(), TestIdentifiers.ECORE_SAMPLE_PROJECT.toString(), representationId);
var flux = this.graphQLRequestor.subscribe(GET_FORM_EVENT_SUBSCRIPTION, formEventInput);

AtomicReference<String> selectId = new AtomicReference<>("");

Predicate<Object> initialFormContentMatcher = object -> Optional.of(object)
.filter(DataFetcherResult.class::isInstance)
.map(DataFetcherResult.class::cast)
.map(DataFetcherResult::getData)
.filter(FormRefreshedEventPayload.class::isInstance)
.map(FormRefreshedEventPayload.class::cast)
.map(FormRefreshedEventPayload::form)
.filter(form -> {
var widgets = form.getPages().get(0).getGroups().get(0).getWidgets();

widgets.stream()
.filter(Select.class::isInstance)
.map(Select.class::cast)
.findFirst()
.ifPresent(select -> selectId.set(select.getId()));

var richText = widgets.stream()
.filter(RichText.class::isInstance)
.map(RichText.class::cast)
.findFirst()
.orElse(null);

return richText.getValue().equals("first");
})
.isPresent();

Runnable changeMasterValue = () -> {
var editSelectInput = new EditSelectInput(UUID.randomUUID(), TestIdentifiers.ECORE_SAMPLE_PROJECT.toString(), representationId, selectId.get(), "second");

var editSelectResult = this.graphQLRequestor.execute(EDIT_SELECT_MUTATION, editSelectInput);
String editSelectResultTypename = JsonPath.read(editSelectResult, "$.data.editSelect.__typename");
assertThat(editSelectResultTypename).isEqualTo(SuccessPayload.class.getSimpleName());

TestTransaction.flagForCommit();
TestTransaction.end();
TestTransaction.start();
};

Predicate<Object> updatedFormContentMatcher = object -> Optional.of(object)
.filter(DataFetcherResult.class::isInstance)
.map(DataFetcherResult.class::cast)
.map(DataFetcherResult::getData)
.filter(FormRefreshedEventPayload.class::isInstance)
.map(FormRefreshedEventPayload.class::cast)
.map(FormRefreshedEventPayload::form)
.filter(form -> {
var widgets = form.getPages().get(0).getGroups().get(0).getWidgets();
var richText = widgets.stream()
.filter(RichText.class::isInstance)
.map(RichText.class::cast)
.findFirst()
.orElse(null);

return richText.getValue().equals("second");
})
.isPresent();

StepVerifier.create(flux)
.expectNextMatches(initialFormContentMatcher)
.then(changeMasterValue)
.expectNextMatches(updatedFormContentMatcher)
.thenCancel()
.verify(Duration.ofSeconds(10));
}

/**
* Used to commit the state of the transaction after its initialization by the @Sql annotation
* in order to make the state persisted in the database. Without this, the initialized state
* will not be visible by the various repositories when the test will switch threads to use
* the thread of the editing context.
*
* This should not be used every single time but only in the couple integrations tests that are
* required to interact with repositories while inside an editing context event handler or a
* representation event handler for example.
*/
private void commitInitializeStateBeforeThreadSwitching() {
TestTransaction.flagForCommit();
TestTransaction.end();
TestTransaction.start();
}
}