1833 lines
61 KiB
JavaScript
1833 lines
61 KiB
JavaScript
// 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, orientation = 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}`);
|
|
|
|
// Generate random position if not provided
|
|
const tableOrientation = orientation || {
|
|
x: Math.floor(Math.random() * 400) + 100,
|
|
y: Math.floor(Math.random() * 400) + 100
|
|
};
|
|
|
|
// Prepare the payload
|
|
const payload = {
|
|
token: token,
|
|
org: orgSlug,
|
|
con: databaseSlug,
|
|
sch: schemaSlug,
|
|
name: tableName,
|
|
external_name: externalName || tableName,
|
|
table_type: tableType,
|
|
description: description,
|
|
config: { orientation: tableOrientation }
|
|
};
|
|
|
|
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: [],
|
|
orientation: tableOrientation, // Add orientation from the config
|
|
config: { orientation: tableOrientation } // Also include full config
|
|
};
|
|
|
|
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;
|
|
}
|
|
};
|
|
|
|
// Note: We've removed the separate updateTablePosition function
|
|
// as we're now using the regular updateTable function to update positions
|
|
// This ensures the standard API endpoint (qbt_table_update) is used for all table updates
|
|
|
|
// 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, orientation } = 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
|
|
// Log orientation data
|
|
console.log('Table update orientation data:', orientation);
|
|
|
|
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 || '',
|
|
config: {
|
|
orientation: orientation || tableData.orientation || {
|
|
x: Math.floor(Math.random() * 700) + 100,
|
|
y: Math.floor(Math.random() * 700) + 100
|
|
}
|
|
}
|
|
};
|
|
|
|
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}`);
|
|
|
|
// Extract orientation from config if available
|
|
const tableOrientation = orientation ||
|
|
(tableData.orientation ? tableData.orientation : null) ||
|
|
(payload.config && payload.config.orientation ? payload.config.orientation : null);
|
|
|
|
// 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,
|
|
orientation: tableOrientation, // Include orientation from config in the response
|
|
config: payload.config // Also include the full config
|
|
};
|
|
|
|
console.log('Returning updated table with orientation:', updatedTable.orientation);
|
|
|
|
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; |