Générer un diagramme / modèle métier par code côté front avec des objets sélectionnés par l'utilisateur

Bonjour,

Est-il possible d’avoir un exemple de code qui ajoute automatiquement des objets liés à d’autre objets par un relation 1-N ou N-N sur un modeler en fonction d’une liste d’objets sélectionnés.
Sans prendre en compte le placement de ces objets.

Je vous remercie

Jamais fait, mais ça doit pouvoir se faire.

  • Au plus bas niveau un modèle est un SVG donc XML manipulable avec tous les utilitaires XML en back si on connait la sémantique du modèle (à mon avis peu maintenable).
  • Au plus haut niveau front, on doit pouvoir utiliser les APIs du contrôleur du modeleur Simplicite.Diagram.Modeler mais ils n’ont pas forcement été conçus pour être un CLI javascript = public ou silencieux, on a besoin d’un context graphique pour dessiner/mesurer, il se peut qu’il y ait des popups de confirmation ou de choix d’options, etc.

Pour créer un modèle à la volée par code en front, il faut essayer de faire comme suit :

  • créer un modèle comme n’importe quel objet métier via la couche Ajax, objet Model méthode create
  • utiliser le controlleur Simplicite.Diagram.Modeler
    • open = instancier / ouvrir le modèle (dans un div caché ?)
    • insertNode = ajouter des éléments
    • fetchRelatedNodes = insérer les objets connexes (links) à un élément
    • save / close

C’est l’occasion de le faire évoluer car ce besoin revient souvent (Renault, Amadeus, …)

1 Like

Après quelques ajustements pour rendre le modeleur invisible en V4, voici comment créer un modèle métier et y insérer des objets via JavaScript sans interaction utilisateur :

L’exemple utilise le template DemoModel de la démo :

  • Créer une action de liste sur DemoProduct de type Front avec le code suivant :
    javascript:demoNewModel(obj);

  • Coder dans le SCRIPT front de l’objet :

function demoNewModel(obj) {
	var app = $ui.getAjax(),
		diagram, modelId,
		mt = app.getBusinessObject("ModelTemplate", "tmp_ajax_ModelTemplate"),
		mod = app.getBusinessObject("Model", "tmp_ajax_Model"),
		ids = obj.selectedIds;

	// Needs objects to insert
	if (!ids || !ids.length) {
		$ui.alert("no selection");
		return;
	}
	
	// Search the model template
	function getTemplate() {
		mt.search(function(list) {
			if (list && list.length>0)
				createModel(list[0]);
			else
				$ui.alert("no template");
		},{
			mtp_name: "DemoModel"
		});
	}
	// Create a new empty model
	function createModel(tpl) {
		mod.create(load, {
			row_id: "0",
			mod_name: 'Product-' + (new Date()).getTime(),
			mod_template_id: tpl.row_id
		});
	}
	function load() {
		modelId = mod.item.row_id;
		// Load SVG engine
		$ui.loadDiagramEngine(true, function() {
			// Load empty SVG file in object
			mod.get(function() {
				// instanciate in silent mode
				$ui.diagram.open(modelId, { svg:true, hidden:true }, build);
			}, modelId, {
				inlineDocs: true
			});
		});
	}
	// Insert objects in diagram
	function build(d) {
		diagram = d;
		var list = [];
		$(ids).each(function(i,id) {
			list.push({
				object: "DemoProduct", // node object
				id: id, // node row_id
				template: "DemoModel_Product", // node template name
				x: i*30 + 30, // dummy position
				y: i*70 + 30,
				container: null // no container
			});
		});
		// async loading with up-to-date data
		diagram.insertNodes(list, function(nodes) {
			// auto load sibling nodes thru links
			diagram.fetchRelatedNodes(nodes, save);
		});
	}
	function save() {
		diagram.save(function() {
			// Close hidden model
			diagram.close(false);
			// Re-open in a window
			$ui.displayModeler(null, modelId, { svg:true, docked:false });
		}, true); // silent
	}
	getTemplate();
}
  • Ensuite il suffit de sélectionner des produits et de cliquer sur l’action pour :
    • construire un nouveau model
      • lui insérer les objets séléctionnés
      • puis ajouter les objets relatifs (les commandes + clients) liés aux produits insérés
      • Enregistrer et fermer le model
    • réouvrir le model créé dans une fenêtre.

Attention tous les traitements sont asynchrones, donc il est important de bien chainer les callbacks de chaque méthode.

Un helper $ui.diagram.create a été ajouté pour simplifier la création en front, dans l’exemple cela donne :

