Compare commits
No commits in common. "ed0cc6ee15382f8942ce5411b415528e8514e019" and "28b8afcf83aeb3a7f66cab2ca8429da7dc102ef5" have entirely different histories.
ed0cc6ee15
...
28b8afcf83
|
|
@ -534,7 +534,8 @@ const AddTableModal = ({
|
|||
// Call the parent component's add table function
|
||||
await onAddTable(tableData);
|
||||
|
||||
// Reset form (modal will be closed by parent component)
|
||||
// Close modal and reset form
|
||||
onClose();
|
||||
resetForm();
|
||||
} catch (error) {
|
||||
console.error('Error adding table:', error);
|
||||
|
|
|
|||
|
|
@ -551,7 +551,6 @@ 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`,
|
||||
|
|
@ -774,61 +773,6 @@ 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 {
|
||||
|
|
@ -884,8 +828,48 @@ 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(() => {
|
||||
|
|
@ -914,9 +898,6 @@ 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,
|
||||
|
|
@ -925,8 +906,7 @@ const ERDiagramCanvasContent = () => {
|
|||
description: targetDatabase.description || `Database: ${targetDatabase.name}`,
|
||||
service: selectedService?.name,
|
||||
dataSource: selectedDataSource?.name,
|
||||
schemas: schemasWithTables,
|
||||
relationships: relationships
|
||||
schemas: schemasWithTables
|
||||
};
|
||||
|
||||
console.log('Complete database structure:', completeDatabase);
|
||||
|
|
@ -1022,14 +1002,6 @@ 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 = [];
|
||||
|
|
@ -1103,7 +1075,7 @@ const ERDiagramCanvasContent = () => {
|
|||
key_type: key.key_type,
|
||||
key_columns: [{
|
||||
column_name: key.column_name,
|
||||
sequence: key.sequence || 1
|
||||
sequence: key.sequence
|
||||
}]
|
||||
}))
|
||||
};
|
||||
|
|
@ -1133,9 +1105,10 @@ const ERDiagramCanvasContent = () => {
|
|||
created_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
console.log('Table created successfully via API:', newTable);
|
||||
// Add the table to existing tables list
|
||||
setExistingTables(prev => [...prev, newTable]);
|
||||
|
||||
// Update the current database structure immediately
|
||||
// Update the current database structure and regenerate the diagram
|
||||
const updatedDatabase = { ...selectedDatabase };
|
||||
const schemaIndex = updatedDatabase.schemas.findIndex(s => s.sch === tableData.schema);
|
||||
|
||||
|
|
@ -1145,51 +1118,14 @@ 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);
|
||||
|
||||
// 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
|
||||
generateERDiagram(updatedDatabase);
|
||||
|
||||
// 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);
|
||||
console.log('Table added successfully:', newTable);
|
||||
}
|
||||
|
||||
// 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');
|
||||
}
|
||||
|
|
@ -1197,9 +1133,6 @@ 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}`;
|
||||
|
|
@ -1214,22 +1147,16 @@ const ERDiagramCanvasContent = () => {
|
|||
|
||||
// Generate ER diagram with Database Wrapper structure
|
||||
const generateERDiagram = (database) => {
|
||||
console.log('🔄 Starting ER diagram generation...');
|
||||
console.log('Database received:', database?.name);
|
||||
console.log('Total schemas:', database?.schemas?.length);
|
||||
|
||||
const newNodes = [];
|
||||
const newEdges = []; // No edges - removing all connections
|
||||
const newEdges = [];
|
||||
|
||||
// Generate dummy relationships
|
||||
const relationships = generateDummyERData(database);
|
||||
|
||||
// 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,
|
||||
tableNames: s.tables?.map(t => t.name) || []
|
||||
})));
|
||||
console.log('Schema details:', database?.schemas?.map(s => ({ name: s.name, sch: s.sch, tableCount: s.tables?.length || 0 })));
|
||||
|
||||
if (database && database.schemas && database.schemas.length > 0) {
|
||||
// Calculate database wrapper dimensions
|
||||
|
|
@ -1389,21 +1316,16 @@ 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 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: col.is_primary_key || colIndex === 0, // Use existing or first column as primary key
|
||||
is_foreign_key: isForeignKey
|
||||
};
|
||||
});
|
||||
// Add primary key and foreign key indicators to columns
|
||||
const enhancedColumns = (table.columns || []).map((col, colIndex) => ({
|
||||
...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
|
||||
)
|
||||
)
|
||||
}));
|
||||
|
||||
newNodes.push({
|
||||
id: tableId,
|
||||
|
|
@ -1436,80 +1358,38 @@ const ERDiagramCanvasContent = () => {
|
|||
});
|
||||
}
|
||||
|
||||
// Create relationship edges based on API data
|
||||
if (database.relationships && database.relationships.length > 0) {
|
||||
console.log('Creating relationship edges:', database.relationships.length);
|
||||
// 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}`}`;
|
||||
|
||||
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 nodes exist
|
||||
const sourceExists = newNodes.some(node => node.id === sourceTableId);
|
||||
const targetExists = newNodes.some(node => node.id === targetTableId);
|
||||
|
||||
// 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}`);
|
||||
|
||||
newEdges.push({
|
||||
id: edgeId,
|
||||
source: sourceNode.id,
|
||||
target: targetNode.id,
|
||||
type: 'erRelationship',
|
||||
data: {
|
||||
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,
|
||||
},
|
||||
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);
|
||||
if (sourceExists && targetExists) {
|
||||
newEdges.push({
|
||||
id: `relationship-${index}`,
|
||||
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
|
||||
},
|
||||
style: {
|
||||
stroke: '#8a2be2',
|
||||
strokeWidth: 2
|
||||
},
|
||||
animated: false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
|
|
@ -1544,7 +1424,23 @@ const ERDiagramCanvasContent = () => {
|
|||
}, 300);
|
||||
};
|
||||
|
||||
// onConnect removed - no connections allowed between tables
|
||||
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]
|
||||
);
|
||||
|
||||
const handleAddClick = () => {
|
||||
setShowAddMenu(!showAddMenu);
|
||||
|
|
@ -1617,8 +1513,14 @@ 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,
|
||||
|
|
@ -1709,7 +1611,38 @@ 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>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue