diff --git a/backend/database/data_profile_manager.py b/backend/database/data_profile_manager.py index 2ba8f30..9034f36 100644 --- a/backend/database/data_profile_manager.py +++ b/backend/database/data_profile_manager.py @@ -1,4 +1,4 @@ -from models.data_profile import DataProfile, DataProfileCreateRequest +from models.data_profile import DataProfile class DataProfileManager: @@ -28,17 +28,11 @@ def get_all_data_profile_names_by_org_id(self, org_id): data_profile_names = [name for (name,) in result] return data_profile_names - def create_dataprofile(self, data_profile_data: DataProfileCreateRequest): + def create_dataprofile(self, data_profile: DataProfile): """Create a new DataProfile.""" - new_data_profile = DataProfile( - name=data_profile_data.name, - file_type=data_profile_data.file_type, - organization_id=data_profile_data.organization_id, - extract_instructions=data_profile_data.extract_instructions, - ) - self.session.add(new_data_profile) + self.session.add(data_profile) self.session.commit() - return new_data_profile + return data_profile def get_dataprofile_by_id(self, data_profile_id: int): """Retrieve a DataProfile by its ID.""" diff --git a/backend/database/sql_executor.py b/backend/database/sql_executor.py index c74e64e..558cfc5 100644 --- a/backend/database/sql_executor.py +++ b/backend/database/sql_executor.py @@ -101,7 +101,7 @@ def get_all_table_names_as_list(self) -> List: print(f"An error occurred: {e}") raise - def get_table_columns(self, table_name: str) -> List: + def get_table_column_names(self, table_name: str) -> List: try: engine = self.session.bind inspector = inspect(engine) diff --git a/backend/database/table_manager.py b/backend/database/table_manager.py index c1699c4..9d846c6 100644 --- a/backend/database/table_manager.py +++ b/backend/database/table_manager.py @@ -42,8 +42,22 @@ def _map_table_to_org( print(f"An error occurred: {e}") raise HTTPException(status_code=400, detail=str(e)) + def _unmap_table_from_org(self, table_name: str): + """Unmaps a table from an organization.""" + try: + if self.session: + table_map_manager = TableMapManager(self.session) + table_map_manager.delete_table_map(table_name) + except Exception as e: + print(f"An error occurred: {e}") + raise HTTPException(status_code=400, detail=str(e)) + def create_table_for_data_profile( - self, org_id: int, table_name: str, column_names_and_types: dict + self, + org_id: int, + table_name: str, + table_alias: str, + column_names_and_types: dict, ): """Creates a table for a data profile.""" try: @@ -51,7 +65,7 @@ def create_table_for_data_profile( executor.create_table_for_data_profile( org_id, table_name, column_names_and_types ) - self._map_table_to_org(org_id, table_name) + self._map_table_to_org(org_id, table_name, table_alias) except Exception as e: print(f"An error occurred: {e}") raise HTTPException(status_code=400, detail=str(e)) @@ -180,6 +194,7 @@ def drop_table(self, table_name: str): try: executor = SQLExecutor(self.session) executor.drop_table(table_name) + self._unmap_table_from_org(table_name) except Exception as e: print(f"An error occurred: {e}") raise HTTPException(status_code=400, detail=str(e)) @@ -193,11 +208,11 @@ def execute_select_query(self, query: str, format_as_dict: bool = True): print(f"An error occurred: {e}") raise HTTPException(status_code=400, detail=str(e)) - def get_table_columns(self, table_name: str): + def get_table_column_names(self, table_name: str): """Returns a list of all of the columns present within the table.""" try: executor = SQLExecutor(self.session) - columns = executor.get_table_columns(table_name) + columns = executor.get_table_column_names(table_name) return columns except Exception as e: print(f"An error occurred: {e}") diff --git a/backend/database/table_map_manager.py b/backend/database/table_map_manager.py index 09ab8d0..46db9f1 100644 --- a/backend/database/table_map_manager.py +++ b/backend/database/table_map_manager.py @@ -42,6 +42,24 @@ def create_table_map(self, table_map: TableMap): print(f"An error occurred: {e}") raise HTTPException(status_code=400, detail=str(e)) + def delete_table_map(self, table_name: str): + """ + Delete a table map from the database. + + Args: + table_name (str): The name of the table to be deleted. + """ + try: + if self.db_session: + self.db_session.query(TableMap).filter( + TableMap.table_name == table_name + ).delete() + self.db_session.commit() + except Exception as e: + self.db_session.rollback() if self.db_session else None + print(f"An error occurred: {e}") + raise HTTPException(status_code=400, detail=str(e)) + def get_org_tables(self, org_id: int) -> List: """Returns a list of names of all of the tables associated with an organization.""" try: diff --git a/backend/envs/dev/initialization/setup_dev_environment.py b/backend/envs/dev/initialization/setup_dev_environment.py index 61c0246..e0926e0 100644 --- a/backend/envs/dev/initialization/setup_dev_environment.py +++ b/backend/envs/dev/initialization/setup_dev_environment.py @@ -174,6 +174,7 @@ def create_sample_dataprofile(): file_type="pdf", organization_id=1, extract_instructions="Sample extract instructions", + table_name="sample_sales", ) # Using DatabaseManager to manage the database session with DatabaseManager() as session: diff --git a/backend/routes/data_profile_routes.py b/backend/routes/data_profile_routes.py index 0bbb4ce..1dd59d6 100644 --- a/backend/routes/data_profile_routes.py +++ b/backend/routes/data_profile_routes.py @@ -72,15 +72,17 @@ async def save_data_profile( table_manager.create_table_for_data_profile( org_id=current_user.organization_id, table_name=table_name, + table_alias=request.name, column_names_and_types=request.column_names_and_types, ) + print(table_name) # Create the data profile new_data_profile = DataProfile( name=request.name, extract_instructions=request.extract_instructions, organization_id=current_user.organization_id, - table_name=table_name, # TODO: To be further implemented + table_name=table_name, ) created_data_profile = data_profile_manager.create_dataprofile(new_data_profile) @@ -175,6 +177,24 @@ async def generate_suggested_column_types( return suggested_column_types +@data_profile_router.get("/data-profiles/{data_profile_name}/table/column-names/") +async def get_data_profile_table_column_names( + data_profile_name: str, current_user: User = Depends(get_current_user) +): + with DatabaseManager() as session: + data_profile_manager = DataProfileManager(session) + data_profile = data_profile_manager.get_dataprofile_by_name_and_org( + data_profile_name, current_user.organization_id + ) + if data_profile is None: + raise HTTPException(status_code=404, detail="Data Profile not found") + print("data_profile.table_name", data_profile.table_name) + table_manager = TableManager(session) + column_names = table_manager.get_table_column_names(data_profile.table_name) + print("column_names", column_names) + return column_names + + @data_profile_router.post("/data-profiles/{data_profile_name}/preview/") async def preview_data_profile_upload( data_profile_name: str, diff --git a/backend/routes/table_routes.py b/backend/routes/table_routes.py index 643c517..79ce04f 100644 --- a/backend/routes/table_routes.py +++ b/backend/routes/table_routes.py @@ -17,12 +17,12 @@ async def get_org_tables(org_id: int, current_user: User = Depends(get_current_u @table_router.get("/table/columns/") -async def get_table_columns( +async def get_table_column_names( table_name: str, current_user: User = Depends(get_current_user) ): with DatabaseManager() as session: manager = TableManager(session) - columns = manager.get_table_columns(table_name) + columns = manager.get_table_column_names(table_name) return columns diff --git a/backend/utils/sql_string_manager.py b/backend/utils/sql_string_manager.py index b7f3d05..a7f1ea2 100644 --- a/backend/utils/sql_string_manager.py +++ b/backend/utils/sql_string_manager.py @@ -100,7 +100,7 @@ def is_valid_create_table_query(self) -> bool: pattern = r"^CREATE TABLE .+;\s*$" return bool(re.match(pattern, clean_query)) - def is_valid_pg_table_name(self, table_name) -> bool: + def is_valid_table_name(self, table_name) -> bool: # Check if the table name matches the allowed pattern # Pattern explanation: # ^[_a-z] : Must start with an underscore or a lowercase letter diff --git a/frontend/src/pages/upload/PreviewTable.jsx b/frontend/src/pages/upload/PreviewTable.jsx index 922268e..d867f12 100644 --- a/frontend/src/pages/upload/PreviewTable.jsx +++ b/frontend/src/pages/upload/PreviewTable.jsx @@ -9,32 +9,29 @@ import { TableRow, } from "@mui/material"; -function PreviewTable({ previewData }) { - const data = Array.isArray(previewData) ? previewData : [previewData]; - - const generateTableHeaders = (data) => { - if (data && data.length > 0) { - return Object.keys(data[0]).map((key) => ( - {key.replace(/_/g, " ").toUpperCase()} - )); - } - return null; - }; - +function PreviewTable({ columnNames, previewData }) { return ( - {generateTableHeaders(data)} + + {columnNames && + columnNames.map((columnName) => ( + + {columnName.replace(/_/g, " ").toUpperCase()} + + ))} + - {data.map((row, index) => ( - - {Object.values(row).map((value, idx) => ( - {value} - ))} - - ))} + {previewData && + previewData.map((row, index) => ( + + {Object.values(row).map((value, idx) => ( + {value} + ))} + + ))}
diff --git a/frontend/src/pages/upload/UploadPage.jsx b/frontend/src/pages/upload/UploadPage.jsx index 1b88fd4..a430215 100644 --- a/frontend/src/pages/upload/UploadPage.jsx +++ b/frontend/src/pages/upload/UploadPage.jsx @@ -24,6 +24,7 @@ function UploadPage() { severity: "info", }); const [showCreateDataProfile, setShowCreateDataProfile] = useState(false); + const [columnNames, setColumnNames] = useState([]); const [previewData, setPreviewData] = useState(null); const [isPreviewLoading, setIsPreviewLoading] = useState(false); const [isPreviewTableOpen, setIsPreviewTableOpen] = useState(false); @@ -37,6 +38,18 @@ function UploadPage() { .catch((error) => console.error("Error fetching data profiles:", error)); }, []); + useEffect(() => { + if (dataProfile) { + axios + .get(`${API_URL}data-profiles/${dataProfile}/table/column-names/`) + .then((response) => { + setColumnNames(response.data); + setPreviewData(null); // Reset preview data + }) + .catch((error) => console.error("Error fetching column names:", error)); + } + }, [dataProfile]); + const handleCreateDataProfile = ( name, extractInstructions, @@ -154,7 +167,9 @@ function UploadPage() { - {previewData && } + {(columnNames.length > 0 || previewData) && ( + + )} {isPreviewLoading && }