diff --git a/src/backend/base/langflow/schema/table.py b/src/backend/base/langflow/schema/table.py index 8f265647b09..6d19311234b 100644 --- a/src/backend/base/langflow/schema/table.py +++ b/src/backend/base/langflow/schema/table.py @@ -1,6 +1,8 @@ from enum import Enum -from pydantic import BaseModel, Field, field_validator +from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator + +VALID_TYPES = ["date", "number", "text", "json", "integer", "int", "float", "str", "string"] class FormatterType(str, Enum): @@ -11,19 +13,35 @@ class FormatterType(str, Enum): class Column(BaseModel): - display_name: str + model_config = ConfigDict(populate_by_name=True) name: str + display_name: str = Field(default="") sortable: bool = Field(default=True) filterable: bool = Field(default=True) - formatter: FormatterType | str | None = None - - @field_validator("formatter") + formatter: FormatterType | str | None = Field(default=None, alias="type") + description: str | None = None + default: str | None = None + + @model_validator(mode="after") + def set_display_name(self): + if not self.display_name: + self.display_name = self.name + return self + + @field_validator("formatter", mode="before") + @classmethod def validate_formatter(cls, value): + if value in ["integer", "int", "float"]: + value = FormatterType.number + if value in ["str", "string"]: + value = FormatterType.text + if value == "dict": + value = FormatterType.json if isinstance(value, str): return FormatterType(value) if isinstance(value, FormatterType): return value - msg = "Invalid formatter type" + msg = f"Invalid formatter type: {value}. Valid types are: {FormatterType}" raise ValueError(msg) diff --git a/src/backend/tests/unit/io/test_table_schema.py b/src/backend/tests/unit/io/test_table_schema.py new file mode 100644 index 00000000000..cefa5f82f3c --- /dev/null +++ b/src/backend/tests/unit/io/test_table_schema.py @@ -0,0 +1,60 @@ +# Generated by qodo Gen + +import pytest + +from langflow.schema.table import Column, FormatterType + + +@pytest.fixture +def client(): + pass + + +class TestColumn: + # Creating a Column instance without display_name sets it to the name + def test_create_column_without_display_name(self): + column = Column(name="test_column") + assert column.display_name == "test_column" + + # Creating a Column instance with valid formatter values + def test_create_column_with_valid_formatter(self): + column = Column(display_name="Test Column", name="test_column", formatter="date") + assert column.formatter == FormatterType.date + + # Formatter is set based on provided formatter value + def test_formatter_set_based_on_value(self): + column = Column(display_name="Test Column", name="test_column", formatter="int") + assert column.formatter == FormatterType.number + + # Default values for sortable and filterable are set to True + def test_default_sortable_filterable(self): + column = Column(display_name="Test Column", name="test_column") + assert column.sortable is True + assert column.filterable is True + + # Ensure formatter field is correctly set when provided a FormatterType + def test_formatter_explicitly_set_to_enum(self): + column = Column(display_name="Date Column", name="date_column", formatter=FormatterType.date) + assert column.formatter == FormatterType.date + + # Invalid formatter raises ValueError + def test_invalid_formatter_raises_value_error(self): + with pytest.raises(ValueError): + Column(display_name="Invalid Column", name="invalid_column", formatter="invalid") + + # Formatter is None when not provided + def test_formatter_none_when_not_provided(self): + column = Column(display_name="Test Column", name="test_column") + assert column.formatter is None + + # Description and default can be set + def test_description_and_default(self): + column = Column( + display_name="Test Column", name="test_column", description="A test column", default="default_value" + ) + assert column.description == "A test column" + assert column.default == "default_value" + + def test_create_with_type_instead_of_formatter(self): + column = Column(display_name="Test Column", name="test_column", type="date") + assert column.formatter == FormatterType.date diff --git a/src/frontend/src/components/parameterRenderComponent/components/TableNodeComponent/index.tsx b/src/frontend/src/components/parameterRenderComponent/components/TableNodeComponent/index.tsx index 113ead8be7f..78d084496d6 100644 --- a/src/frontend/src/components/parameterRenderComponent/components/TableNodeComponent/index.tsx +++ b/src/frontend/src/components/parameterRenderComponent/components/TableNodeComponent/index.tsx @@ -92,7 +92,7 @@ export default function TableNodeComponent({ function addRow() { const newRow = {}; componentColumns.forEach((column) => { - newRow[column.name] = null; + newRow[column.name] = column.default ?? null; // Use the default value if available }); handleOnNewValue({ value: [...value, newRow] }); } diff --git a/src/frontend/src/types/utils/functions.ts b/src/frontend/src/types/utils/functions.ts index 077d7c767d6..f4ea85e9e7b 100644 --- a/src/frontend/src/types/utils/functions.ts +++ b/src/frontend/src/types/utils/functions.ts @@ -16,10 +16,12 @@ export enum FormatterType { json = "json", } -export type ColumnField = { - display_name: string; +export interface ColumnField { name: string; + display_name: string; sortable: boolean; filterable: boolean; formatter?: FormatterType; -}; + description?: string; + default?: any; // Add this line +} diff --git a/src/frontend/src/utils/utils.ts b/src/frontend/src/utils/utils.ts index 1ba82a392e8..16baf93d8ed 100644 --- a/src/frontend/src/utils/utils.ts +++ b/src/frontend/src/utils/utils.ts @@ -528,7 +528,18 @@ export function generateBackendColumnsFromValue(rows: Object[]): ColumnField[] { display_name: column.headerName ?? "", sortable: true, filterable: true, + default: null, // Initialize default to null or appropriate value }; + + // Attempt to infer the default value from the data, if possible + if (rows.length > 0) { + const sampleValue = rows[0][column.field ?? ""]; + if (sampleValue !== undefined) { + newColumn.default = sampleValue; + } + } + + // Determine the formatter based on the sample value if (rows[0] && rows[0][column.field ?? ""]) { const value = rows[0][column.field ?? ""] as any; if (typeof value === "string") { @@ -538,7 +549,6 @@ export function generateBackendColumnsFromValue(rows: Object[]): ColumnField[] { newColumn.formatter = FormatterType.text; } } else if (typeof value === "object" && value !== null) { - // Check if the object is a Date object if ( Object.prototype.toString.call(value) === "[object Date]" || value instanceof Date