feat: Add default value support for table columns (#4043)
* Add 'type', 'description', and 'default' fields to Table schema and enhance formatter validation * Add type-based mapping to formatter validator in table schema * Add default value support for new table rows in TableNodeComponent * Add optional 'description' and 'default' fields to ColumnField interface * Add default value inference for table columns in utils.ts - Initialize 'default' property for table columns to null. - Infer default value from the first row of data if available. - Adjust column formatter determination based on sample value. * Add default table input validation and update formatter logic in Column model * Add unit tests for Column class in table schema module --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
parent
90b2c9d577
commit
65153374ed
5 changed files with 100 additions and 10 deletions
|
|
@ -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
|
||||
formatter: FormatterType | str | None = Field(default=None, alias="type")
|
||||
description: str | None = None
|
||||
default: str | None = None
|
||||
|
||||
@field_validator("formatter")
|
||||
@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)
|
||||
|
||||
|
||||
|
|
|
|||
60
src/backend/tests/unit/io/test_table_schema.py
Normal file
60
src/backend/tests/unit/io/test_table_schema.py
Normal file
|
|
@ -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
|
||||
|
|
@ -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] });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue