Skip to content

Commit

Permalink
feat: Add default value support for table columns (#4043)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
ogabrielluiz and github-actions[bot] authored Oct 9, 2024
1 parent 90b2c9d commit 6515337
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 11 deletions.
30 changes: 24 additions & 6 deletions src/backend/base/langflow/schema/table.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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)


Expand Down
60 changes: 60 additions & 0 deletions src/backend/tests/unit/io/test_table_schema.py
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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] });
}
Expand Down
8 changes: 5 additions & 3 deletions src/frontend/src/types/utils/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
12 changes: 11 additions & 1 deletion src/frontend/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand All @@ -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
Expand Down

0 comments on commit 6515337

Please sign in to comment.