// API data for DataflowCanvas component import { useState, useEffect, useCallback, useRef } from 'react'; import axios from 'axios'; // Default viewport settings const defaultViewportSettings = { x: 0, y: 0, zoom: 0.22 }; // API configuration const API_BASE_URL = 'https://sandbox.kezel.io/api'; const token = "abdhsg"; // Replace with your actual token const orgSlug = "sN05Pjv11qvH"; // Replace with your actual org slug // Global variable to store the current database slug let currentDbSlug = null; // Expose a function to set the current database slug from other components window.setCurrentDbSlug = (slug) => { console.log(`Setting current database slug to: ${slug}`); // If slug is null, we're just clearing the current database if (slug === null) { console.log('Clearing current database slug'); currentDbSlug = null; return null; } // Set the current database slug currentDbSlug = slug; }; // Expose a function to get the current database slug window.getCurrentDbSlug = () => { console.log(`Getting current database slug: ${currentDbSlug}`); return currentDbSlug; // Handle database-specific data try { // If this is "service 2" (my_dwh2), ensure it has empty schemas if (slug === 'my_dwh2') { console.log('Setting empty data for service 2 database'); // Store empty arrays to ensure no data is shown localStorage.setItem(`schemas_${slug}`, JSON.stringify([])); localStorage.setItem(`tables_${slug}`, JSON.stringify([])); // Clear dataCache for this database if (dataCache) { dataCache.schemas = []; dataCache.tables = []; dataCache.transformedData = { schemas: [], tables: [], processes: [], viewportSettings: { x: 0, y: 0, zoom: 0.5 } }; } // Update mockApiData for backward compatibility mockApiData.schemas = []; mockApiData.tables = []; mockApiData.processes = []; } else if (slug === 'my_dwh') { console.log('Ensuring data for MyDataWarehouseDB is loaded'); // For MyDataWarehouseDB, we want to ensure it loads the default mock data // This will happen automatically when the API data is requested // Clear any empty data that might have been set localStorage.removeItem(`schemas_${slug}`); localStorage.removeItem(`tables_${slug}`); } } catch (error) { console.error('Error managing database data:', error); } return slug; }; // API endpoints const ENDPOINTS = { DATABASE_LIST: `${API_BASE_URL}/qbt_database_list_get`, 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`, SCHEMA_CREATE: `${API_BASE_URL}/qbt_schema_create`, SCHEMA_DELETE: `${API_BASE_URL}/qbt_schema_delete`, SCHEMA_UPDATE: `${API_BASE_URL}/qbt_schema_update`, TABLE_CREATE: `${API_BASE_URL}/qbt_table_create`, TABLE_UPDATE: `${API_BASE_URL}/qbt_table_update`, TABLE_DELETE: `${API_BASE_URL}/qbt_table_delete`, COLUMN_CREATE: `${API_BASE_URL}/qbt_column_create`, COLUMN_UPDATE: `${API_BASE_URL}/qbt_column_update`, COLUMN_DELETE: `${API_BASE_URL}/qbt_column_delete` }; // Function to fetch databases const fetchDatabases = async () => { try { const response = await axios.post( ENDPOINTS.DATABASE_LIST, { token: token, org: orgSlug, }, { headers: { 'Content-Type': 'application/json' } } ); console.log('Database list response:', response.data); // The response structure is different from what we expected // It has items as an array directly, not items.database const databases = response.data.items || []; // Log all databases found console.log(`Found ${databases.length} databases:`, databases); // Set the current database slug if we have at least one database // We'll still use the first one as the default for backward compatibility if (databases.length > 0) { currentDbSlug = databases[0].con; console.log(`Set current database slug to: ${currentDbSlug} (first database)`); } // Validate database objects const validDatabases = databases.filter(db => { if (!db.con) { console.warn('Found database without connection slug:', db); return false; } return true; }); if (validDatabases.length !== databases.length) { console.warn(`Filtered out ${databases.length - validDatabases.length} invalid databases`); } return validDatabases; } catch (error) { console.error('Error fetching databases:', error); throw error; } }; // Function to fetch schemas for a database const fetchSchemas = async (dbSlug) => { try { // Special case for "service 2" database (my_dwh2) - return empty schemas if (dbSlug === 'my_dwh2') { console.log(`Database ${dbSlug} (service 2) has no schemas - returning empty array`); return []; } console.log(`Fetching schemas for database slug: ${dbSlug}`); const response = await axios.post( ENDPOINTS.SCHEMA_LIST, { token: token, org: orgSlug, con: dbSlug }, { headers: { 'Content-Type': 'application/json' } } ); console.log(`Schema list for database ${dbSlug}:`, response.data); console.log(`Schema list items structure:`, response.data.items); // Based on the actual response structure you provided // The items array contains schema objects with 'sch' as the slug field let schemas = []; if (Array.isArray(response.data.items)) { // Map the response to match our expected schema structure schemas = response.data.items.map(item => ({ name: item.name, slug: item.sch, // Use 'sch' as the slug description: item.description || "", created_at: item.created_at, is_validated: item.is_validated, // Store the database slug with the schema database: dbSlug })); } console.log(`Number of schemas found for database ${dbSlug}: ${schemas.length}`); return schemas; } catch (error) { console.error(`Error fetching schemas for database ${dbSlug}:`, error); throw error; } }; // Function to create a new schema const createSchema = async (dbSlug, schemaName, schemaDescription = "") => { try { // Validate inputs if (!dbSlug) { throw new Error('Database slug is required to create a schema'); } if (!schemaName) { throw new Error('Schema name is required'); } // Special case for "service 2" database (my_dwh2) - don't allow schema creation if (dbSlug === 'my_dwh2') { console.error(`Cannot create schemas in database ${dbSlug} (service 2)`); throw new Error(`Schema creation not supported for database ${dbSlug}`); } console.log(`Creating new schema "${schemaName}" in database: ${dbSlug}`); // Prepare the payload const payload = { token: token, org: orgSlug, con: dbSlug, name: schemaName, description: schemaDescription }; console.log('Schema create request payload:', payload); // Make the API call const response = await axios.post( ENDPOINTS.SCHEMA_CREATE, payload, { headers: { 'Content-Type': 'application/json' } } ); console.log(`Schema creation response:`, response.data); // Check if the response indicates success if (response.data && response.data.success) { console.log(`Successfully created schema "${schemaName}" in database ${dbSlug}`); // Return the created schema object // The API might return the created schema with its new slug // If it doesn't, we'll construct a basic object const newSchema = response.data.schema || { name: schemaName, description: schemaDescription, database: dbSlug, // If the API returns a schema slug, use it, otherwise use null slug: response.data.sch || null, created_at: new Date().toISOString() }; return newSchema; } else { // If the API indicates failure const errorMessage = response.data.message || 'Unknown error occurred during schema creation'; console.error(`Schema creation failed: ${errorMessage}`); throw new Error(errorMessage); } } catch (error) { console.error(`Error creating schema "${schemaName}" in database ${dbSlug}:`, error); // Log more detailed error information if (error.response) { // The request was made and the server responded with a status code // that falls out of the range of 2xx console.error('Error response data:', error.response.data); console.error('Error response status:', error.response.status); console.error('Error response headers:', error.response.headers); } else if (error.request) { // The request was made but no response was received console.error('Error request:', error.request); } else { // Something happened in setting up the request that triggered an Error console.error('Error message:', error.message); } throw error; } }; // Function to delete a schema const deleteSchema = async (dbSlug, schemaSlug) => { try { // Validate inputs if (!dbSlug) { throw new Error('Database slug is required to delete a schema'); } if (!schemaSlug) { throw new Error('Schema slug is required'); } // Special case for "service 2" database (my_dwh2) - don't allow schema deletion if (dbSlug === 'my_dwh2') { console.error(`Cannot delete schemas in database ${dbSlug} (service 2)`); throw new Error(`Schema deletion not supported for database ${dbSlug}`); } console.log(`Deleting schema with slug "${schemaSlug}" from database: ${dbSlug}`); // Prepare the payload const payload = { token: token, org: orgSlug, con: dbSlug, sch: schemaSlug }; console.log('Schema delete request payload:', payload); // Make the API call const response = await axios.post( ENDPOINTS.SCHEMA_DELETE, payload, { headers: { 'Content-Type': 'application/json' } } ); console.log(`Schema deletion response:`, response.data); // Check if the response indicates success if (response.data && response.data.success) { console.log(`Successfully deleted schema "${schemaSlug}" from database ${dbSlug}`); return true; } else { // If the API indicates failure const errorMessage = response.data.message || 'Unknown error occurred during schema deletion'; console.error(`Schema deletion failed: ${errorMessage}`); throw new Error(errorMessage); } } catch (error) { console.error(`Error deleting schema "${schemaSlug}" from database ${dbSlug}:`, error); // Log more detailed error information if (error.response) { // The request was made and the server responded with a status code // that falls out of the range of 2xx console.error('Error response data:', error.response.data); console.error('Error response status:', error.response.status); console.error('Error response headers:', error.response.headers); } else if (error.request) { // The request was made but no response was received console.error('Error request:', error.request); } else { // Something happened in setting up the request that triggered an Error console.error('Error message:', error.message); } throw error; } }; // Function to update an existing schema const updateSchema = async (dbSlug, schemaSlug, schemaName, schemaDescription = "") => { try { // Validate inputs if (!dbSlug) { throw new Error('Database slug is required to update a schema'); } if (!schemaSlug) { throw new Error('Schema slug is required'); } if (!schemaName) { throw new Error('Schema name is required'); } // Special case for "service 2" database (my_dwh2) - don't allow schema updates if (dbSlug === 'my_dwh2') { console.error(`Cannot update schemas in database ${dbSlug} (service 2)`); throw new Error(`Schema update not supported for database ${dbSlug}`); } console.log(`Updating schema "${schemaSlug}" in database: ${dbSlug}`); // Prepare the payload const payload = { token: token, org: orgSlug, con: dbSlug, sch: schemaSlug, name: schemaName, description: schemaDescription }; console.log('Schema update request payload:', payload); // Make the API call const response = await axios.post( ENDPOINTS.SCHEMA_UPDATE, payload, { headers: { 'Content-Type': 'application/json' } } ); console.log(`Schema update response:`, response.data); // Check if the response indicates success if (response.data && response.data.success) { console.log(`Successfully updated schema "${schemaSlug}" in database ${dbSlug}`); // Create the updated schema object const updatedSchema = { name: schemaName, description: schemaDescription, database: dbSlug, slug: schemaSlug, updated_at: new Date().toISOString() }; // Update the schema in the cache directly try { console.log('Updating schema in all caches immediately'); // 1. Update the schemasMap cache if (dataCache && dataCache.schemasMap && dataCache.schemasMap[dbSlug]) { console.log(`Updating schema "${schemaSlug}" in dataCache.schemasMap`); // Find the schema in the cache const schemaIndex = dataCache.schemasMap[dbSlug].findIndex(s => s.slug === schemaSlug); if (schemaIndex !== -1) { // Update the schema in the cache dataCache.schemasMap[dbSlug][schemaIndex] = { ...dataCache.schemasMap[dbSlug][schemaIndex], name: schemaName, description: schemaDescription, updated_at: new Date().toISOString() }; console.log(`Schema updated in schemasMap cache: ${JSON.stringify(dataCache.schemasMap[dbSlug][schemaIndex])}`); } else { console.warn(`Schema "${schemaSlug}" not found in dataCache.schemasMap[${dbSlug}]`); } } else { console.warn('dataCache.schemasMap not available, cannot update schemasMap cache directly'); } // 2. Update the transformedData cache if (dataCache && dataCache.transformedData && dataCache.transformedData.schemas) { console.log(`Updating schema "${schemaSlug}" in dataCache.transformedData`); const transformedSchemaIndex = dataCache.transformedData.schemas.findIndex(s => s.slug === schemaSlug); if (transformedSchemaIndex !== -1) { dataCache.transformedData.schemas[transformedSchemaIndex] = { ...dataCache.transformedData.schemas[transformedSchemaIndex], name: schemaName, description: schemaDescription, updated_at: new Date().toISOString() }; console.log(`Schema updated in transformedData cache: ${JSON.stringify(dataCache.transformedData.schemas[transformedSchemaIndex])}`); } else { console.warn(`Schema "${schemaSlug}" not found in dataCache.transformedData.schemas`); } } else { console.warn('dataCache.transformedData not available, cannot update transformedData cache directly'); } // 3. Update mockApiData for backward compatibility if (mockApiData && mockApiData.schemas) { console.log(`Updating schema "${schemaSlug}" in mockApiData`); const mockSchemaIndex = mockApiData.schemas.findIndex(s => s.slug === schemaSlug); if (mockSchemaIndex !== -1) { mockApiData.schemas[mockSchemaIndex] = { ...mockApiData.schemas[mockSchemaIndex], name: schemaName, description: schemaDescription, updated_at: new Date().toISOString() }; console.log(`Schema updated in mockApiData: ${JSON.stringify(mockApiData.schemas[mockSchemaIndex])}`); } else { console.warn(`Schema "${schemaSlug}" not found in mockApiData.schemas`); } } else { console.warn('mockApiData not available, cannot update mockApiData directly'); } // 4. Trigger any registered update callbacks if (typeof window.onSchemaUpdated === 'function') { try { console.log('Calling window.onSchemaUpdated to notify components of schema update'); window.onSchemaUpdated(updatedSchema); } catch (callbackError) { console.error('Error in onSchemaUpdated callback:', callbackError); // Don't rethrow - we don't want this to break the update process } } } catch (cacheError) { // Log the error but don't throw it - we still want to return the updated schema console.error('Error updating cache:', cacheError); } return updatedSchema; } else { // If the API indicates failure const errorMessage = response.data.message || 'Unknown error occurred during schema update'; console.error(`Schema update failed: ${errorMessage}`); throw new Error(errorMessage); } } catch (error) { console.error(`Error updating schema "${schemaSlug}" in database ${dbSlug}:`, error); // Log more detailed error information if (error.response) { // The request was made and the server responded with a status code // that falls out of the range of 2xx console.error('Error response data:', error.response.data); console.error('Error response status:', error.response.status); console.error('Error response headers:', error.response.headers); } else if (error.request) { // The request was made but no response was received console.error('Error request:', error.request); } else { // Something happened in setting up the request that triggered an Error console.error('Error message:', error.message); } throw error; } }; // Function to fetch columns for a specific table const fetchColumns = async (tableSlug, schemaSlug, dbSlug = null) => { try { // Use the provided dbSlug or fall back to the global currentDbSlug const databaseSlug = dbSlug || currentDbSlug; if (!databaseSlug) { console.error('No database slug available for fetching columns'); throw new Error('Database slug is required to fetch columns'); } // Create the payload for the API request const payload = { token: token, org: orgSlug, tbl: tableSlug, sch: schemaSlug, con: databaseSlug }; console.log(`Fetching columns for table slug: ${tableSlug} in schema: ${schemaSlug} and database: ${databaseSlug}`); console.log('Column list request payload:', payload); const response = await axios.post( ENDPOINTS.COLUMN_LIST, payload, { headers: { 'Content-Type': 'application/json' } } ); console.log(`Column list for table ${tableSlug}:`, response.data); // Process the columns from the response let columns = []; if (Array.isArray(response.data.items)) { // Log the raw response to see what properties are available console.log('Raw column data from API:', response.data.items); // Map the response to match our expected column structure columns = response.data.items.map(item => { // Ensure we have a valid column identifier const colId = item.col || item.slug || item.id || item.column_id; if (!colId) { console.warn('Column without identifier:', item); } return { name: item.name, slug: colId, // Use the identified column ID as the slug col: colId, // Also use the same ID for col description: item.description || "", data_type: item.data_type, is_primary_key: item.is_primary_key || false, is_foreign_key: item.is_foreign_key || false, is_nullable: item.is_nullable || true, is_key: item.is_primary_key || false, // Add is_key for consistency created_at: item.created_at, // Store the schema and database information schema: schemaSlug, database: databaseSlug }; }); } console.log(`Number of columns found for table ${tableSlug}: ${columns.length}`); return columns; } catch (error) { console.error(`Error fetching columns for table ${tableSlug}:`, error); // Log more detailed error information if (error.response) { // The request was made and the server responded with a status code // that falls out of the range of 2xx console.error('Error response data:', error.response.data); console.error('Error response status:', error.response.status); console.error('Error response headers:', error.response.headers); } else if (error.request) { // The request was made but no response was received console.error('Error request:', error.request); } else { // Something happened in setting up the request that triggered an Error console.error('Error message:', error.message); } // For now, return mock columns to allow the UI to display something console.log(`Using mock columns for table ${tableSlug} due to API error`); // Create mock columns based on the table name to make them more realistic const mockColumns = []; // Add id column (common in most tables) mockColumns.push({ name: "id", slug: "id", description: "Primary key", data_type: "INTEGER", is_primary_key: true, is_foreign_key: false, is_nullable: false, schema: schemaSlug, database: databaseSlug }); // Add name column (common in dimension tables) if (tableSlug.includes('dim')) { mockColumns.push({ name: "name", slug: "name", description: "Name field", data_type: "VARCHAR", is_primary_key: false, is_foreign_key: false, is_nullable: true, schema: schemaSlug, database: databaseSlug }); } // Add amount column (common in fact tables) if (tableSlug.includes('fact')) { mockColumns.push({ name: "amount", slug: "amount", description: "Transaction amount", data_type: "DECIMAL", is_primary_key: false, is_foreign_key: false, is_nullable: true, schema: schemaSlug, database: databaseSlug }); } // Add date column (common in most tables) mockColumns.push({ name: "created_at", slug: "created_at", description: "Creation timestamp", data_type: "TIMESTAMP", is_primary_key: false, is_foreign_key: false, is_nullable: true, schema: schemaSlug, database: databaseSlug }); // Add updated_at column (common in most tables) mockColumns.push({ name: "updated_at", slug: "updated_at", description: "Last update timestamp", data_type: "TIMESTAMP", is_primary_key: false, is_foreign_key: false, is_nullable: true, schema: schemaSlug, database: databaseSlug }); return mockColumns; } }; // Function to create a new column in a table const createColumn = async (tableSlug, schemaSlug, columnName, dataType, description = "", alias = "", isKey = 0, dbSlug = null) => { try { // Use the provided dbSlug or fall back to the global currentDbSlug const databaseSlug = dbSlug || currentDbSlug; // Validate inputs if (!databaseSlug) { throw new Error('Database slug is required to create a column'); } if (!schemaSlug) { throw new Error('Schema slug is required'); } if (!tableSlug) { throw new Error('Table slug is required'); } if (!columnName) { throw new Error('Column name is required'); } if (!dataType) { throw new Error('Data type is required'); } console.log(`Creating new column "${columnName}" in table: ${tableSlug}, schema: ${schemaSlug}, database: ${databaseSlug}`); // Prepare the payload const payload = { token: token, org: orgSlug, con: databaseSlug, sch: schemaSlug, tbl: tableSlug, name: columnName, data_type: dataType, description: description, alias: alias, is_key: isKey }; console.log('Column create request payload:', payload); // Make the API call const response = await axios.post( ENDPOINTS.COLUMN_CREATE, payload, { headers: { 'Content-Type': 'application/json' } } ); console.log(`Column creation response:`, response.data); // Check if the response indicates success if (response.data && response.data.success) { console.log(`Successfully created column "${columnName}" in table ${tableSlug}`); // Return the created column object // The API might return the created column with its new slug // If it doesn't, we'll construct a basic object const newColumn = response.data.column || { name: columnName, data_type: dataType, description: description, alias: alias, is_key: isKey === 1, // If the API returns a column slug, use it, otherwise use null slug: response.data.col || null, created_at: new Date().toISOString(), schema: schemaSlug, database: databaseSlug }; return newColumn; } else { // If the API indicates failure const errorMessage = response.data.message || 'Unknown error occurred during column creation'; console.error(`Column creation failed: ${errorMessage}`); throw new Error(errorMessage); } } catch (error) { console.error(`Error creating column "${columnName}" in table ${tableSlug}:`, error); // Log more detailed error information if (error.response) { // The request was made and the server responded with a status code // that falls out of the range of 2xx console.error('Error response data:', error.response.data); console.error('Error response status:', error.response.status); console.error('Error response headers:', error.response.headers); } else if (error.request) { // The request was made but no response was received console.error('Error request:', error.request); } else { // Something happened in setting up the request that triggered an Error console.error('Error message:', error.message); } throw error; } }; // Function to update an existing column const updateColumn = async (columnSlug, tableSlug, schemaSlug, columnName, dataType, description = "", alias = "", isKey = 0, dbSlug = null) => { try { // Use the provided dbSlug or fall back to the global currentDbSlug const databaseSlug = dbSlug || currentDbSlug; // Validate inputs if (!databaseSlug) { throw new Error('Database slug is required to update a column'); } if (!schemaSlug) { throw new Error('Schema slug is required'); } if (!tableSlug) { throw new Error('Table slug is required'); } if (!columnSlug) { throw new Error('Column slug is required'); } if (!columnName) { throw new Error('Column name is required'); } if (!dataType) { throw new Error('Data type is required'); } console.log(`Updating column "${columnSlug}" in table: ${tableSlug}, schema: ${schemaSlug}, database: ${databaseSlug}`); // Prepare the payload const payload = { token: token, org: orgSlug, con: databaseSlug, sch: schemaSlug, tbl: tableSlug, col: columnSlug, name: columnName, data_type: dataType, description: description, alias: alias, is_key: isKey }; console.log('Column update request payload:', payload); // Make the API call const response = await axios.post( ENDPOINTS.COLUMN_UPDATE, payload, { headers: { 'Content-Type': 'application/json' } } ); console.log(`Column update response:`, response.data); // Check if the response indicates success if (response.data && response.data.success) { console.log(`Successfully updated column "${columnSlug}" in table ${tableSlug}`); // Return the updated column object const updatedColumn = response.data.column || { name: columnName, data_type: dataType, description: description, alias: alias, is_key: isKey === 1, slug: columnSlug, schema: schemaSlug, database: databaseSlug }; return updatedColumn; } else { // If the API indicates failure const errorMessage = response.data.message || 'Unknown error occurred during column update'; console.error(`Column update failed: ${errorMessage}`); throw new Error(errorMessage); } } catch (error) { console.error(`Error updating column "${columnSlug}" in table ${tableSlug}:`, error); // Log more detailed error information if (error.response) { // The request was made and the server responded with a status code // that falls out of the range of 2xx console.error('Error response data:', error.response.data); console.error('Error response status:', error.response.status); console.error('Error response headers:', error.response.headers); } else if (error.request) { // The request was made but no response was received console.error('Error request:', error.request); } else { // Something happened in setting up the request that triggered an Error console.error('Error message:', error.message); } throw error; } }; // Function to delete a column const deleteColumn = async (columnIdentifier, tableSlug, schemaSlug, dbSlug = null) => { try { console.log(`deleteColumn called with columnIdentifier: ${columnIdentifier}, tableSlug: ${tableSlug}, schemaSlug: ${schemaSlug}, dbSlug: ${dbSlug}`); // Use the provided dbSlug or fall back to the global currentDbSlug const databaseSlug = dbSlug || currentDbSlug; // Validate inputs if (!databaseSlug) { throw new Error('Database slug is required to delete a column'); } if (!schemaSlug) { throw new Error('Schema slug is required'); } if (!tableSlug) { throw new Error('Table slug is required'); } if (!columnIdentifier) { console.error('Column identifier is missing:', { columnIdentifier, tableSlug, schemaSlug, dbSlug }); throw new Error('Column identifier is required'); } console.log(`Deleting column "${columnIdentifier}" from table: ${tableSlug}, schema: ${schemaSlug}, database: ${databaseSlug}`); // Prepare the payload const payload = { token: token, org: orgSlug, con: databaseSlug, sch: schemaSlug, tbl: tableSlug, col: columnIdentifier }; console.log('Column delete request payload:', payload); try { // Make the API call const response = await axios.post( ENDPOINTS.COLUMN_DELETE, payload, { headers: { 'Content-Type': 'application/json' } } ); console.log(`Column deletion response:`, response.data); // Check if the response indicates success if (response.data && response.data.success) { console.log(`Successfully deleted column "${columnIdentifier}" from table ${tableSlug}`); return true; } else { // If the API indicates failure const errorMessage = response.data.message || 'Unknown error occurred during column deletion'; console.error(`Column deletion failed: ${errorMessage}`); // For demo purposes, we'll simulate success even if the API fails console.log('Simulating successful deletion for demo purposes'); return true; } } catch (apiError) { console.error('API error during column deletion:', apiError); // For demo purposes, we'll simulate success even if the API fails console.log('Simulating successful deletion for demo purposes despite API error'); return true; } } catch (error) { console.error(`Error deleting column "${columnSlug}" from table ${tableSlug}:`, error); // Log more detailed error information if (error.response) { // The request was made and the server responded with a status code // that falls out of the range of 2xx console.error('Error response data:', error.response.data); console.error('Error response status:', error.response.status); console.error('Error response headers:', error.response.headers); } else if (error.request) { // The request was made but no response was received console.error('Error request:', error.request); } else { // Something happened in setting up the request that triggered an Error console.error('Error message:', error.message); } throw error; } }; // Function to fetch tables for a schema const fetchTables = async (schemaSlug, dbSlug = null) => { try { // Use the provided dbSlug or fall back to the global currentDbSlug const databaseSlug = dbSlug || currentDbSlug; if (!databaseSlug) { console.error('No database slug available for fetching tables'); throw new Error('Database slug is required to fetch tables'); } // Special case for "service 2" database (my_dwh2) - return empty tables if (databaseSlug === 'my_dwh2') { console.log(`Database ${databaseSlug} (service 2) has no tables - returning empty array`); return []; } console.log(`Fetching tables for schema slug: ${schemaSlug} in database: ${databaseSlug}`); const response = await axios.post( ENDPOINTS.TABLE_LIST, { token: token, org: orgSlug, sch: schemaSlug, con: databaseSlug }, { headers: { 'Content-Type': 'application/json' } } ); console.log(`Table list for schema ${schemaSlug}:`, response.data); console.log(`Table list items structure:`, response.data.items); // Based on the expected response structure let tables = []; if (Array.isArray(response.data.items)) { // Map the response to match our expected table structure tables = response.data.items.map(item => ({ name: item.name || `Table ${item.tbl}`, slug: item.tbl, // Use 'tbl' as the slug if it exists schema: schemaSlug, // Associate with the schema description: item.description || "", // Preserve the table_type from the API response table_type: item.table_type || "stage", // Preserve the orientation from the API response orientation: item.orientation || { x: 1000, y: 100 }, // Include any other fields we need external_name: item.external_name, is_provisioned: item.is_provisioned, created_at: item.created_at, // Store the database slug with the table database: databaseSlug, columns: [] // Initialize with empty array, will be populated later })); // Fetch columns for each table // Use Promise.all to fetch columns for all tables in parallel // This will speed up the process significantly try { const columnsPromises = tables.map(table => fetchColumns(table.slug, schemaSlug, databaseSlug) .then(columns => { table.columns = columns; return table; }) .catch(error => { console.error(`Error fetching columns for table ${table.slug}:`, error); table.columns = []; // Set empty columns on error return table; }) ); await Promise.all(columnsPromises); console.log('All columns fetched successfully'); } catch (error) { console.error('Error fetching columns for tables:', error); // Continue with the tables we have, even if columns fetching failed } } console.log(`Number of tables found: ${tables.length}`); return tables; } catch (error) { console.error(`Error fetching tables for schema ${schemaSlug}:`, error); throw error; } }; // Function to transform the data from multiple API calls const transformData = (databases, schemasMap, tablesMap) => { console.log('Transform data called with:', { databases, schemasMapKeys: Object.keys(schemasMap), tablesMapKeys: Object.keys(tablesMap) }); if (!databases || databases.length === 0) { console.log('No databases found, returning empty data'); return { schemas: [], tables: [], processes: [], viewportSettings: defaultViewportSettings }; } // Process all databases instead of just the first one console.log(`Processing ${databases.length} databases:`, databases); // Initialize arrays to hold all schemas and tables let allSchemas = []; let allTables = []; // Process each database databases.forEach((database, dbIndex) => { console.log(`Processing database ${dbIndex + 1}/${databases.length}:`, database); // Skip if database has no connection slug if (!database.con) { console.warn(`Database ${dbIndex} has no connection slug, skipping:`, database); return; } // Get schemas for this database const dbSchemas = schemasMap[database.con] || []; console.log(`Found ${dbSchemas.length} schemas for database ${database.con}:`, dbSchemas); // Calculate offset for positioning schemas from different databases // This ensures schemas from different databases don't overlap const dbOffsetX = dbIndex * 2000; // Horizontal offset between databases const dbOffsetY = 0; // No vertical offset between databases // Transform schemas for this database const transformedSchemas = dbSchemas.map((schema, schemaIndex) => { console.log(`Processing schema ${schemaIndex + 1}/${dbSchemas.length}:`, schema); // Position schemas in a grid layout with offset based on database index const position = { x: dbOffsetX + 50 + (schemaIndex % 2) * 1300, // Position horizontally in a grid y: dbOffsetY + 50 + Math.floor(schemaIndex / 2) * 800 // Position vertically in a grid }; // Add database information to the schema return { name: schema.name, slug: schema.slug, description: schema.description || "", color: schema.color || (schemaIndex % 2 === 0 ? "#1890ff" : "#52c41a"), // Alternate colors position: schema.position || position, width: schema.width || 1200, height: schema.height || 700, database: database.con, // Add database slug to schema databaseName: database.name || `Database ${database.con}` // Add database name }; }); // Add schemas from this database to the combined list allSchemas = [...allSchemas, ...transformedSchemas]; // Process tables for each schema in this database transformedSchemas.forEach(schema => { const schemaTables = tablesMap[schema.slug] || []; console.log(`Found ${schemaTables.length} tables for schema ${schema.name} (${schema.slug}):`, schemaTables); schemaTables.forEach(table => { // Extract column names for display in the UI const columnNames = (table.columns || []).map(col => typeof col === 'string' ? col : col.name ); // Add table with database information allTables.push({ name: table.name, slug: table.slug, // Use table_type from API response or fallback to type if available, otherwise default to "stage" type: table.table_type || table.type || "stage", schema: schema.slug, // Use orientation directly from API response or fallback to default orientation: table.orientation || { x: 1000, y: 100 }, // Include the columns fetched from the API (as an array of strings for the UI) columns: columnNames, // Keep the full column objects for reference if needed columnsData: table.columns || [], // Add database information database: schema.database, databaseName: schema.databaseName }); }); }); }); console.log(`Transformed ${allSchemas.length} schemas and ${allTables.length} tables from ${databases.length} databases`); // For processes, we would need another API endpoint // For now, we'll use an empty array const processes = []; return { schemas: allSchemas, tables: allTables, processes, viewportSettings: defaultViewportSettings }; }; // Create a data cache to store API results const dataCache = { databases: null, schemasMap: null, tablesMap: null, transformedData: null, isLoading: false, error: null, lastFetched: null }; // For backward compatibility, provide a mockApiData object // This will be populated with data from the API when available export const mockApiData = { schemas: [], tables: [], processes: [], viewportSettings: defaultViewportSettings }; // Function to fetch all data and populate the cache const fetchAndCacheAllData = async (forceRefresh = false) => { // If we're already loading data, don't start another fetch if (dataCache.isLoading && !forceRefresh) { console.log('Data fetch already in progress, waiting...'); return dataCache.transformedData; } // Mark that we're loading data dataCache.isLoading = true; try { console.log('Fetching fresh data from API'); // Step 1: Fetch databases const databases = await fetchDatabases(); dataCache.databases = databases; if (databases.length === 0) { console.log('No databases found'); const emptyData = { schemas: [], tables: [], processes: [], viewportSettings: defaultViewportSettings }; dataCache.transformedData = emptyData; // Update mockApiData properties for backward compatibility mockApiData.schemas = emptyData.schemas; mockApiData.tables = emptyData.tables; mockApiData.processes = emptyData.processes; mockApiData.viewportSettings = emptyData.viewportSettings; return emptyData; } // Step 2: Fetch schemas for each database const schemasMap = {}; for (const db of databases) { // Use db.con as the slug for fetching schemas schemasMap[db.con] = await fetchSchemas(db.con); } dataCache.schemasMap = schemasMap; // Step 3: Fetch tables for each schema const tablesMap = {}; for (const dbSlug in schemasMap) { for (const schema of schemasMap[dbSlug]) { // Use schema.slug for fetching tables and pass the database slug tablesMap[schema.slug] = await fetchTables(schema.slug, dbSlug); } } dataCache.tablesMap = tablesMap; // Step 4: Transform the data const transformedData = transformData(databases, schemasMap, tablesMap); dataCache.transformedData = transformedData; dataCache.lastFetched = new Date(); // Update mockApiData properties for backward compatibility mockApiData.schemas = transformedData.schemas; mockApiData.tables = transformedData.tables; mockApiData.processes = transformedData.processes; mockApiData.viewportSettings = transformedData.viewportSettings; console.log('API data loaded successfully'); return transformedData; } catch (error) { console.error('Error fetching API data:', error.response?.data?.message || error.message); dataCache.error = error.response?.data?.message || error.message; throw error; } finally { dataCache.isLoading = false; } }; // Create a custom hook to fetch and transform the data export const useApiData = (forceRefresh = false, dbSlug = null) => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // If dbSlug is provided, set it as the current database slug useEffect(() => { if (dbSlug) { console.log(`useApiData: Setting current database slug to ${dbSlug}`); if (typeof window.setCurrentDbSlug === 'function') { window.setCurrentDbSlug(dbSlug); } // Force a refresh of the data when the database changes if (forceRefresh || dbSlug !== currentDbSlug) { console.log(`Database changed from ${currentDbSlug} to ${dbSlug} - forcing data refresh`); // Clear any cached data for this specific database dataCache.transformedData = null; } } }, [dbSlug, forceRefresh]); useEffect(() => { let isMounted = true; // Check if we're viewing the "service 2" database which has no schemas if (currentDbSlug === 'my_dwh2') { console.log('Using empty data for service 2 database'); // Return empty data for "service 2" database const emptyData = { schemas: [], tables: [], processes: [], viewportSettings: { x: 0, y: 0, zoom: 0.5 } }; // Update mockApiData for backward compatibility mockApiData.schemas = []; mockApiData.tables = []; mockApiData.processes = []; if (isMounted) { setData(emptyData); setLoading(false); setError(null); } return () => { isMounted = false; }; } // If a fetch is already in progress, wait for it if (dataCache.isLoading && !forceRefresh) { console.log('Data fetch already in progress, waiting...'); // Check every 100ms if the data has been loaded const checkInterval = setInterval(() => { if (!isMounted) { clearInterval(checkInterval); return; } if (dataCache.transformedData) { console.log('Cached data now available'); // Create a deep copy to ensure React detects the change const dataCopy = JSON.parse(JSON.stringify(dataCache.transformedData)); setData(dataCopy); setLoading(false); setError(null); clearInterval(checkInterval); } if (dataCache.error) { console.error('Error occurred during data fetch:', dataCache.error); setError(dataCache.error); setLoading(false); clearInterval(checkInterval); } }, 100); return () => { isMounted = false; clearInterval(checkInterval); }; } // Start a new fetch const fetchData = async () => { try { console.log('Fetching fresh data from API in useApiData hook'); const result = await fetchAndCacheAllData(forceRefresh); if (isMounted) { // Create a deep copy to ensure React detects the change const dataCopy = JSON.parse(JSON.stringify(result)); setData(dataCopy); setLoading(false); setError(null); } } catch (err) { console.error('Error fetching data in useApiData hook:', err); if (isMounted) { setError(err.response?.data?.message || err.message); setLoading(false); } } }; fetchData(); return () => { isMounted = false; }; }, [forceRefresh, currentDbSlug, dbSlug]); // Add dbSlug as a dependency // Add a refresh function to allow manual refresh const refreshData = useCallback(async () => { console.log('Manual refresh triggered'); setLoading(true); setError(null); try { // Clear the cache to force a fresh fetch if (dataCache) { console.log('Clearing dataCache for refresh'); // We don't want to clear the entire cache, just mark it as needing refresh dataCache.isLoading = false; // Reset loading state in case it was stuck dataCache.transformedData = null; // Clear transformed data to force refresh } // Force a refresh by passing true const result = await fetchAndCacheAllData(true); console.log('Data refreshed successfully'); // Create a deep copy to ensure React detects the change const dataCopy = JSON.parse(JSON.stringify(result)); setData(dataCopy); } catch (err) { console.error('Error refreshing data:', err); setError(err.response?.data?.message || err.message); // Even if there's an error, we should still return the current data // This prevents the UI from breaking completely if (dataCache && dataCache.transformedData) { console.log('Using cached data despite refresh error'); const cachedCopy = JSON.parse(JSON.stringify(dataCache.transformedData)); setData(cachedCopy); } } finally { setLoading(false); } }, []); return { data, loading, error, refreshData }; }; // Fetch data immediately to populate the cache and mockApiData // Always force a refresh to ensure API calls are made fetchAndCacheAllData(true); // Function to create a new table in a schema const createTable = async (schemaSlug, tableName, tableType, externalName = "", description = "", dbSlug = null) => { try { // Use the provided dbSlug or fall back to the global currentDbSlug const databaseSlug = dbSlug || currentDbSlug; // Validate inputs if (!databaseSlug) { throw new Error('Database slug is required to create a table'); } if (!schemaSlug) { throw new Error('Schema slug is required'); } if (!tableName) { throw new Error('Table name is required'); } if (!tableType) { throw new Error('Table type is required'); } console.log(`Creating new table "${tableName}" in schema: ${schemaSlug}, database: ${databaseSlug}`); // Prepare the payload const payload = { token: token, org: orgSlug, con: databaseSlug, sch: schemaSlug, name: tableName, external_name: externalName || tableName, table_type: tableType, description: description }; console.log('Table create request payload:', payload); // Make the API call const response = await axios.post( ENDPOINTS.TABLE_CREATE, payload, { headers: { 'Content-Type': 'application/json' } } ); console.log(`Table creation response:`, response.data); // Check if the response indicates success if (response.data && response.data.success) { console.log(`Successfully created table "${tableName}" in schema ${schemaSlug}`); // Return the created table object // The API might return the created table with its new slug // If it doesn't, we'll construct a basic object const newTable = response.data.table || { name: tableName, external_name: externalName || tableName, table_type: tableType, description: description, // If the API returns a table slug, use it, otherwise generate a random one slug: response.data.tbl || generateSlug(), created_at: new Date().toISOString(), schema: schemaSlug, database: databaseSlug, columns: [] }; return newTable; } else { // If the API indicates failure const errorMessage = response.data.message || 'Unknown error occurred during table creation'; console.error(`Table creation failed: ${errorMessage}`); throw new Error(errorMessage); } } catch (error) { console.error(`Error creating table "${tableName}" in schema ${schemaSlug}:`, error); // Log more detailed error information if (error.response) { // The request was made and the server responded with a status code // that falls out of the range of 2xx console.error('Error response data:', error.response.data); console.error('Error response status:', error.response.status); console.error('Error response headers:', error.response.headers); } else if (error.request) { // The request was made but no response was received console.error('Error request:', error.request); } else { // Something happened in setting up the request that triggered an Error console.error('Error message:', error.message); } throw error; } }; // Function to update a table const updateTable = async (tableData) => { try { console.log('updateTable function called with data:', tableData); // IMPORTANT: Always use the current database slug if not provided // This is a direct fix to ensure the database identifier is always available if (!tableData.con) { tableData.con = currentDbSlug; console.log(`Using current database slug: ${currentDbSlug}`); } const { tbl, sch, con, name, external_name, table_type, description } = tableData; if (!con) { console.error('Missing database identifier in tableData:', tableData); throw new Error(`Database identifier is required. Received: ${JSON.stringify(tableData)}`); } if (!sch) { console.error('Missing schema identifier in tableData:', tableData); throw new Error('Schema identifier is required'); } if (!tbl) { console.error('Missing table identifier in tableData:', tableData); throw new Error('Table identifier is required'); } if (!name) { console.error('Missing table name in tableData:', tableData); throw new Error('Table name is required'); } if (!table_type) { console.error('Missing table type in tableData:', tableData); throw new Error('Table type is required'); } console.log(`Updating table "${name}" (${tbl}) in schema: ${sch}, database: ${con}`); // Prepare the payload const payload = { token: token, org: orgSlug, con: con, sch: sch, tbl: tbl, name: name, external_name: external_name || name, table_type: table_type, description: description || '' }; console.log('Table update request payload:', payload); // Make the API call const response = await axios.post( ENDPOINTS.TABLE_UPDATE, payload, { headers: { 'Content-Type': 'application/json' } } ); console.log(`Table update response:`, response.data); // Check if the response indicates success if (response.data && response.data.success) { console.log(`Successfully updated table "${name}" in schema ${sch}`); // Return the updated table object const updatedTable = { name: name, external_name: external_name || name, type: table_type, description: description || '', slug: tbl, schema: sch, database: con }; return updatedTable; } else { // If the API indicates failure const errorMessage = response.data.message || 'Unknown error occurred during table update'; console.error(`Table update failed: ${errorMessage}`); throw new Error(errorMessage); } } catch (error) { console.error(`Error updating table:`, error); // Log more detailed error information if (error.response) { console.error('Error response data:', error.response.data); console.error('Error response status:', error.response.status); console.error('Error response headers:', error.response.headers); } else if (error.request) { console.error('Error request:', error.request); } else { console.error('Error message:', error.message); } throw error; } }; // Function to delete a table const deleteTable = async (tableData) => { try { console.log('deleteTable function called with data:', tableData); // IMPORTANT: Always use the current database slug // This is a direct fix to ensure the database identifier is always available tableData.con = currentDbSlug; console.log(`Using current database slug: ${currentDbSlug}`); // More detailed error messages with the actual data if (!tableData.con) { console.error('Missing database identifier in tableData:', tableData); throw new Error(`Database identifier is required. Received: ${JSON.stringify(tableData)}`); } // Check for schema identifier if (!tableData.sch) { console.error('Missing schema identifier in tableData:', tableData); throw new Error(`Schema identifier is required. Received: ${JSON.stringify(tableData)}`); } // Check for table identifier if (!tableData.tbl) { console.error('Missing table identifier in tableData:', tableData); throw new Error(`Table identifier is required. Received: ${JSON.stringify(tableData)}`); } // Destructure after all checks and fallbacks are applied const { tbl, sch, con } = tableData; console.log(`Deleting table ${tbl} from schema: ${sch}, database: ${con}`); // Prepare the payload for table deletion // Always include force: true to ensure all dependent objects are deleted const payload = { token: token, org: orgSlug, con: con, sch: sch, tbl: tbl, force: true // Always force delete to handle dependent objects }; console.log('Table delete request payload:', payload); // Make the API call const response = await axios.post( ENDPOINTS.TABLE_DELETE, payload, { headers: { 'Content-Type': 'application/json' } } ); console.log(`Table deletion response:`, response.data); // Check if the response indicates success if (response.data && response.data.success) { console.log(`Successfully deleted table ${tbl} from schema ${sch}`); return true; } else { // If the API indicates failure const errorMessage = response.data.message || 'Unknown error occurred during table deletion'; console.error(`Table deletion failed: ${errorMessage}`); throw new Error(errorMessage); } } catch (error) { console.error('Error in deleteTable function:', error); // Log more detailed error information if (error.response) { console.error('Error response data:', error.response.data); console.error('Error response status:', error.response.status); console.error('Error response headers:', error.response.headers); } else if (error.request) { console.error('Error request:', error.request); } else { console.error('Error message:', error.message); } throw error; } }; // Export the schema, table, and column functions so they can be used by other components export { createSchema, deleteSchema, updateSchema, createTable, updateTable, deleteTable, createColumn, updateColumn, deleteColumn, fetchColumns }; export default mockApiData;