function demoNewModel(obj) {
	var ids = obj.selectedIds, list = [];
	// Needs objects to insert
	if (!ids || !ids.length) {
		$ui.alert("no selection");
		return;
	}
	$(ids).each(function(i,id) {
		list.push({
			object: "DemoProduct", // node object
			id: id, // node row_id
			template: "DemoModel_Product", // node template name
			x: i*50 + 30, // dummy position
			y: i*30 + 30,
			container: null // no container
		});
	});
	
	// Load SVG engine
	$ui.loadDiagramEngine(true, function() {
		try {
			// Create the model in silent mode
			var name = "Product-" + (new Date()).getTime();
			$ui.diagram.create("DemoModel", name, {
				hidden: true, // hide the modeler
				nodes: list,  // nodes to insert
				fetch: true   // then fetch related orders and customers
			},
			function(diagram) {
				// close the hidden model
				diagram.close(false);
				// Re-open in a window
				$ui.displayModeler(null, diagram.modelId, {
					svg: true,
					docked: false
				});
			});
		}
		catch(e) {
			console.log(e);
		}
	});
}

Cool !
@bmo je pense que ça peut t’intéresser aussi

1 Like

Merci David (et François), j’avais déjà épinglé le post de la semaine dernière =)
J’ai un ticket sur la R5 de notre module GDPR qui porte sur la génération automatique du diagramme de flux des données et acteurs documentés sur les traitements de données personnelles… quelques devoirs de vacances en perspective.

Pour avoir déjà jardiné sur les platebandes du dummy position (coordonnées, calcul d’intersections, smart layout), je sais déjà que c’est là que sera l’os ! … J’ai hâte :wink:

@bmo oui chaque chose en son temps…

En V5, il reste pas mal de boulot sur le positionnement 2D des modèles SVG.

  • il faut ré-intégrer les layouts classiques en arbre ou en étoile qui avaient été faits en canvas
  • je travaille actuellement sur un positionnement 2D automatique, un algo NP-complet en javascripts de “remplissage de sac-à-dos”, c’est un enfer… par contre un algo à base de physique des “ressorts” me semble plus efficace pour dessiner des modèles d’objets (entités/relations)

Ce projet est assez inspirant : http://getspringy.com/
je vais essayer de l’intégrer rapidement car ça me chatouille aussi depuis longtemps.

@bmo
Voilà une première implémentation d’un algorithme d’auto-placement à base de ressorts et de répulsion/gravité entre les objets.

On peut s’en servir pour placer les objets, puis désactiver le layout pour reprendre la main sur le placement.

En cas de création de modèle par code, il faut simplement lancer le processus :

diagram.layoutSprings({
  stiffness: 100,
  repulsion: 500,
  damping: 0.5,
  remoteness: 50,
  gravity: 100,
  maxDuration: 5000, // 5 sec
  callback: function() { ... save ... }
});

Exemple avec les produits/commandes/clients, en affichant le modèle qui se construit par code :

Si on retire tous les frottements, le modèle se met à danser ;-)

1 Like

Bonjour,

J’ai implémenté le code de démo sur l’environnement RCI et cela génère bien le modèle avec les objets liés, mais le positionnement ne semble pas fonctionner.

Le /health

Version=4.0.P24c
BuiltOn=2020-09-14 14:50 (revision d644e41c86027b6b224b6aa992a321d927f008eb)

J’ai l’erreur suivante dans la console

resource?row_id=288&d=responsive_ThemeAdmin&_=d644e41c86027b6b224b6aa992a321d927f008eb_20200916145204:2 Uncaught TypeError: diagram.layoutSprings is not a function
    at resource?row_id=288&d=responsive_ThemeAdmin&_=d644e41c86027b6b224b6aa992a321d927f008eb_20200916145204:2
    at done (diagram-bundle.js?_=d644e41c86027b6b224b6aa992a321d927f008eb_20200916145204:31)
    at Simplicite.Ajax.BusinessObject.error (diagram-bundle.js?_=d644e41c86027b6b224b6aa992a321d927f008eb_20200916145204:55)
    at Simplicite.Ajax.BusinessObject.<anonymous> (ui-bundle.js?_=d644e41c86027b6b224b6aa992a321d927f008eb:264)
    at Simplicite.Ajax._callResponse (ui-bundle.js?_=d644e41c86027b6b224b6aa992a321d927f008eb:59)
    at XMLHttpRequest.xhr.onreadystatechange (ui-bundle.js?_=d644e41c86027b6b224b6aa992a321d927f008eb:53)

Et voici mon code

function demoNewModel(obj) {
	var ids = obj.selectedIds, list = [];
	// Needs objects to insert
	if (!ids || !ids.length) {
		$ui.alert("no selection");
		return;
	}
	$(ids).each(function(i,id) {
		console.log("id" + id);
		list.push({
			object: "DemoProduct", // node object
			id: id, // node row_id
			template: "DemoModel_Product", // node template name
			x: i*50 + 30, // dummy position
			y: i*30 + 30,
			container: null // no container
		});
	});
// Load SVG engine
$ui.loadDiagramEngine(function() {
	try {
			console.log("loadDiagramEngine");
		// Create the model in silent mode
		var name = "Product-" + (new Date()).getTime();
			console.log(name);
		$ui.diagram.create("DemoModel", name, {
			hidden: true, // hide the modeler
			nodes: list,  // nodes to insert
			fetch: true   // then fetch related orders and customers
		},
		function(diagram) {
			// close the hidden model
			diagram.close(false);
			// Re-open in a window
			$ui.displayModeler(null, diagram.modelId, {
				svg: true,
				docked: false
			});
			
			diagram.layoutSprings({
			  stiffness: 100,
			  repulsion: 500,
			  damping: 0.5,
			  remoteness: 50,
			  gravity: 100,
			  maxDuration: 5000, // 5 sec
			  callback: function() { diagram.save(); }
			});
		});
	}
	catch(e) {
			console.log("Erreur");
		console.log(e);
	}
});

}

