Skip to content

Commit

Permalink
Unify colors and fonts in the composer entity headers (Issue #5986, PR
Browse files Browse the repository at this point in the history
…#6019)

# Description

https://github.com/user-attachments/assets/22615d5b-543a-4279-8bd6-5ab160631e7b

closes #5986

# 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: )
  • Loading branch information
matborowczyk authored and inmantaci committed Nov 7, 2024
1 parent fdd8698 commit 7e8a884
Show file tree
Hide file tree
Showing 11 changed files with 149 additions and 40 deletions.
6 changes: 6 additions & 0 deletions changelogs/unreleased/5986-header-colors.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
description: Unify colors and fonts in the composer entity headers
issue-nr: 5986
change-type: patch
destination-branches: [master]
sections:
minor-improvement: "{{description}}"
4 changes: 3 additions & 1 deletion src/UI/Components/Diagram/Canvas.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,9 @@ describe("Canvas.tsx", () => {

const header = screen.getByJointSelector("header");

expect(header).toHaveClass("-core");
expect(header.getAttribute("fill")).toContain(
"var(--pf-v5-global--palette--gold-400)",
);
});

it("navigating out of the View works correctly", async () => {
Expand Down
10 changes: 10 additions & 0 deletions src/UI/Components/Diagram/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ describe("createComposerEntity", () => {
expect(coreEntity.get("holderName")).toBe(undefined);
expect(coreEntity.get("isEmbedded")).toBe(undefined);
expect(coreEntity.get("isCore")).toBe(true);
expect(coreEntity.attr("header/fill")).toBe(
"var(--pf-v5-global--palette--gold-400)",
);
expect(coreEntity.get("isInEditMode")).toBe(false);
});

Expand All @@ -51,8 +54,12 @@ describe("createComposerEntity", () => {
expect(embeddedEntity.get("holderName")).toBe(containerModel.name);
expect(embeddedEntity.get("isEmbedded")).toBe(true);
expect(embeddedEntity.get("isCore")).toBe(undefined);
expect(embeddedEntity.attr("header/fill")).toBe(
"var(--pf-v5-global--palette--blue-400)",
);
expect(embeddedEntity.get("isInEditMode")).toBe(false);
});

it("creates a new entity with inster-service relations", () => {
const childEntity = createComposerEntity({
serviceModel: childModel,
Expand All @@ -64,6 +71,9 @@ describe("createComposerEntity", () => {
expect(childEntity.get("isEmbedded")).toBe(undefined);
expect(childEntity.get("isCore")).toBe(undefined);
expect(childEntity.get("isInEditMode")).toBe(false);
expect(childEntity.attr("header/fill")).toBe(
"var(--pf-v5-global--palette--purple-500)",
);
expect(childEntity.get("relatedTo")).toMatchObject(new Map());
});
});
Expand Down
9 changes: 6 additions & 3 deletions src/UI/Components/Diagram/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import candidateImage from "./icons/candidate-icon.svg";
import {
ComposerEntityOptions,
EmbeddedEventEnum,
EntityType,
relationId,
} from "./interfaces";
import { Link, ServiceEntityBlock } from "./shapes";
Expand Down Expand Up @@ -59,15 +60,17 @@ export function createComposerEntity({
}

if (isEmbedded) {
instanceAsTable.setTabColor("embedded");
instanceAsTable.setTabColor(EntityType.EMBEDDED);
instanceAsTable.set("embeddedTo", embeddedTo);
instanceAsTable.set("isEmbedded", isEmbedded);
instanceAsTable.set("holderName", holderName);
// If the instance is not core, we need to apply its stencil name to the shape to later disable its corresponding stencil in the sidebar
instanceAsTable.set("stencilName", stencilName);
} else if (isCore) {
instanceAsTable.set("isCore", isCore);
instanceAsTable.setTabColor("core");
instanceAsTable.setTabColor(EntityType.CORE);
} else {
instanceAsTable.setTabColor(EntityType.RELATION);
}

instanceAsTable.set("isInEditMode", isInEditMode);
Expand Down Expand Up @@ -514,7 +517,7 @@ export function updateAttributes(
if (isInitial) {
serviceEntity.appendColumns(attributesToDisplay);
} else {
serviceEntity.editColumns(
serviceEntity.updateColumns(
attributesToDisplay,
serviceEntity.attributes.isCollapsed,
);
Expand Down
25 changes: 25 additions & 0 deletions src/UI/Components/Diagram/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { dia, g } from "@inmanta/rappid";
import {
global_palette_purple_500,
global_palette_gold_400,
global_palette_blue_400,
} from "@patternfly/react-tokens";
import {
EmbeddedEntity,
InstanceAttributeModel,
Expand All @@ -10,6 +15,15 @@ import {
ServiceOrderItemConfig,
} from "@/Slices/Orders/Core/Query";

/**
* Enum representing header colors for different types of entities.
*/
const HeaderColor = {
CORE: global_palette_gold_400.var,
EMBEDDED: global_palette_blue_400.var,
RELATION: global_palette_purple_500.var,
};

/**
* Enum representing types of actions possible to perform on entities.
*/
Expand All @@ -19,6 +33,15 @@ enum ActionEnum {
DELETE = "delete",
}

/**
* Enum representing types of entities on the canvas.
*/
enum EntityType {
CORE = "core",
EMBEDDED = "embedded",
RELATION = "relation",
}

/**
* Interface representing data for a column for displayable attributes in the entity.
*/
Expand Down Expand Up @@ -175,6 +198,7 @@ interface ComposerEntityOptions {
}

export {
HeaderColor,
ActionEnum,
ColumnData,
RouterOptions,
Expand All @@ -190,4 +214,5 @@ export {
TypeEnum,
EmbeddedEventEnum,
ComposerEntityOptions,
EntityType,
};
103 changes: 89 additions & 14 deletions src/UI/Components/Diagram/shapes.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { dia, shapes, util } from "@inmanta/rappid";
import {
global_FontFamily_monospace,
global_palette_white,
} from "@patternfly/react-tokens";
import { updateLabelPosition } from "./helpers";
import expandButton from "./icons/expand-icon.svg";
import { ColumnData } from "./interfaces";
import { ColumnData, EntityType, HeaderColor } from "./interfaces";

/**
* https://resources.jointjs.com/tutorial/custom-elements
* https://resources.jointjs.com/tutorial/ts-shape
*
* actions that are in ServiceEntity returns updated state of the object, we follow convention introduced by JointJS team in their demos
*/
export class ServiceEntityBlock extends shapes.standard.HeaderedRecord {
defaults() {
Expand All @@ -23,17 +29,17 @@ export class ServiceEntityBlock extends shapes.standard.HeaderedRecord {
attrs: {
body: {
class: "joint-entityBlock-body",
strokeWidth: 1,
strokeWidth: 0,
cursor: "default",
},
header: {
class: "joint-entityBlock-header",
strokeWidth: 1,
strokeWidth: 0,
cursor: "grab",
},
headerLabel: {
class: "joint-entityBlock-header-label",
fontFamily: global_FontFamily_monospace.var,
textTransform: "uppercase",
fill: global_palette_white.var,
fontSize: 14,
textWrap: {
ellipsis: true,
Expand Down Expand Up @@ -220,7 +226,14 @@ export class ServiceEntityBlock extends shapes.standard.HeaderedRecord {
}
}

setName(name: string, options?: object) {
/**
* Sets the name of the entity and updates the header label with a shortened version if necessary.
*
* @param {string} name - The name to set for the entity.
* @param {object} [options] - Optional settings for the attribute update.
* @returns {this} The current instance for method chaining.
*/
setName(name: string, options?: object): this {
const shortenName = util.breakText(
name,
{ width: 140, height: 30 },
Expand All @@ -247,12 +260,23 @@ export class ServiceEntityBlock extends shapes.standard.HeaderedRecord {
}
}

/**
* Retrieves the inter-service relations of the entity.
*
* @returns {Map<dia.Cell.ID, string> | null} - Map of relations
*/
getRelations(): Map<dia.Cell.ID, string> | null {
const relations = this.get("relatedTo");

return relations || null;
}

/**
* Adds a inter-service relation to the entity.
*
* @param {dia.Cell.ID} id - The identifier of the relation.
* @param {string} relationName - The name of the relation.
*/
addRelation(id: dia.Cell.ID, relationName: string): void {
const currentRelation = this.getRelations();

Expand All @@ -265,6 +289,12 @@ export class ServiceEntityBlock extends shapes.standard.HeaderedRecord {
}
}

/**
* Removes a inter-service relation by its identifier.
*
* @param {string} id - The identifier of the relation to remove.
* @returns {boolean} True if the relation was removed, false otherwise.
*/
removeRelation(id: string): boolean {
const currentRelation = this.getRelations();
let wasThereRelationToRemove = false;
Expand All @@ -277,17 +307,42 @@ export class ServiceEntityBlock extends shapes.standard.HeaderedRecord {
return wasThereRelationToRemove;
}

/**
* Retrieves the name of the entity.
*
* @returns {string} The name of the entity.
*/
getName(): string {
return this.get("entityName");
}

setTabColor(color: string) {
const currentClassName = this.attr(["header", "class"]);

return this.attr(["header", "class"], `${currentClassName} -${color}`);
/**
* Sets the tab color based on the entity type.
*
* @param {EntityType} type - The type of the entity.
* @returns {this} updated entity block - this.attr(x, y) returns updated object - or the current entity block as default scenario
*/
setTabColor(type: EntityType): this {
switch (type) {
case EntityType.CORE:
return this.attr(["header", "fill"], HeaderColor.CORE);
case EntityType.EMBEDDED:
return this.attr(["header", "fill"], HeaderColor.EMBEDDED);
case EntityType.RELATION:
return this.attr(["header", "fill"], HeaderColor.RELATION);
default:
return this;
}
}

appendColumns(data: Array<ColumnData>, initializeButton = true) {
/**
* Appends columns to the entity and optionally initializes a expand/collapse button.
*
* @param {Array<ColumnData>} data - The array of column data to append.
* @param {boolean} [initializeButton=true] - Flag indicating whether to initialize a button to expand/collapse.
* @returns {this} The updated entity block.
*/
appendColumns(data: Array<ColumnData>, initializeButton = true): this {
this._setColumns(data, initializeButton);

if (initializeButton && this.get("isCollapsed")) {
Expand All @@ -297,13 +352,28 @@ export class ServiceEntityBlock extends shapes.standard.HeaderedRecord {
return this;
}

editColumns(data: Array<ColumnData>, shouldBeCollapsed = true) {
/**
* Updates the columns of the entity and optionally sets the collapsed state.
*
* @param {Array<ColumnData>} data - The array of column data to set.
* @param {boolean} [shouldBeCollapsed=true] - Flag indicating whether the entity should be collapsed.
* @returns {this} The updated entity block.
*/
updateColumns(data: Array<ColumnData>, shouldBeCollapsed = true): this {
this._setColumns(data, shouldBeCollapsed);

return this;
}

appendButton() {
/**
* Appends a button to the entity block.
*
* This method sets the padding and attributes for the spacer, button body, and toggle button.
* It positions the button based on the bounding box of the entity block.
*
* @returns {void}
*/
appendButton(): void {
this.set("padding", {
bottom: 44,
left: 10,
Expand Down Expand Up @@ -343,7 +413,12 @@ export class ServiceEntityBlock extends shapes.standard.HeaderedRecord {
});
}

toJSON() {
/**
* Converts the entity to a JSON representation.
*
* @returns {dia.Cell.JSON<any, dia.Element.Attributes>} The JSON representation of the entity.
*/
toJSON(): dia.Cell.JSON<any, dia.Element.Attributes> {
const json = super.toJSON();

// keeping only the `items` attribute as columns are omitted in our use-case
Expand Down
4 changes: 2 additions & 2 deletions src/UI/Components/Diagram/stencil/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ describe("createStencilElement", () => {
x: 233,
d: "M 0 0 H calc(w) V calc(h) H 0 Z",
strokeWidth: 2,
fill: "#0066cc",
fill: "var(--pf-v5-global--palette--blue-400)",
stroke: "none",
class: "body_default",
});
expect(embeddedElementWithModel.attributes.attrs?.bodyTwo).toStrictEqual({
width: 240,
height: 40,
fill: "#FFFFFF",
fill: "var(--pf-v5-global--palette--white)",
stroke: "none",
class: "bodyTwo_default",
});
Expand Down
6 changes: 4 additions & 2 deletions src/UI/Components/Diagram/stencil/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { shapes } from "@inmanta/rappid";
import { global_palette_white } from "@patternfly/react-tokens";
import { v4 as uuidv4 } from "uuid";
import { EmbeddedEntity, InstanceAttributeModel, ServiceModel } from "@/Core";
import { HeaderColor } from "../interfaces";

/**
* It recursively goes through embedded entities in the service model or embedded entity and creates stencil elements for each of them.
Expand Down Expand Up @@ -67,14 +69,14 @@ export const createStencilElement = (
width: 7,
height: 40,
x: 233,
fill: isEmbedded ? "#0066cc" : "#6753AC",
fill: isEmbedded ? HeaderColor.EMBEDDED : HeaderColor.RELATION,
stroke: "none",
},
bodyTwo: {
class: "bodyTwo_" + name,
width: 240,
height: 40,
fill: "#FFFFFF",
fill: global_palette_white.var,
stroke: "none",
},
label: {
Expand Down
3 changes: 2 additions & 1 deletion src/UI/Components/Diagram/stencil/instanceStencil.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { dia, ui } from "@inmanta/rappid";
import { global_palette_white } from "@patternfly/react-tokens";
import { ServiceModel } from "@/Core";
import {
CreateModifierHandler,
Expand Down Expand Up @@ -67,7 +68,7 @@ export class InstanceStencilTab {
centre: false,
dx: 0,
dy: 0,
background: "#FFFFFF",
background: global_palette_white.var,
},
});
stencilElement.appendChild(this.stencil.el);
Expand Down
Loading

0 comments on commit 7e8a884

Please sign in to comment.