Skip to content

Commit

Permalink
#470: editing cells for column names with special characters
Browse files Browse the repository at this point in the history
  • Loading branch information
aschonfeld committed Apr 27, 2021
1 parent aa7ad53 commit 715ba75
Show file tree
Hide file tree
Showing 20 changed files with 267 additions and 148 deletions.
1 change: 1 addition & 0 deletions docs/source/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ scipy
scikit-learn
statsmodels==0.10.2; python_version < '3.0'
statsmodels; python_version > '3.0'
streamlit
xarray
pyarrow
xlrd
56 changes: 34 additions & 22 deletions dtale/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1774,9 +1774,9 @@ def txt_count(r):
return string_metrics, code


@dtale.route("/describe/<data_id>/<column>")
@dtale.route("/describe/<data_id>")
@exception_decorator
def describe(data_id, column):
def describe(data_id):
"""
:class:`flask:flask.Flask` route which returns standard details about column data using
:meth:`pandas:pandas.DataFrame.describe` to the front-end as JSON
Expand All @@ -1792,6 +1792,7 @@ def describe(data_id, column):
}
"""
column = get_str_arg(request, "col")
data = global_state.get_data(data_id)[[column]]
additional_aggs = None
dtype = global_state.get_dtype_info(data_id, column)
Expand Down Expand Up @@ -1882,9 +1883,9 @@ def describe(data_id, column):
return jsonify(return_data)


@dtale.route("/variance/<data_id>/<column>")
@dtale.route("/variance/<data_id>")
@exception_decorator
def variance(data_id, column):
def variance(data_id):
"""
:class:`flask:flask.Flask` route which returns standard details about column data using
:meth:`pandas:pandas.DataFrame.describe` to the front-end as JSON
Expand All @@ -1900,6 +1901,7 @@ def variance(data_id, column):
}
"""
column = get_str_arg(request, "col")
s = global_state.get_data(data_id)[column]
code = ["s = df['{}']".format(column)]
unique_ct = unique_count(s)
Expand Down Expand Up @@ -1970,9 +1972,10 @@ def build_outlier_query(iqr_lower, iqr_upper, min_val, max_val, column):
return "(({}))".format(") or (".join(queries)) if len(queries) > 1 else queries[0]


@dtale.route("/outliers/<data_id>/<column>")
@dtale.route("/outliers/<data_id>")
@exception_decorator
def outliers(data_id, column):
def outliers(data_id):
column = get_str_arg(request, "col")
df = global_state.get_data(data_id)
s = df[column]
iqr_lower, iqr_upper = calc_outlier_range(s)
Expand Down Expand Up @@ -2000,9 +2003,10 @@ def outliers(data_id, column):
)


@dtale.route("/toggle-outlier-filter/<data_id>/<column>")
@dtale.route("/toggle-outlier-filter/<data_id>")
@exception_decorator
def toggle_outlier_filter(data_id, column):
def toggle_outlier_filter(data_id):
column = get_str_arg(request, "col")
settings = global_state.get_settings(data_id) or {}
outlierFilters = settings.get("outlierFilters") or {}
if column in outlierFilters:
Expand All @@ -2023,9 +2027,10 @@ def toggle_outlier_filter(data_id, column):
return jsonify(dict(success=True, outlierFilters=settings["outlierFilters"]))


@dtale.route("/delete-col/<data_id>/<column>")
@dtale.route("/delete-col/<data_id>")
@exception_decorator
def delete_col(data_id, column):
def delete_col(data_id):
column = get_str_arg(request, "col")
data = global_state.get_data(data_id)
data = data[[c for c in data.columns if c != column]]
curr_history = global_state.get_history(data_id) or []
Expand All @@ -2043,9 +2048,10 @@ def delete_col(data_id, column):
return jsonify(success=True)


@dtale.route("/rename-col/<data_id>/<column>")
@dtale.route("/rename-col/<data_id>")
@exception_decorator
def rename_col(data_id, column):
def rename_col(data_id):
column = get_str_arg(request, "col")
rename = get_str_arg(request, "rename")
data = global_state.get_data(data_id)
if column != rename and rename in data.columns:
Expand All @@ -2070,9 +2076,10 @@ def rename_col(data_id, column):
return jsonify(success=True)


