Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions dataloom-backend/app/api/endpoints/transformations.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@
def _handle_basic_transform(df, transformation_input, project, db, project_id):
"""Apply a basic transformation and optionally persist changes.

For operations that modify data (addRow, delRow, addCol, delCol, changeCellValue, fillEmpty),
saves to disk and logs the transformation. For read-only operations (filter, sort),
only returns the result.
Filter and sort are persisted to the working copy so GET reload and export match
the last applied view (see apply_logged_transformation for checkpoint replay).

Returns:
Tuple of (result_df, should_save).
Expand All @@ -38,13 +37,13 @@ def _handle_basic_transform(df, transformation_input, project, db, project_id):
if not transformation_input.parameters:
raise HTTPException(status_code=400, detail="Filter parameters required")
p = transformation_input.parameters
return ts.apply_filter(df, p.column, p.condition, p.value), False
return ts.apply_filter(df, p.column, p.condition, p.value), True

elif op == "sort":
if not transformation_input.sort_params:
raise HTTPException(status_code=400, detail="Sort parameters required")
p = transformation_input.sort_params
return ts.apply_sort(df, p.column, p.ascending), False
return ts.apply_sort(df, p.column, p.ascending), True

elif op == "addRow":
if not transformation_input.row_params:
Expand Down Expand Up @@ -118,13 +117,13 @@ def _handle_complex_transform(df, transformation_input, project, db, project_id)
elif op == "advQueryFilter":
if not transformation_input.adv_query:
raise HTTPException(status_code=400, detail="Query parameter required")
return ts.advanced_query(df, transformation_input.adv_query.query), False
return ts.advanced_query(df, transformation_input.adv_query.query), True

elif op == "pivotTables":
if not transformation_input.pivot_query:
raise HTTPException(status_code=400, detail="Pivot parameters required")
p = transformation_input.pivot_query
return ts.pivot_table(df, p.index, p.value, p.column, p.aggfun), False
return ts.pivot_table(df, p.index, p.value, p.column, p.aggfun), True

elif op == "dropNa":
columns = None
Expand Down
23 changes: 23 additions & 0 deletions dataloom-backend/app/services/transformation_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,29 @@ def apply_logged_transformation(df: pd.DataFrame, action_type: str, action_detai
columns = action_details.get("drop_na_params", {}).get("columns")
return drop_na(df, columns)

elif action_type == "filter":
params = action_details["parameters"]
condition = params["condition"]
return apply_filter(df, params["column"], condition, params["value"])

elif action_type == "sort":
p = action_details["sort_params"]
return apply_sort(df, p["column"], p["ascending"])

elif action_type == "advQueryFilter":
query = action_details["adv_query"]["query"]
return advanced_query(df, query)

elif action_type == "pivotTables":
p = action_details["pivot_query"]
column = p.get("column") or None
if column == "":
column = None
agg = p.get("aggfun", "sum")
if hasattr(agg, "value"):
agg = agg.value
return pivot_table(df, p["index"], p["value"], column, str(agg))

else:
logger.warning("Unknown action type in log replay: %s", action_type)
return df
41 changes: 41 additions & 0 deletions dataloom-backend/tests/test_transformations.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
add_row,
advanced_query,
apply_filter,
apply_logged_transformation,
apply_sort,
change_cell_value,
delete_column,
Expand Down Expand Up @@ -105,6 +106,46 @@ def test_delete_row_out_of_range(self, sample_df):
delete_row(sample_df, 10)


class TestApplyLoggedViewTransforms:
"""Log replay for filter/sort/adv query/pivot (persisted view operations)."""

def test_replay_filter(self, sample_df):
details = {"parameters": {"column": "name", "condition": "=", "value": "Alice"}}
result = apply_logged_transformation(sample_df, "filter", details)
assert len(result) == 1
assert result.iloc[0]["name"] == "Alice"

def test_replay_sort(self, sample_df):
details = {"sort_params": {"column": "age", "ascending": True}}
result = apply_logged_transformation(sample_df, "sort", details)
assert result.iloc[0]["name"] == "Bob"

def test_replay_adv_query(self, sample_df):
details = {"adv_query": {"query": "age > 28"}}
result = apply_logged_transformation(sample_df, "advQueryFilter", details)
assert len(result) == 2

def test_replay_pivot(self):
df = pd.DataFrame(
{
"city": ["NY", "NY", "LA"],
"product": ["A", "B", "A"],
"sales": [1, 2, 3],
}
)
details = {
"pivot_query": {
"index": "city",
"column": "product",
"value": "sales",
"aggfun": "sum",
}
}
result = apply_logged_transformation(df, "pivotTables", details)
assert "city" in result.columns
assert len(result) >= 1


class TestAddColumn:
def test_add_column(self, sample_df):
result = add_column(sample_df, 1, "email")
Expand Down
5 changes: 4 additions & 1 deletion dataloom-frontend/src/Components/DataScreen.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Table from "./Table";

export default function DataScreen() {
const { projectId } = useParams();
const { setProjectInfo, refreshProject } = useProjectContext();
const { setProjectInfo, refreshProject, updateData } = useProjectContext();
const [tableData, setTableData] = useState(null);

useEffect(() => {
Expand All @@ -18,6 +18,9 @@ export default function DataScreen() {

const handleTransform = (data) => {
setTableData(data);
if (data?.columns != null && data?.rows != null) {
updateData(data.columns, data.rows, data.dtypes);
}
};

return (
Expand Down
4 changes: 4 additions & 0 deletions dataloom-frontend/src/Components/MenuNavbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ const MenuNavbar = ({ projectId, onTransform }) => {
setActiveForm(null);
}}
projectId={projectId}
onTransform={onTransform}
/>
)}
{showSortForm && (
Expand All @@ -316,6 +317,7 @@ const MenuNavbar = ({ projectId, onTransform }) => {
setActiveForm(null);
}}
projectId={projectId}
onTransform={onTransform}
/>
)}
{showDropDuplicateForm && (
Expand All @@ -335,6 +337,7 @@ const MenuNavbar = ({ projectId, onTransform }) => {
setActiveForm(null);
}}
projectId={projectId}
onTransform={onTransform}
/>
)}
{showPivotTableForm && (
Expand All @@ -344,6 +347,7 @@ const MenuNavbar = ({ projectId, onTransform }) => {
setActiveForm(null);
}}
projectId={projectId}
onTransform={onTransform}
/>
)}
{showCastDataTypeForm && (
Expand Down
7 changes: 4 additions & 3 deletions dataloom-frontend/src/Components/forms/AdvQueryFilterForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ import { ADV_QUERY_FILTER } from "../../constants/operationTypes";
import useError from "../../hooks/useError";
import FormErrorAlert from "../common/FormErrorAlert";

const AdvQueryFilterForm = ({ projectId, onClose }) => {
const AdvQueryFilterForm = ({ projectId, onClose, onTransform }) => {
const [query, setQuery] = useState("");
const [result, setResult] = useState(null);
const [loading, setLoading] = useState(false);
const { error, clearError, handleError } = useError();

const handleSubmit = async (e) => {
e.preventDefault();
console.log("Query:", query);
setLoading(true);
clearError();
try {
Expand All @@ -23,7 +22,8 @@ const AdvQueryFilterForm = ({ projectId, onClose }) => {
adv_query: { query },
});
setResult(response);
console.log("Query API response:", response);
onTransform(response);
onClose();
} catch (err) {
console.error("Error applying query:", err.message);
handleError(err);
Expand Down Expand Up @@ -73,6 +73,7 @@ const AdvQueryFilterForm = ({ projectId, onClose }) => {
AdvQueryFilterForm.propTypes = {
projectId: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
onTransform: PropTypes.func.isRequired,
};

export default AdvQueryFilterForm;
6 changes: 4 additions & 2 deletions dataloom-frontend/src/Components/forms/FilterForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import TransformResultPreview from "./TransformResultPreview";
import useError from "../../hooks/useError";
import FormErrorAlert from "../common/FormErrorAlert";

const FilterForm = ({ projectId, onClose }) => {
const FilterForm = ({ projectId, onClose, onTransform }) => {
const [filterParams, setFilterParams] = useState({
column: "",
condition: "=",
Expand Down Expand Up @@ -34,7 +34,8 @@ const FilterForm = ({ projectId, onClose }) => {
parameters: filterParams,
});
setResult(response);
console.log("Filter API response:", response);
onTransform(response);
onClose();
} catch (err) {
console.error("Error applying filter:", err.response?.data || err.message);
handleError(err);
Expand Down Expand Up @@ -115,6 +116,7 @@ const FilterForm = ({ projectId, onClose }) => {
FilterForm.propTypes = {
projectId: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
onTransform: PropTypes.func.isRequired,
};

export default FilterForm;
6 changes: 4 additions & 2 deletions dataloom-frontend/src/Components/forms/PivotTableForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { PIVOT_TABLES } from "../../constants/operationTypes";
import useError from "../../hooks/useError";
import FormErrorAlert from "../common/FormErrorAlert";

const PivotTableForm = ({ projectId, onClose }) => {
const PivotTableForm = ({ projectId, onClose, onTransform }) => {
const [index, setIndex] = useState("");
const [column, setColumn] = useState("");
const [value, setValue] = useState("");
Expand All @@ -25,7 +25,8 @@ const PivotTableForm = ({ projectId, onClose }) => {
pivot_query: { index, column, value, aggfun },
});
setResult(response);
console.log("Pivot API response:", response);
onTransform(response);
onClose();
} catch (err) {
console.error("Error applying pivot table:", err.message);
handleError(err);
Expand Down Expand Up @@ -113,6 +114,7 @@ const PivotTableForm = ({ projectId, onClose }) => {
PivotTableForm.propTypes = {
projectId: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
onTransform: PropTypes.func.isRequired,
};

export default PivotTableForm;
6 changes: 4 additions & 2 deletions dataloom-frontend/src/Components/forms/SortForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import TransformResultPreview from "./TransformResultPreview";
import useError from "../../hooks/useError";
import FormErrorAlert from "../common/FormErrorAlert";

const SortForm = ({ projectId, onClose }) => {
const SortForm = ({ projectId, onClose, onTransform }) => {
const [column, setColumn] = useState("");
const [ascending, setAscending] = useState(true);
const [result, setResult] = useState(null);
Expand All @@ -27,7 +27,8 @@ const SortForm = ({ projectId, onClose }) => {
},
});
setResult(response);
console.log("Sort API response:", response);
onTransform(response);
onClose();
} catch (err) {
console.error("Error applying sort:", err.response?.data || err.message);
handleError(err);
Expand Down Expand Up @@ -89,6 +90,7 @@ const SortForm = ({ projectId, onClose }) => {
SortForm.propTypes = {
projectId: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
onTransform: PropTypes.func.isRequired,
};

export default SortForm;
Loading