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

Add attribute manipulation to the CME #867

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
15de329
Add textual menu for drag edge and functionality
RadStr Jan 17, 2025
ba5736c
Change Selection UI based on issue
RadStr Jan 17, 2025
d8059da
Fix setSelectedNodes to work with internal selection
RadStr Jan 17, 2025
2278f62
Fix not adding multi-edges in selection extension
RadStr Jan 17, 2025
26bd18f
Add option to extend selection only through edges
RadStr Jan 17, 2025
26b65d7
Save attributes to vis model + tests + visibility
RadStr Jan 19, 2025
f8a0087
Add dialog to set attribute order on node
RadStr Jan 19, 2025
fd42f38
Show names instead of ids for attributes
RadStr Jan 20, 2025
7ba414e
Fix not removing attribute from vis model on del
RadStr Jan 20, 2025
b7561c4
WIP - Fix removal of selection node drag
RadStr Jan 21, 2025
ee9fa36
Remove notDraggedNodes reference, using all selected instead
RadStr Jan 21, 2025
7bbb244
Fix unselecting whole group when only part is unselected
RadStr Jan 21, 2025
f7beb24
npm run lint and remove debug prints
RadStr Jan 21, 2025
cca2374
Remove debug print on node
RadStr Jan 21, 2025
81b5d6f
Fix sticky groups
RadStr Jan 21, 2025
df4a0be
Finalize Edit attributes on node dialog
RadStr Jan 21, 2025
a857a66
Fix selection extension not working correctly
RadStr Jan 22, 2025
bf7aeed
Merge branch 'main' into cme/UI-changes
RadStr Jan 22, 2025
2b5524f
Add TODOs
RadStr Jan 22, 2025
ac58627
Merge branch 'cme/UI-changes' into cme-feature/attribute-manipulation
RadStr Jan 22, 2025
d141295
Fix build errors
RadStr Jan 22, 2025
4e88452
Respect attribute order on reopen in dragdrop
RadStr Jan 22, 2025
24dc066
Try to improve edit attr dialog style
RadStr Jan 22, 2025
6964c8c
Merge branch 'main' into cme-feature/attribute-manipulation-package-l…
RadStr Jan 22, 2025
c83fb13
Add dependency for drag-drop in cme
RadStr Jan 22, 2025
324d9d5
Change icons and add key to react component
RadStr Jan 22, 2025
be8b5d4
Add TODOs, fix typos and remove create class on drag edge
RadStr Jan 22, 2025
9b88336
Remove the rounded corners in dnd dialog
RadStr Jan 23, 2025
513c37c
Add button that creates new attribute to the dnd dialog
RadStr Jan 29, 2025
cccc0e6
Add localization for dnd, simplify, move logic to controller
RadStr Jan 30, 2025
b3ed4db
Merge branch 'main' into cme-feature/attribute-manipulation-package-l…
RadStr Jan 30, 2025
805f43e
Code review fixes
RadStr Feb 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion applications/conceptual-model-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"iri": "^1.3.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tsx": "^4.19.2"
"tsx": "^4.19.2",
"@hello-pangea/dnd": "17.0.0"
},
"devDependencies": {
"@eslint/config-array": "^0.19.1",
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ export const addClassNeighborhoodToVisualModelAction = (
areIdentifiersFromVisualModel: false
};
const neighborhoodPromise = extendSelectionAction(notifications, graph, classes, inputForExtension,
[ExtensionType.ASSOCIATION, ExtensionType.GENERALIZATION], VisibilityFilter.ALL, false, null);
[ExtensionType.Association, ExtensionType.Generalization], VisibilityFilter.All, false, null);
neighborhoodPromise.then(neighborhood => {
const classesOrClassProfilesToAdd: EntityToAddToVisualModel[] = [{identifier, position: null}];

// We have to filter the source class, whose neighborhood we are adding, from the extension
classesOrClassProfilesToAdd.push(...neighborhood.selectionExtension.nodeSelection.filter(node => node !== identifier).map(node => ({identifier: node, position: null})));
addSemanticEntitiesToVisualModelAction(notifications, classes, graph, visualModel, diagram, classesOrClassProfilesToAdd);
})
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ClassesContextType } from "../context/classes-context";
import { findPositionForNewNodesUsingLayouting } from "./layout-visual-model";
import { findSourceModelOfEntity } from "../service/model-service";
import { createLogger } from "../application";
import { getVisualNodeContentBasedOnExistingEntities } from "./add-semantic-attribute-to-visual-model";