@dtale.route("/edit-cell/<data_id>/<column>")
@dtale.route("/edit-cell/<data_id>")
@exception_decorator
def edit_cell(data_id, column):
def edit_cell(data_id):
column = get_str_arg(request, "col")
row_index = get_int_arg(request, "rowIndex")
updated = get_str_arg(request, "updated")
updated_str = updated
Expand Down Expand Up @@ -2159,9 +2166,10 @@ def build_filter_vals(series, data_id, column, fmt):
return vals


@dtale.route("/column-filter-data/<data_id>/<column>")
@dtale.route("/column-filter-data/<data_id>")
@exception_decorator
def get_column_filter_data(data_id, column):
def get_column_filter_data(data_id):
column = get_str_arg(request, "col")
s = global_state.get_data(data_id)[column]
dtype = find_dtype(s)
fmt = find_dtype_formatter(dtype)
Expand All @@ -2176,9 +2184,10 @@ def get_column_filter_data(data_id, column):
return jsonify(ret)


@dtale.route("/async-column-filter-data/<data_id>/<column>")
@dtale.route("/async-column-filter-data/<data_id>")
@exception_decorator
def get_async_column_filter_data(data_id, column):
def get_async_column_filter_data(data_id):
column = get_str_arg(request, "col")
input = get_str_arg(request, "input")
s = global_state.get_data(data_id)[column]
dtype = find_dtype(s)
Expand All @@ -2188,9 +2197,10 @@ def get_async_column_filter_data(data_id, column):
return jsonify(vals)


@dtale.route("/save-column-filter/<data_id>/<column>")
@dtale.route("/save-column-filter/<data_id>")
@exception_decorator
def save_column_filter(data_id, column):
def save_column_filter(data_id):
column = get_str_arg(request, "col")
curr_filters = ColumnFilter(
data_id, column, get_str_arg(request, "cfg")
).save_filter()
Expand Down Expand Up @@ -3197,9 +3207,11 @@ def shortest_path(data_id):
return jsonify(dict(data=shortest_path, success=True))


