From dbfe5c75af924087167b43fa9dea8936983fea26 Mon Sep 17 00:00:00 2001 From: Mateusz Borowczyk Date: Thu, 21 Nov 2024 09:08:07 +0100 Subject: [PATCH] Instance composer will render all required by default embedded entities in the canvas (Issue #6037, PR #6054) # Description Render all required default embedded entities on Canvas, previously we would render only one, now multiple entites of the same type are also covered ![Screenshot 2024-11-15 at 14 14 58](https://github.com/user-attachments/assets/e1798a7e-123a-484f-8e05-d5bb7f46308b) closes #6037 # Self Check: Strike through any lines that are not applicable (`~~line~~`) then check the box - [ ] Attached issue to pull request - [ ] Changelog entry - [ ] Code is clear and sufficiently documented - [ ] Sufficient test cases (reproduces the bug/tests the requested feature) - [ ] Correct, in line with design - [ ] End user documentation is included or an issue is created for end-user documentation (add ref to issue here: ) --- .../6037-multiple-default-embedded.yml | 6 ++ src/UI/Components/Diagram/actions.test.ts | 36 ++++++++- src/UI/Components/Diagram/actions.ts | 81 +++++++++++++------ .../Diagram/components/RightSidebar.tsx | 2 +- 4 files changed, 98 insertions(+), 27 deletions(-) create mode 100644 changelogs/unreleased/6037-multiple-default-embedded.yml diff --git a/changelogs/unreleased/6037-multiple-default-embedded.yml b/changelogs/unreleased/6037-multiple-default-embedded.yml new file mode 100644 index 000000000..8452e50a2 --- /dev/null +++ b/changelogs/unreleased/6037-multiple-default-embedded.yml @@ -0,0 +1,6 @@ +description: Instance composer will render all required by default embedded entities in the canvas +issue-nr: 6037 +change-type: patch +destination-branches: [master] +sections: + minor-improvement: "{{description}}" diff --git a/src/UI/Components/Diagram/actions.test.ts b/src/UI/Components/Diagram/actions.test.ts index 52b59bf99..ffc07f344 100644 --- a/src/UI/Components/Diagram/actions.test.ts +++ b/src/UI/Components/Diagram/actions.test.ts @@ -251,7 +251,7 @@ describe("updateAttributes", () => { }); }); -describe("createComposerEntity", () => { +describe("addDefaultEntities", () => { it("return empty array for service without embedded entities to add to the graph ", () => { const graph = new dia.Graph({}); const embedded = addDefaultEntities(graph, parentModel); @@ -279,6 +279,40 @@ describe("createComposerEntity", () => { expect(embedded[0].getName()).toStrictEqual("child_container"); }); + it("adds all default entities to the graph for service with embedded entity with lower_limit > 1 ", () => { + const dispatchEventSpy = jest.spyOn(document, "dispatchEvent"); + const graph = new dia.Graph({}); + const customModel = { + ...containerModel, + embedded_entities: [ + { ...containerModel.embedded_entities[0], lower_limit: 2 }, + ], + }; + + const embedded = addDefaultEntities(graph, customModel); + + expect(dispatchEventSpy).toHaveBeenCalledTimes(2); + + //assert the arguments of the first call - calls is array of the arguments of each call + expect( + (dispatchEventSpy.mock.calls[0][0] as CustomEvent).detail, + ).toMatchObject({ + action: "add", + name: "child_container", + }); + //assert the arguments of the second call - calls is array of the arguments of each call + expect( + (dispatchEventSpy.mock.calls[1][0] as CustomEvent).detail, + ).toMatchObject({ + action: "add", + name: "child_container", + }); + expect(embedded.length).toBe(2); + + expect(embedded[0].getName()).toStrictEqual("child_container"); + expect(embedded[1].getName()).toStrictEqual("child_container"); + }); + it("adds default entity for service with nested embedded entities to the graph ", () => { const dispatchEventSpy = jest.spyOn(document, "dispatchEvent"); diff --git a/src/UI/Components/Diagram/actions.ts b/src/UI/Components/Diagram/actions.ts index 3b5c0638e..f341736de 100644 --- a/src/UI/Components/Diagram/actions.ts +++ b/src/UI/Components/Diagram/actions.ts @@ -451,44 +451,75 @@ export function addDefaultEntities( ): ServiceEntityBlock[] { const embedded_entities = service.embedded_entities .filter((embedded_entity) => embedded_entity.lower_limit > 0) - .map((embedded_entity) => { + .flatMap((embedded_entity) => { const fieldCreator = new FieldCreator(new CreateModifierHandler()); const fields = fieldCreator.attributesToFields( embedded_entity.attributes, ); + const attributes = createFormState(fields); - const embeddedEntity = createComposerEntity({ - serviceModel: embedded_entity, - isCore: false, - isInEditMode: false, - attributes: createFormState(fields), - isEmbedded: true, - holderName: service.name, - }); - - document.dispatchEvent( - new CustomEvent("updateStencil", { - detail: { - name: embedded_entity.name, - action: EmbeddedEventEnum.ADD, - }, - }), - ); + if (embedded_entity.lower_limit > 1) { + const embedded_entities: ServiceEntityBlock[] = []; - embeddedEntity.addTo(graph); - const subEmbeddedEntities = addDefaultEntities(graph, embedded_entity); + for (let i = 0; i < embedded_entity.lower_limit; i++) { + embedded_entities.push( + addSingleEntity(graph, embedded_entity, attributes, service.name), + ); + } - subEmbeddedEntities.forEach((entity) => { - entity.set("embeddedTo", embeddedEntity.id); - }); - connectEntities(graph, embeddedEntity, subEmbeddedEntities); + return embedded_entities; + } - return embeddedEntity; + return addSingleEntity(graph, embedded_entity, attributes, service.name); }); return embedded_entities; } +/** + * Helper function to add single default Embedded Entity to the graph - it's created to avoid code duplication in the addDefaultEntities function + * + * @param {dia.Graph} graph - The jointJS graph to which entities should be added. + * @param {EmbeddedEntity} service - The service model or embedded entity used to generate the default entities. + * @param {InstanceAttributeModel} attributes - attributes of given instance/entity + * @param {string} holderName - name of the entity to which it is embedded/connected + * @returns {ServiceEntityBlock} + */ +const addSingleEntity = ( + graph: dia.Graph, + model: EmbeddedEntity, + attributes: InstanceAttributeModel, + holderName: string, +): ServiceEntityBlock => { + const embeddedEntity = createComposerEntity({ + serviceModel: model, + isCore: false, + isInEditMode: false, + attributes, + isEmbedded: true, + holderName, + }); + + document.dispatchEvent( + new CustomEvent("updateStencil", { + detail: { + name: model.name, + action: EmbeddedEventEnum.ADD, + }, + }), + ); + + embeddedEntity.addTo(graph); + const subEmbeddedEntities = addDefaultEntities(graph, model); + + subEmbeddedEntities.forEach((entity) => { + entity.set("embeddedTo", embeddedEntity.id); + }); + connectEntities(graph, embeddedEntity, subEmbeddedEntities); + + return embeddedEntity; +}; + /** * Function that iterates through service instance attributes for values and appends in jointJS entity for display * diff --git a/src/UI/Components/Diagram/components/RightSidebar.tsx b/src/UI/Components/Diagram/components/RightSidebar.tsx index 580a71402..8cd26cbc0 100644 --- a/src/UI/Components/Diagram/components/RightSidebar.tsx +++ b/src/UI/Components/Diagram/components/RightSidebar.tsx @@ -193,7 +193,7 @@ export const RightSidebar: React.FC = ({ editable }) => { const lowerLimit = entityState.min; const isLowerLimitReached = - lowerLimit && entityState.current === lowerLimit; + lowerLimit && entityState.current <= lowerLimit; return !isCellCore && canBeRemoved && !isLowerLimitReached; });