Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 16 additions & 12 deletions frontend/src/spanner-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -638,31 +638,35 @@ class GraphStore {
}

/**
* Gets the type of a specific property for a node.
* @param {GraphNode} node - The node to get the property type from
* Gets the type of a specific property for a node or edge.
* @param {GraphNode|GraphEdge} graphObject - The object to get the property type from
* @param {string} propertyName - The name of the property to get the type for
* @returns {PropertyDeclarationType|null} The type of the property, or null if not found
*/
getPropertyType(node, propertyName) {
if (!this.config.schema || !this.config.schema.rawSchema || !node) {
getPropertyType(graphObject, propertyName) {
if (!this.config.schema || !this.config.schema.rawSchema) {
return null;
}

if (!(graphObject instanceof Node) && !(graphObject instanceof Edge)) {
return;
}

const schema = this.config.schema.rawSchema;

// Find matching node tables for this node's labels
const matchingNodeTables = schema.nodeTables.filter(nodeTable =>
node.labels.some(label => nodeTable.labelNames.includes(label))
// Find matching node/edge tables for the labels
const matchingTables = schema[graphObject instanceof Node ? 'nodeTables' : 'edgeTables'].filter(table =>
graphObject.labels.some(label => table.labelNames.includes(label))
);

if (matchingNodeTables.length === 0) {
console.error(`No matching node table found for labels: ${node.labels.join(', ')}`);
if (matchingTables.length === 0) {
console.error(`No matching table found for labels: ${graphObject.labels.join(', ')}`);
return null;
}

// Look through all matching node tables for the property
for (const nodeTable of matchingNodeTables) {
const propertyDef = nodeTable.propertyDefinitions.find(
for (const table of matchingTables) {
const propertyDef = table.propertyDefinitions.find(
prop => prop.propertyDeclarationName === propertyName
);

Expand All @@ -678,7 +682,7 @@ class GraphStore {
}
}

console.error(`Property ${propertyName} not found in any matching node tables for labels: ${node.labels.join(', ')}`);
console.error(`Property ${propertyName} not found in any matching tables for labels: ${graphObject.labels.join(', ')}`);
return null;
}
}
Expand Down
10 changes: 8 additions & 2 deletions frontend/src/visualization/spanner-sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -831,14 +831,20 @@ class SidebarConstructor {
}

const createPropertyRow = (key, value) => {
const property = document.createElement('div');
/** @type PropertyDeclarationType */
const arrayPropertyType = 'ARRAY';
if (this.store.getPropertyType(selectedObject, key) === arrayPropertyType) {
value = `[${value}]`;
}

const property = document.createElement('div');
property.className = 'property';
property.innerHTML =
`<div class="property-label ${labelWrapClass}">${key}</div>
<div class="property-value ${valueWrapClass}">${value}</div>`;

return property;
}
};

const properties = Object
.entries(selectedObject.properties)
Expand Down
49 changes: 47 additions & 2 deletions frontend/tests/unit/spanner-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -799,8 +799,8 @@ describe('GraphStore', () => {
});

it('should handle null/undefined inputs', () => {
expect(store.getPropertyType(null, 'name')).toBeNull();
expect(store.getPropertyType(undefined, 'name')).toBeNull();
expect(store.getPropertyType(null, 'name')).toBeUndefined();
expect(store.getPropertyType(undefined, 'name')).toBeUndefined();
expect(store.getPropertyType(userNode, null)).toBeNull();
expect(store.getPropertyType(userNode, undefined)).toBeNull();
});
Expand Down Expand Up @@ -830,6 +830,51 @@ describe('GraphStore', () => {

expect(storeWithoutDeclarations.getPropertyType(userNode, 'age')).toBeNull();
});

it('should return correct property type for an edge', () => {
// Create a test edge with the FOLLOWS label
const testEdge = new Edge({
identifier: 'edge1',
source_node_identifier: 'user1',
destination_node_identifier: 'user2',
labels: ['FOLLOWS']
});

// Set source and destination nodes (required for proper edge handling)
testEdge.sourceNode = userNode;
testEdge.destinationNode = userNode;

// Test getting property type for the edge
const activeType = store.getPropertyType(testEdge, 'active');

// The 'active' property should be of type 'BOOL' as defined in the mock data
expect(activeType).toBe('BOOL');

// Test with a non-existent property
const nonExistentType = store.getPropertyType(testEdge, 'nonexistent');
expect(nonExistentType).toBeNull();

// Test with an edge that has a non-existent label
const invalidEdge = new Edge({
identifier: 'invalid-edge',
source_node_identifier: 'user1',
destination_node_identifier: 'user2',
labels: ['NON_EXISTENT_LABEL']
});

invalidEdge.sourceNode = userNode;
invalidEdge.destinationNode = userNode;

const invalidEdgePropertyType = store.getPropertyType(invalidEdge, 'active');
expect(invalidEdgePropertyType).toBeNull();
});

it('should handle invalid graph objects', () => {
// Test with an object that is neither a Node nor an Edge
const invalidObject = { labels: ['User'] };
const propertyType = store.getPropertyType(invalidObject as any, 'name');
expect(propertyType).toBeUndefined();
});
});

describe('getNodeCount', () => {
Expand Down
Loading