@dtale.route("/sorted-sequential-diffs/<data_id>/<column>/<sort>")
@dtale.route("/sorted-sequential-diffs/<data_id>")
@exception_decorator
def get_sorted_sequential_diffs(data_id, column, sort):
def get_sorted_sequential_diffs(data_id):
column = get_str_arg(request, "col")
sort = get_str_arg(request, "sort")
df = global_state.get_data(data_id)
metrics, _ = build_sequential_diffs(df[column], column, sort=sort)
return jsonify(metrics)
Expand Down
2 changes: 1 addition & 1 deletion static/__tests__/filters/ColumnFilter-date-test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe("ColumnFilter date tests", () => {
beforeAll(() => {
const mockBuildLibs = withGlobalJquery(() =>
mockPopsicle.mock(url => {
if (_.startsWith(url, "/dtale/column-filter-data/1/col4")) {
if (_.startsWith(url, "/dtale/column-filter-data/1?col=col4")) {
return {
success: true,
hasMissing: true,
Expand Down
4 changes: 2 additions & 2 deletions static/__tests__/filters/ColumnFilter-invalid-type-test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ describe("ColumnFilter string tests", () => {
beforeAll(() => {
const mockBuildLibs = withGlobalJquery(() =>
mockPopsicle.mock(url => {
if (_.startsWith(url, "/dtale/column-filter-data/1/col3")) {
if (_.startsWith(url, "/dtale/column-filter-data/1?col=col3")) {
return { success: true, hasMissing: true, uniques: ["a", "b", "c"] };
}
if (_.startsWith(url, "/dtale/toggle-outlier-filter/1/col3")) {
if (_.startsWith(url, "/dtale/toggle-outlier-filter/1?col=col3")) {
return { success: true, outlierFilters: {} };
}
const { urlFetcher } = require("../redux-test-utils").default;
Expand Down
4 changes: 2 additions & 2 deletions static/__tests__/filters/ColumnFilter-numeric-async-test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe("ColumnFilter numeric tests", () => {
const mockBuildLibs = withGlobalJquery(() =>
mockPopsicle.mock(url => {
const { urlFetcher, DATA, DTYPES } = require("../redux-test-utils").default;
if (_.startsWith(url, "/dtale/column-filter-data/1/col1")) {
if (_.startsWith(url, "/dtale/column-filter-data/1?col=col1")) {
return {
success: true,
hasMissing: true,
Expand All @@ -33,7 +33,7 @@ describe("ColumnFilter numeric tests", () => {
columns: _.map(DATA.columns, c => (c.name === "col1" ? col1Dtype : c)),
};
}
if (_.startsWith(url, "/dtale/async-column-filter-data/1/col1")) {
if (_.startsWith(url, "/dtale/async-column-filter-data/1?col=col1")) {
return ASYNC_OPTIONS;
}
return urlFetcher(url);
Expand Down
4 changes: 2 additions & 2 deletions static/__tests__/filters/ColumnFilter-numeric-test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe("ColumnFilter numeric tests", () => {
beforeAll(() => {
const mockBuildLibs = withGlobalJquery(() =>
mockPopsicle.mock(url => {
if (_.startsWith(url, "/dtale/column-filter-data/1/col1")) {
if (_.startsWith(url, "/dtale/column-filter-data/1?col=col1")) {
return {
success: true,
hasMissing: true,
Expand All @@ -22,7 +22,7 @@ describe("ColumnFilter numeric tests", () => {
max: 3,
};
}
if (_.startsWith(url, "/dtale/column-filter-data/1/col2")) {
if (_.startsWith(url, "/dtale/column-filter-data/1?col=col2")) {
return { success: true, hasMissing: true, min: 1.0, max: 3.0 };
}
const { urlFetcher } = require("../redux-test-utils").default;
Expand Down
4 changes: 2 additions & 2 deletions static/__tests__/filters/ColumnFilter-string-async-test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe("ColumnFilter string tests", () => {
const mockBuildLibs = withGlobalJquery(() =>
mockPopsicle.mock(url => {
const { urlFetcher, DATA, DTYPES } = require("../redux-test-utils").default;
if (_.startsWith(url, "/dtale/column-filter-data/1/col3")) {
if (_.startsWith(url, "/dtale/column-filter-data/1?col=col3")) {
return { success: true, hasMissing: true, uniques: INITIAL_UNIQUES };
}
if (_.startsWith(url, "/dtale/data")) {
Expand All @@ -28,7 +28,7 @@ describe("ColumnFilter string tests", () => {
columns: _.map(DATA.columns, c => (c.name === "col3" ? col3Dtype : c)),
};
}
if (_.startsWith(url, "/dtale/async-column-filter-data/1/col3")) {
if (_.startsWith(url, "/dtale/async-column-filter-data/1?col=col3")) {
return ASYNC_OPTIONS;
}
return urlFetcher(url);
Expand Down
2 changes: 1 addition & 1 deletion static/__tests__/filters/ColumnFilter-string-test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe("ColumnFilter string tests", () => {
beforeAll(() => {
const mockBuildLibs = withGlobalJquery(() =>
mockPopsicle.mock(url => {
if (_.startsWith(url, "/dtale/column-filter-data/1/col3")) {
if (_.startsWith(url, "/dtale/column-filter-data/1?col=col3")) {
return { success: true, hasMissing: true, uniques: ["a", "b", "c"] };
}
const { urlFetcher } = require("../redux-test-utils").default;
Expand Down
4 changes: 3 additions & 1 deletion static/__tests__/popups/Variance-test.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import qs from "querystring";

import { mount } from "enzyme";
import _ from "lodash";
import React from "react";
Expand Down Expand Up @@ -54,7 +56,7 @@ describe("Variance tests", () => {
const mockBuildLibs = withGlobalJquery(() =>
mockPopsicle.mock(url => {
if (_.startsWith(url, "/dtale/variance")) {
const col = _.last(url.split("?")[0].split("/"));
const { col } = qs.parse(url.split("?")[1]);
if (col === "error") {
return { error: "variance error" };
}
Expand Down
2 changes: 1 addition & 1 deletion static/__tests__/popups/window/Describe-test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe("Describe tests", () => {
if (url === "/dtale/dtypes/1") {
return { error: "dtypes error" };
}
if (url === "/dtale/describe/2/col1") {
if (url === "/dtale/describe/2?col=col1") {
return { error: "describe error" };
}
const { urlFetcher } = require("../../redux-test-utils").default;
Expand Down
5 changes: 2 additions & 3 deletions static/__tests__/redux-test-utils.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,8 @@ function urlFetcher(url) {
}
return { success: true };
} else if (_.startsWith(url, "/dtale/describe")) {
const column = _.last(url.split("/"));
if (_.has(DESCRIBE, column)) {
return _.assignIn({ success: true, code: "describe code test" }, DESCRIBE[column]);
if (_.has(DESCRIBE, urlParams.col)) {
return _.assignIn({ success: true, code: "describe code test" }, DESCRIBE[urlParams.col]);
}
return { error: "Column not found!" };
} else if (_.includes(url, "pypi.org")) {
Expand Down
33 changes: 29 additions & 4 deletions static/actions/url-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,32 @@ function dtypesUrl(dataId) {
return `/dtale/dtypes/${dataId}`;
}

function saveColFilterUrl(dataId, column) {
return `/dtale/save-column-filter/${dataId}/${escape(column)}`;
function saveColFilterUrl(dataId, col, cfg) {
return buildURLString(`/dtale/save-column-filter/${dataId}`, { col, cfg: JSON.stringify(cfg) });
}

function toggleOutlierFilterUrl(dataId, column) {
return `/dtale/toggle-outlier-filter/${dataId}/${escape(column)}`;
function toggleOutlierFilterUrl(dataId) {
return `/dtale/toggle-outlier-filter/${dataId}`;
}

function describeUrl(dataId, col) {
return buildURLString(`/dtale/describe/${dataId}`, { col });
}

function outliersUrl(dataId, col) {
return buildURLString(`/dtale/outliers/${dataId}`, { col });
}

function columnFilterDataUrl(dataId, async = false) {
return `/dtale/${async ? "async-" : ""}column-filter-data/${dataId}`;
}

function varianceUrl(dataId) {
return `/dtale/variance/${dataId}`;
}

function sequentialDiffsUrl(dataId) {
return `/dtale/sorted-sequential-diffs/${dataId}`;
}

function cleanupEndpoint(endpoint) {
Expand All @@ -64,5 +84,10 @@ export {
dtypesUrl,
saveColFilterUrl,
toggleOutlierFilterUrl,
describeUrl,
outliersUrl,
columnFilterDataUrl,
varianceUrl,
sequentialDiffsUrl,
cleanupEndpoint,
};
7 changes: 4 additions & 3 deletions static/dtale/serverStateManagement.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ function updateSettings(settings, dataId, callback = _.noop) {
}

function renameColumn(dataId, col, rename, callback) {
fetchJson(buildURLString(`/dtale/rename-col/${dataId}/${escape(col)}`, { rename }), callback);
fetchJson(buildURLString(`/dtale/rename-col/${dataId}`, { col, rename }), callback);
}

function updateFormats(dataId, col, format, all, nanDisplay, callback = _.noop) {
Expand All @@ -145,7 +145,8 @@ function updateFormats(dataId, col, format, all, nanDisplay, callback = _.noop)

function editCell(dataId, col, rowIndex, updated, callback) {
fetchJson(
buildURLString(`/dtale/edit-cell/${dataId}/${escape(col)}`, {
buildURLString(`/dtale/edit-cell/${dataId}`, {
col,
rowIndex,
updated,
}),
Expand Down Expand Up @@ -184,7 +185,7 @@ export default {
persistVisibility(dataId, { visibility: JSON.stringify(visibility) }, callback),
toggleVisibility: (dataId, toggle, callback) => persistVisibility(dataId, { toggle }, callback),
updateSettings,
deleteColumn: (dataId, col) => () => fetchJson(buildURLString(`/dtale/delete-col/${dataId}/${escape(col)}`), _.noop),
deleteColumn: (dataId, col) => () => fetchJson(buildURLString(`/dtale/delete-col/${dataId}`, { col }), _.noop),
renameColumn,
updateFormats,
editCell,
Expand Down
8 changes: 4 additions & 4 deletions static/filters/AsyncValueSelect.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import qs from "querystring";

import _ from "lodash";
import PropTypes from "prop-types";
import React from "react";
import AsyncSelect from "react-select/async";

import { buildURLString, columnFilterDataUrl } from "../actions/url-utils";
import { fetchJsonPromise } from "../fetcher";

class AsyncValueSelect extends React.Component {
Expand All @@ -21,9 +20,10 @@ class AsyncValueSelect extends React.Component {

loadOptions(input) {
return fetchJsonPromise(
`/dtale/async-column-filter-data/${this.props.dataId}/${escape(this.props.selectedCol)}?${qs.stringify({
buildURLString(columnFilterDataUrl(this.props.dataId, true), {
col: this.props.selectedCol,
input,
})}`
})
);
}

Expand Down
Loading

0 comments on commit 715ba75

Please sign in to comment.