const LOG = createLogger(import.meta.url);

Expand Down Expand Up @@ -43,20 +44,23 @@ export async function addSemanticClassProfileToVisualModelAction(
entityIdentifier, model.getId(),
isSemanticModelClassUsage, (entity) => {
addSemanticClassProfileToVisualModelCommand(
visualModel, entity, model.getId(),
position);
classes, visualModel, entity,
model.getId(), position);
addRelatedEntitiesAction(
notifications, graph, visualModel, Object.values(entities),
graph.models, entity);
});
}

function addSemanticClassProfileToVisualModelCommand(
classes: ClassesContextType,
visualModel: WritableVisualModel,
entity: SemanticModelClassUsage,
model: string,
position: { x: number, y: number },
) {
const content = getVisualNodeContentBasedOnExistingEntities(
classes, entity);
visualModel.addVisualNode({
model: model,
representedEntity: entity.id,
Expand All @@ -65,7 +69,7 @@ function addSemanticClassProfileToVisualModelCommand(
y: position.y,
anchored: null,
},
content: [],
content,
visualModels: [],
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { addRelatedEntitiesAction } from "./add-related-entities-to-visual-model
import { findPositionForNewNodesUsingLayouting } from "./layout-visual-model";
import { ClassesContextType } from "../context/classes-context";
import { addVisualNode } from "../dataspecer/visual-model/command/add-visual-node";
import { getVisualNodeContentBasedOnExistingEntities } from "./add-semantic-attribute-to-visual-model";

export async function addSemanticClassToVisualModelAction(
notifications: UseNotificationServiceWriterType,
Expand All @@ -28,7 +29,10 @@ export async function addSemanticClassToVisualModelAction(
withAggregatedEntity(notifications, entities,
entityIdentifier, modelIdentifier,
isSemanticModelClass, (entity) => {
addVisualNode(visualModel, entity, modelIdentifier, position);
// TODO PRQuestion: How to handle this? Put it into the addVisualNode?
const content = getVisualNodeContentBasedOnExistingEntities(
classes, entity);
addVisualNode(visualModel, entity, modelIdentifier, position, content);
addRelatedEntitiesAction(
notifications, graph, visualModel, Object.values(entities),
graph.models, entity);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { isVisualNode, WritableVisualModel } from "@dataspecer/core-v2/visual-model";
import { UseNotificationServiceWriterType } from "../notification/notification-service-context";
import { getDomainAndRange } from "../util/relationship-utils";
import { isSemanticModelAttributeUsage, SemanticModelClassUsage } from "@dataspecer/core-v2/semantic-model/usage/concepts";
import { isSemanticModelAttribute, SemanticModelClass } from "@dataspecer/core-v2/semantic-model/concepts";
import { ClassesContextType } from "../context/classes-context";

// TODO RadStr: Maybe not really an action but just helper method?
export function addSemanticAttributeToVisualModelAction(
notifications: UseNotificationServiceWriterType,
visualModel: WritableVisualModel,
domainIdentifier: string,
attribute: string,
position: number | null,
) {
const visualNode = visualModel.getVisualEntityForRepresented(domainIdentifier);
if(visualNode === null) {
notifications.error("The visual node representing domain is not present.");
return;
}
if(!isVisualNode(visualNode)) {
notifications.error("The visual node representing domain of attribute is not a visual node");
return;
}
if(position === null) {
position = visualNode.content.length;
}

const newContent = [...visualNode.content];
newContent.splice(position, 0, attribute);

visualModel.updateVisualEntity(visualNode.identifier, {content: newContent});
}

// TODO RadStr: 1 Action per file
/**
* @returns The visual content (attributes) of node to relevant values existing in semantic model.
*/
export function getVisualNodeContentBasedOnExistingEntities(
RadStr marked this conversation as resolved.
Show resolved Hide resolved
classes: ClassesContextType,
entity: SemanticModelClass | SemanticModelClassUsage,
): string[] {
const nodeContent: string[] = [];
const attributes = classes.relationships.filter(isSemanticModelAttribute);
const attributesProfiles = classes.profiles.filter(isSemanticModelAttributeUsage);

const nodeAttributes = attributes
.filter(isSemanticModelAttribute)
.filter((attr) => getDomainAndRange(attr).domain?.concept === entity.id);

const nodeAttributeProfiles = attributesProfiles
.filter(isSemanticModelAttributeUsage)
.filter((attr) => getDomainAndRange(attr).domain?.concept === entity.id);

for (const attribute of nodeAttributes) {
nodeContent.push(attribute.id);
}

for (const attributeProfile of nodeAttributeProfiles) {
nodeContent.push(attributeProfile.id);
}

return nodeContent;
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ function createDefaultRelationshipProfiles(
) {
const writableSemanticModel = graph.models.get(writableCmeModel.dsIdentifier) as InMemorySemanticModel; // Casting ... the correctness should be already validated
for(const edgeToProfile of edgesToProfile) {
createDefaultRelationshipProfile(notifications, graph, writableSemanticModel, visualModel, edgeToProfile, createdClassProfiles, shouldBeAddedToVisualModel);
createDefaultRelationshipProfile(
notifications, graph, writableSemanticModel, visualModel,
edgeToProfile, createdClassProfiles, shouldBeAddedToVisualModel);
}
}

Expand All @@ -143,13 +145,13 @@ function createDefaultRelationshipProfile(
createdClassProfiles: Record<string, string | null>,
shouldBeAddedToVisualModel: boolean
) {
const relationshipOrRelationshipProfileToBeProfiled = getAndValidateRelationshipOrRelationshipProfileToBeProfiled(notifications, graph, entityToProfile);
if(relationshipOrRelationshipProfileToBeProfiled === null) {
const relationshipToProfile = getAndValidateRelationshipToBeProfiled(notifications, graph, entityToProfile);
if(relationshipToProfile === null) {
return;
}

const ends: SemanticModelRelationshipEndUsage[] | undefined = [];
for (const end of relationshipOrRelationshipProfileToBeProfiled.ends) {
for (const end of relationshipToProfile.ends) {
if(end.concept === null) {
return;
}
Expand All @@ -170,12 +172,12 @@ function createDefaultRelationshipProfile(
}

let usageNote = null;
if(isSemanticModelRelationshipUsage(relationshipOrRelationshipProfileToBeProfiled)) {
usageNote = relationshipOrRelationshipProfileToBeProfiled.usageNote;
if(isSemanticModelRelationshipUsage(relationshipToProfile)) {
usageNote = relationshipToProfile.usageNote;
}

const { success, id: identifier } = model.executeOperation(createRelationshipUsage({
usageOf: relationshipOrRelationshipProfileToBeProfiled.id,
usageOf: relationshipToProfile.id,
usageNote: usageNote,
ends: ends,
}));
Expand All @@ -192,26 +194,27 @@ function createDefaultRelationshipProfile(
}
}

function getAndValidateRelationshipOrRelationshipProfileToBeProfiled(
function getAndValidateRelationshipToBeProfiled(
notifications: UseNotificationServiceWriterType,
graph: ModelGraphContextType,
entityToProfile: string
): SemanticModelRelationship | SemanticModelRelationshipUsage | null {
const relationshipOrRelationshipProfileToBeProfiled = graph.aggregatorView.getEntities()?.[entityToProfile]?.aggregatedEntity;
if(relationshipOrRelationshipProfileToBeProfiled === undefined || relationshipOrRelationshipProfileToBeProfiled === null) {
const relationshipToProfile = graph.aggregatorView.getEntities()?.[entityToProfile]?.aggregatedEntity;
if(relationshipToProfile === undefined || relationshipToProfile === null) {
notifications.error("The entity (edge) to be profiled from selection is not present in aggregatorView");
return null;
}
if(isSemanticModelClassUsage(relationshipOrRelationshipProfileToBeProfiled)) { // The visual edge representing class profile
if(isSemanticModelClassUsage(relationshipToProfile)) { // The visual edge representing class profile
return null;
}
if(isSemanticModelGeneralization(relationshipOrRelationshipProfileToBeProfiled)) {
if(isSemanticModelGeneralization(relationshipToProfile)) {
return null;
}
if(!isSemanticModelRelationship(relationshipOrRelationshipProfileToBeProfiled) && !isSemanticModelRelationshipUsage(relationshipOrRelationshipProfileToBeProfiled)) {
if(!isSemanticModelRelationship(relationshipToProfile) &&
!isSemanticModelRelationshipUsage(relationshipToProfile)) {
notifications.error("The entity to be profiled from selection is not a association or association profile");
return null;
}

return relationshipOrRelationshipProfileToBeProfiled;
return relationshipToProfile;
}
Loading
Loading