From 6936f320a5ea4904a93c6d18ff23a01fbfb5fc5a Mon Sep 17 00:00:00 2001 From: Tristan Huet Date: Fri, 31 Jan 2025 15:50:00 +0100 Subject: [PATCH] feat: [PROD-14225] make attributes order configurable in CytoViz component This commit adds an 'elementsMetadata' prop, that can be used to define the desired order of attributes when selecting a node or edge in the visualization tool. The expected format for this prop is: { attributesOrder: { nodes: { nodeType1: ['someImportantAttribute', 'attribute1', 'attribute2'] }, edges: { edgeType1: ['attributeA', 'attributeB']}}, } } If some attributes are missing in the list, they will be displayed after all the other attributes. --- src/charts/CytoViz/CytoViz.js | 26 ++++++++++++++++++- .../components/ElementData/ElementData.js | 25 ++++++++++++++---- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/charts/CytoViz/CytoViz.js b/src/charts/CytoViz/CytoViz.js index 10f0a7cd..22cf5b93 100644 --- a/src/charts/CytoViz/CytoViz.js +++ b/src/charts/CytoViz/CytoViz.js @@ -54,6 +54,7 @@ export const CytoViz = (props) => { cytoscapeStylesheet, defaultSettings, elements, + elementsMetadata, error, extraLayouts, getElementDetails, @@ -81,7 +82,17 @@ export const CytoViz = (props) => { let getElementDetailsCallback = getElementDetails; if (!getElementDetailsCallback) { // eslint-disable-next-line react/display-name - getElementDetailsCallback = (element) => ; + getElementDetailsCallback = (element) => { + const elementType = element.classes && element.classes()?.[0]; + return ( + + ); + }; getElementDetailsCallback.displayName = 'ElementData'; } @@ -714,6 +725,18 @@ CytoViz.propTypes = { * Array of cytoscape elements to display */ elements: PropTypes.array.isRequired, + /** + * Optional array of metadata for cytoscape elements. Currently, it only takes an attributesOrder property to force + * the order of attributes when entity details are displayed. + * Expected format example: + * { + * attributesOrder: { + * nodes: { nodeType1: ['someImportantAttribute', 'attribute1', 'attribute2', 'attribute3'] }, + * edges: { edgeType1: ['attributeA', 'attributeB']}}, + * } + * } + */ + elementsMetadata: PropTypes.object, /** * Object of extra layouts to register in cytoscape. The keys of this object must be the layout names, and the values must be the extension object to provide to cytoscape.use(...). If you want to add a default cytoscape layout @@ -829,6 +852,7 @@ CytoViz.defaultProps = { showStats: false, spacingFactor: 1, }, + elementsMetadata: {}, extraLayouts: {}, groups: {}, labels: DEFAULT_LABELS, diff --git a/src/charts/CytoViz/components/ElementData/ElementData.js b/src/charts/CytoViz/components/ElementData/ElementData.js index 8117e2fe..87c4c020 100644 --- a/src/charts/CytoViz/components/ElementData/ElementData.js +++ b/src/charts/CytoViz/components/ElementData/ElementData.js @@ -69,14 +69,26 @@ const _generateAttributeDetails = (classes, labels, attributeName, attributeValu } }; +const getSortedAttributeNames = (expectedKeys, allKeys) => { + // Start with expected keys in desired order + const sortedKeys = expectedKeys.filter((key) => allKeys.includes(key)); + allKeys.filter((key) => !expectedKeys.includes(key)).forEach((key) => sortedKeys.push(key)); // Add unknown keys + return sortedKeys; +}; + const ElementData = (props) => { const classes = useStyles(); - const { data, labels } = props; - if (!data) { - return labels.noData; - } + const { data, labels, metadata, type } = props; + if (!data) return labels.noData; + + const attributesOrderConfig = metadata?.attributesOrder; + const desiredAttributesOrder = type && (attributesOrderConfig?.nodes?.[type] ?? attributesOrderConfig?.edges?.[type]); + + let sortedElementAttributeNames = Object.keys(data); + if (desiredAttributesOrder != null) + sortedElementAttributeNames = getSortedAttributeNames(desiredAttributesOrder, Object.keys(data)); - let filteredElementAttributes = Object.keys(data) + let filteredElementAttributes = sortedElementAttributeNames .map((key) => _generateAttributeDetails(classes, labels, key, data[key])) .filter((el) => el !== null); if (filteredElementAttributes.length === 0) { @@ -89,10 +101,13 @@ const ElementData = (props) => { ElementData.propTypes = { data: PropTypes.object, labels: PropTypes.object, + metadata: PropTypes.object, + type: PropTypes.string, }; ElementData.defaultProps = { data: PropTypes.object, + metadata: {}, labels: { attributes: {}, dictKey: 'Key',