Compare commits

...

2 Commits

2 changed files with 216 additions and 150 deletions

View File

@ -534,8 +534,7 @@ const AddTableModal = ({
// Call the parent component's add table function
await onAddTable(tableData);
// Close modal and reset form
onClose();
// Reset form (modal will be closed by parent component)
resetForm();
} catch (error) {
console.error('Error adding table:', error);

View File

@ -551,6 +551,7 @@ const ERDiagramCanvasContent = () => {
SCHEMA_LIST: `${API_BASE_URL}/qbt_schema_list_get`,
TABLE_LIST: `${API_BASE_URL}/qbt_table_list_get`,
COLUMN_LIST: `${API_BASE_URL}/qbt_column_list_get`,
DATASOURCE_KEY_LIST: `${API_BASE_URL}/qbt_datasource_key_list_get`,
SCHEMA_CREATE: `${API_BASE_URL}/qbt_schema_create`,
SCHEMA_DELETE: `${API_BASE_URL}/qbt_schema_delete`,
SCHEMA_UPDATE: `${API_BASE_URL}/qbt_schema_update`,
@ -773,6 +774,61 @@ const ERDiagramCanvasContent = () => {
}
};
// Function to fetch relationships/foreign keys from the API
const fetchRelationships = async (dbSlug) => {
try {
console.log(`Fetching relationships for database: ${dbSlug}`);
const response = await axios.post(
ENDPOINTS.DATASOURCE_KEY_LIST,
{
token: token,
org: orgSlug,
con: dbSlug
},
{
headers: {
'Content-Type': 'application/json'
}
}
);
console.log(`Relationships response for database ${dbSlug}:`, response.data);
let relationships = [];
if (response.data.status === 200 && Array.isArray(response.data.items)) {
relationships = response.data.items.map((item, index) => {
// Extract source and destination information
const source = item.source?.[0];
const destination = item.destination?.[0];
if (source && destination) {
return {
id: `relationship-${index}`,
sourceTable: source.table_slug,
sourceSchema: source.schema_slug,
sourceColumn: source.column_slug,
sourceKeyName: source.key_name,
targetTable: destination.table_slug,
targetSchema: destination.schema_slug,
targetColumn: destination.column_slug,
targetKeyName: destination.key_name,
relationship_type: '1:N' // Default relationship type, can be enhanced later
};
}
return null;
}).filter(Boolean); // Remove null entries
}
console.log(`Number of relationships found for database ${dbSlug}: ${relationships.length}`);
console.log('Parsed relationships:', relationships);
return relationships;
} catch (error) {
console.error(`Error fetching relationships for database ${dbSlug}:`, error);
// Return empty array on error to prevent breaking the diagram
return [];
}
};
// Function to fetch complete database structure with schemas, tables, and columns
const fetchCompleteDatabase = async (dbSlug) => {
try {
@ -828,48 +884,8 @@ const ERDiagramCanvasContent = () => {
}
};
// Generate dummy ER relationship data based on single database structure
const generateDummyERData = (database) => {
const erData = [];
if (database.schemas && database.schemas.length > 0) {
database.schemas.forEach(schema => {
if (schema.tables && schema.tables.length > 0) {
// Create relationships between tables in the same schema
for (let i = 0; i < schema.tables.length - 1; i++) {
const sourceTable = schema.tables[i];
const targetTable = schema.tables[i + 1];
// Create a dummy relationship with varied types
const relationshipTypes = ['1:N', '1:1', 'N:M'];
const randomType = relationshipTypes[Math.floor(Math.random() * relationshipTypes.length)];
erData.push({
source_column_set: [
{
table_id: sourceTable.id,
column_name: sourceTable.columns?.[0]?.name || 'id',
table_name: sourceTable.name,
schema_name: schema.sch
}
],
destination_column_set: [
{
table_id: targetTable.id,
column_name: `${sourceTable.name}_id`,
table_name: targetTable.name,
schema_name: schema.sch
}
],
relationship_type: randomType
});
}
}
});
}
return erData;
};
// Fetch databases and generate ER diagram using real API
useEffect(() => {
@ -898,6 +914,9 @@ const ERDiagramCanvasContent = () => {
// Fetch complete structure (schemas, tables, columns) for the selected database
const schemasWithTables = await fetchCompleteDatabase(targetDatabase.con);
// Fetch relationships for the selected database
const relationships = await fetchRelationships(targetDatabase.con);
// Create the complete database object using actual API data
const completeDatabase = {
id: targetDatabase.id,
@ -906,7 +925,8 @@ const ERDiagramCanvasContent = () => {
description: targetDatabase.description || `Database: ${targetDatabase.name}`,
service: selectedService?.name,
dataSource: selectedDataSource?.name,
schemas: schemasWithTables
schemas: schemasWithTables,
relationships: relationships
};
console.log('Complete database structure:', completeDatabase);
@ -1002,6 +1022,14 @@ const ERDiagramCanvasContent = () => {
}
}, [nodes, isLoading, fitView]);
// Regenerate diagram when selectedDatabase changes (including after adding new table)
useEffect(() => {
if (selectedDatabase && selectedDatabase.schemas) {
console.log('Selected database changed, regenerating diagram...');
generateERDiagram(selectedDatabase);
}
}, [selectedDatabase]);
// Auto-alignment layout calculator
const calculateOptimalLayout = (databaseData) => {
const layouts = [];
@ -1075,7 +1103,7 @@ const ERDiagramCanvasContent = () => {
key_type: key.key_type,
key_columns: [{
column_name: key.column_name,
sequence: key.sequence
sequence: key.sequence || 1
}]
}))
};
@ -1105,10 +1133,9 @@ const ERDiagramCanvasContent = () => {
created_at: new Date().toISOString()
};
// Add the table to existing tables list
setExistingTables(prev => [...prev, newTable]);
console.log('Table created successfully via API:', newTable);
// Update the current database structure and regenerate the diagram
// Update the current database structure immediately
const updatedDatabase = { ...selectedDatabase };
const schemaIndex = updatedDatabase.schemas.findIndex(s => s.sch === tableData.schema);
@ -1118,14 +1145,51 @@ const ERDiagramCanvasContent = () => {
}
updatedDatabase.schemas[schemaIndex].tables.push(newTable);
console.log(`Added table "${newTable.name}" to schema "${tableData.schema}"`);
console.log('Updated database structure:', updatedDatabase);
// Update the selected database state
setSelectedDatabase(updatedDatabase);
// Regenerate the ER diagram with the new table
generateERDiagram(updatedDatabase);
console.log('Table added successfully:', newTable);
// Update databases list
setDatabases(prevDatabases => {
return prevDatabases.map(db => {
if (db.id === selectedDatabase?.id) {
return updatedDatabase;
}
return db;
});
});
// Regenerate the ER diagram with the new table immediately
console.log('Scheduling diagram regeneration...');
setTimeout(() => {
console.log('Regenerating diagram with updated database...');
generateERDiagram(updatedDatabase);
}, 100);
}
// Update existing tables list for the modal
setExistingTables(prev => [...prev, newTable]);
// Close the modal immediately after successful creation
setIsAddTableModalOpen(false);
console.log('Table added successfully and modal closed');
// Force a small delay to ensure all state updates are processed and then fit view
setTimeout(() => {
if (fitView) {
fitView({
padding: 0.1,
includeHiddenNodes: false,
minZoom: 0.15,
maxZoom: 0.8,
duration: 800
});
}
}, 500);
} else {
throw new Error(response.data.message || 'Failed to create table');
}
@ -1133,6 +1197,9 @@ const ERDiagramCanvasContent = () => {
} catch (error) {
console.error('Error adding table:', error);
// Always close the modal on error to prevent it from being stuck
setIsAddTableModalOpen(false);
// Check if it's an API error
if (error.response) {
const errorMessage = error.response.data?.message || `API Error: ${error.response.status}`;
@ -1147,16 +1214,22 @@ const ERDiagramCanvasContent = () => {
// Generate ER diagram with Database Wrapper structure
const generateERDiagram = (database) => {
const newNodes = [];
const newEdges = [];
console.log('🔄 Starting ER diagram generation...');
console.log('Database received:', database?.name);
console.log('Total schemas:', database?.schemas?.length);
// Generate dummy relationships
const relationships = generateDummyERData(database);
const newNodes = [];
const newEdges = []; // No edges - removing all connections
// Process the selected database
console.log('Generating ER diagram for database:', database?.name);
console.log('Total schemas in database:', database?.schemas?.length);
console.log('Schema details:', database?.schemas?.map(s => ({ name: s.name, sch: s.sch, tableCount: s.tables?.length || 0 })));
console.log('Schema details:', database?.schemas?.map(s => ({
name: s.name,
sch: s.sch,
tableCount: s.tables?.length || 0,
tableNames: s.tables?.map(t => t.name) || []
})));
if (database && database.schemas && database.schemas.length > 0) {
// Calculate database wrapper dimensions
@ -1316,16 +1389,21 @@ const ERDiagramCanvasContent = () => {
console.warn(`Table "${table.name}" position (${tableX}, ${tableY}) exceeds schema bounds (${schemaLayout.width}x${schemaLayout.height})`);
}
// Add primary key and foreign key indicators to columns
const enhancedColumns = (table.columns || []).map((col, colIndex) => ({
// Add primary key and foreign key indicators to columns based on relationships
const enhancedColumns = (table.columns || []).map((col, colIndex) => {
// Check if this column is a foreign key based on relationships
const isForeignKey = database.relationships?.some(rel =>
rel.targetTable === table.tbl &&
rel.targetSchema === schemaLayout.schema.sch &&
rel.targetColumn === col.col
) || false;
return {
...col,
is_primary_key: colIndex === 0, // First column as primary key
is_foreign_key: relationships.some(rel =>
rel.destination_column_set.some(dest =>
dest.column_name === col.name && dest.table_name === table.name
)
)
}));
is_primary_key: col.is_primary_key || colIndex === 0, // Use existing or first column as primary key
is_foreign_key: isForeignKey
};
});
newNodes.push({
id: tableId,
@ -1358,38 +1436,80 @@ const ERDiagramCanvasContent = () => {
});
}
// Create edges for relationships
relationships.forEach((rel, index) => {
const sourceTableId = `table-${rel.source_column_set[0]?.table_id || `${rel.source_column_set[0]?.schema_name}-${rel.source_column_set[0]?.table_name}`}`;
const targetTableId = `table-${rel.destination_column_set[0]?.table_id || `${rel.destination_column_set[0]?.schema_name}-${rel.destination_column_set[0]?.table_name}`}`;
// Create relationship edges based on API data
if (database.relationships && database.relationships.length > 0) {
console.log('Creating relationship edges:', database.relationships.length);
// Check if both nodes exist
const sourceExists = newNodes.some(node => node.id === sourceTableId);
const targetExists = newNodes.some(node => node.id === targetTableId);
database.relationships.forEach((relationship, index) => {
// Find source and target table nodes
const sourceTableId = `table-${database.slug}-${relationship.sourceSchema}-${relationship.sourceTable}`;
const targetTableId = `table-${database.slug}-${relationship.targetSchema}-${relationship.targetTable}`;
// Check if both source and target tables exist in the nodes
const sourceNode = newNodes.find(node =>
node.type === 'erTable' &&
(node.id === sourceTableId ||
node.id.includes(relationship.sourceTable) &&
node.data?.schema === relationship.sourceSchema)
);
const targetNode = newNodes.find(node =>
node.type === 'erTable' &&
(node.id === targetTableId ||
node.id.includes(relationship.targetTable) &&
node.data?.schema === relationship.targetSchema)
);
if (sourceNode && targetNode) {
const edgeId = `edge-${relationship.id || index}`;
console.log(`Creating edge: ${sourceNode.id} -> ${targetNode.id}`);
console.log(`Relationship: ${relationship.sourceTable}.${relationship.sourceColumn} -> ${relationship.targetTable}.${relationship.targetColumn}`);
if (sourceExists && targetExists) {
newEdges.push({
id: `relationship-${index}`,
id: edgeId,
source: sourceNode.id,
target: targetNode.id,
type: 'erRelationship',
source: sourceTableId,
target: targetTableId,
data: {
relationship_type: rel.relationship_type,
source_column: rel.source_column_set[0]?.column_name,
target_column: rel.destination_column_set[0]?.column_name
relationship_type: relationship.relationship_type || '1:N',
source_column: relationship.sourceColumn,
target_column: relationship.targetColumn,
source_key_name: relationship.sourceKeyName,
target_key_name: relationship.targetKeyName
},
style: {
stroke: '#8a2be2',
strokeWidth: 2
strokeWidth: 2,
},
animated: false
markerEnd: {
type: MarkerType.ArrowClosed,
color: '#8a2be2',
},
animated: false,
selectable: true
});
} else {
console.warn(`Could not find nodes for relationship: ${relationship.sourceTable} -> ${relationship.targetTable}`);
console.warn(`Source node found: ${!!sourceNode}, Target node found: ${!!targetNode}`);
console.warn(`Looking for source ID: ${sourceTableId}, target ID: ${targetTableId}`);
}
});
console.log(`Created ${newEdges.length} relationship edges out of ${database.relationships.length} relationships`);
} else {
console.log('No relationships found in database data');
}
console.log('📊 Setting nodes and edges...');
console.log('New nodes count:', newNodes.length);
console.log('New edges count:', newEdges.length);
setNodes(newNodes);
setEdges(newEdges);
console.log('✅ ER diagram generation completed');
// Update available schemas and existing tables for the modal
if (database && database.schemas) {
setAvailableSchemas(database.schemas);
@ -1424,23 +1544,7 @@ const ERDiagramCanvasContent = () => {
}, 300);
};
const onConnect = useCallback(
(params) => setEdges((eds) => addEdge({
...params,
type: 'erRelationship',
data: {
relationship_type: '1:N',
source_column: 'id',
target_column: 'foreign_key_id'
},
style: {
stroke: '#8a2be2',
strokeWidth: 2
},
animated: true
}, eds)),
[setEdges]
);
// onConnect removed - no connections allowed between tables
const handleAddClick = () => {
setShowAddMenu(!showAddMenu);
@ -1513,14 +1617,8 @@ const ERDiagramCanvasContent = () => {
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
connectionLineStyle={{
stroke: '#8a2be2',
strokeWidth: 2,
}}
connectionLineType="bezier"
fitView
fitViewOptions={{
padding: 0.15,
@ -1611,38 +1709,7 @@ const ERDiagramCanvasContent = () => {
</div>
</Panel>
{/* Relationship Legend Panel */}
<Panel position="bottom-left">
<div style={{
background: 'rgba(26, 26, 26, 0.9)',
padding: '12px 16px',
borderRadius: '8px',
border: '1px solid rgba(138, 43, 226, 0.3)',
color: '#fff',
fontSize: '12px',
backdropFilter: 'blur(5px)',
minWidth: '200px'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' }}>
<FaLink style={{ color: '#8a2be2' }} />
<strong>Relationship Types</strong>
</div>
<div style={{ fontSize: '11px', lineHeight: '1.4' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '4px' }}>
<span style={{ color: '#8a2be2' }}></span>
<span><strong>1:N</strong> - One to Many</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '4px' }}>
<span style={{ color: '#8a2be2' }}></span>
<span><strong>1:1</strong> - One to One</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '4px' }}>
<span style={{ color: '#8a2be2' }}></span>
<span><strong>N:M</strong> - Many to Many</span>
</div>
</div>
</div>
</Panel>
</ReactFlow>
</div>