Qubit_EPM/src/components/mockData.js

1802 lines
59 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) => {
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;