Merci d’avance !

A mon avis l’objet diagram n’est plus valide puisque le modeler est fermé puis réouvert dans votre code. Il faut faire le placement avant de le fermer et le réouvrir, et en pensant que les méthodes sont asynchrones donc à chainer :

$ui.diagram.create("DemoModel", name, {
	hidden: true, // hide the modeler
	nodes: list,  // nodes to insert
	fetch: true   // then fetch related orders and customers
},
function(diagram) {
	// auto-placement
	diagram.layoutSprings && diagram.layoutSprings({
		stiffness: 100,
		repulsion: 500,
		damping: 0.5,
		remoteness: 50,
		gravity: 100,
		maxDuration: 5000, // 5sec
		callback: function() {
			// Save and close
			diagram.canClose(false, function() { // confirm=false means auto-save
				diagram.close();
				// Re-open in a window
				$ui.displayModeler(null, diagram.modelId, {
					svg: true,
					docked: false
				});
			});
		}
	});
});

Il faudra tunner les paramètrres pour avoir un résultat correct.

Sinon pour simplifier, il suffit de mettre hidden: false dès le début pour voir le diagramme se créer, et sans avoir à l’enregistrer/réouvrir à la fin.
.

C’est implémenté en V5.

Bonjour,

Est-il possible en génération par code via le helper, d’afficher plusieurs objets liés à la suite ? Dans mon cas les applications sont liées entre elles par des flux. Je voudrais afficher un carré avec Application A → carré avec nom du flux → carré avec Application B mais le fetch semble s’arrêter au premier objet lié, le flux.
Sinon sans le helper ?

VOici comment sont configurés mes template links, j’ai entouré celui qui n’est pas fetché

Version=4.0.P24c
BuiltOn=2020-09-23 22:10 (revision 9796d4cfb7577dd55890649f38dc1ce0dda5880a)

Merci d’avance pour votre aide !

Le fetchRelatedNodes est à un seul niveau, il n’est pas récursif. On pourrait prévoir d’ajouter une profondeur de recherche, ou de préciser quels Links parcourir…

Il faut donc que votre code fasse les recherches nécessaires à votre modèle (ici les flux de A)
et invoque les fetchRelatedNodes sur chaque flux par exemple pour ramener les applications B.

Merci beaucoup pour cet éclairage.
Je vais essayer de le faire par code, mais je crains que ça ne fetche pas le link que j’ai configuré dans le template.
Voici mon modèle généré via le helper (désolée pour la mauvaise qualité des indications)

Et si à partir du canvas, je clique sur le Flux et choisis “fetch related”, ce qui apparaît est le ModelObjectContent configuré sur le ModelObjectTemplate Flux, et non l’Application configurée dans le ModelTemplateLink du node Flux.

Le seul moyen de faire apparaître l’application semble être d’aller la chercher à la main avec le Insert object.

Est-ce que j’ai mal configuré quelque chose ?
Merci !

J’ai l’impression que c’est le lien FluxData qui est ramené ici.
Vous avez également dû le définir en contenu du node car on le voit dans les contenu de CR1.

Dans vos 2 liens flux/app, il faut que Target field soit row_id. On part du Flux avec 2 sources/FK vers les row_id d’Application. Votre paramétrage de FluxAppEm me semble dans le mauvais sens, et le fetch ne le regarde pas.

En fait le node de départ est l’application (l’action pour générer le modèle est sur l’application)
De l’application le helper fetche les flux avec leur content ce qui est parfait
Mais après je dois trouver un moyen pour que mon code fetche les applications destinataires du flux (link FluAppRec)
Peut-être en récupérant dans mon code à la main les flux liés à mon application (je n’en ai qu’une) et en les pushant dans la liste que je passe au helper. Je vais essayer
Merci !

Oui, faites un obj.search des Flux filtrés sur la source (et/ou la destination) qui vous donnera pour chacun les row_id (flux + application A/B) à insérer dans la liste des noeuds du modèle.

Sinon je continue de penser qu’un de vos Link est paramétré à l’envers et ne ramène rien, avez vous des erreur SQL dans les logs ?

Oui si je pars du Flux vers les Applications il faut que je change le sens.
En revanche je n’arrive plus à faire fonctionner les liens sans “link object”, y a-t-il eu une mise à jour récente ?

Le fetch related ne me ramène pas les applications alors qu’il y en a dans mon jeu de données

Version=4.0.P24c
BuiltOn=2020-09-24 17:47 (revision 831ff58c51f96cdc2244eca901e6286c2d156ce0)