diff --git a/dtale/dash_application/charts.py b/dtale/dash_application/charts.py index 3d0919bf..f4134372 100644 --- a/dtale/dash_application/charts.py +++ b/dtale/dash_application/charts.py @@ -23,7 +23,6 @@ valid_chart, weekday_tick_handler) from dtale.dash_application.layout import (AGGS, ANIMATE_BY_CHARTS, ANIMATION_CHARTS, build_error, - test_plotly_version, update_label_for_freq) from dtale.utils import (build_code_export, classify_type, dict_merge, divide_chunks, export_to_csv_buffer, @@ -1031,8 +1030,6 @@ def map_builder(data_id, export=False, **inputs): code += agg_code geo_layout = {} - if test_plotly_version('4.5.0'): - geo_layout['fitbounds'] = 'locations' if scope is not None: geo_layout['scope'] = scope if proj is not None: diff --git a/dtale/views.py b/dtale/views.py index c580f7ee..b82b6fb9 100644 --- a/dtale/views.py +++ b/dtale/views.py @@ -1036,6 +1036,23 @@ def describe(data_id, column): return jsonify_error(e) +@dtale.route('/delete-col//') +def delete_col(data_id, column): + try: + data = global_state.get_data(data_id) + data = data[[c for c in data.columns if c != column]] + dtypes = global_state.get_dtypes(data_id) + dtypes = [dt for dt in dtypes if dt['name'] != column] + curr_settings = global_state.get_settings(data_id) + curr_settings['locked'] = [c for c in curr_settings.get('locked', []) if c != column] + global_state.set_data(data_id, data) + global_state.set_dtypes(data_id, dtypes) + global_state.set_settings(data_id, curr_settings) + return jsonify(success=True) + except BaseException as e: + return jsonify_error(e) + + @dtale.route('/column-filter-data//') def get_column_filter_data(data_id, column): try: diff --git a/static/__tests__/dtale/DataViewer-base-test.jsx b/static/__tests__/dtale/DataViewer-base-test.jsx index c7f809fb..5427c8ac 100644 --- a/static/__tests__/dtale/DataViewer-base-test.jsx +++ b/static/__tests__/dtale/DataViewer-base-test.jsx @@ -13,33 +13,7 @@ import { buildInnerHTML, clickMainMenuButton, withGlobalJquery } from "../test-u const originalOffsetHeight = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "offsetHeight"); const originalOffsetWidth = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "offsetWidth"); -const COL_PROPS = [ - { - locked: true, - width: 70, - name: "dtale_index", - dtype: "int64", - visible: true, - }, - { locked: false, width: 20, name: "col1", dtype: "int64", visible: true }, - { - locked: false, - width: 20, - name: "col2", - dtype: "float64", - visible: true, - min: 2.5, - max: 5.5, - }, - { locked: false, width: 20, name: "col3", dtype: "object", visible: true }, - { - locked: false, - width: 20, - name: "col4", - dtype: "datetime64[ns]", - visible: true, - }, -]; +const COL_PROPS = _.map(reduxUtils.DATA.columns, (c, i) => _.assignIn({ width: i == 0 ? 70 : 20, locked: i == 0 }, c)); describe("DataViewer tests", () => { beforeAll(() => { diff --git a/static/__tests__/dtale/DataViewer-heatmap-test.jsx b/static/__tests__/dtale/DataViewer-heatmap-test.jsx index a2e758e8..80ca8ec7 100644 --- a/static/__tests__/dtale/DataViewer-heatmap-test.jsx +++ b/static/__tests__/dtale/DataViewer-heatmap-test.jsx @@ -6,7 +6,7 @@ import { Provider } from "react-redux"; import mockPopsicle from "../MockPopsicle"; import * as t from "../jest-assertions"; import reduxUtils from "../redux-test-utils"; -import { buildInnerHTML, clickMainMenuButton, withGlobalJquery } from "../test-utils"; +import { buildInnerHTML, findMainMenuButton, withGlobalJquery } from "../test-utils"; const originalOffsetHeight = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "offsetHeight"); const originalOffsetWidth = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "offsetWidth"); @@ -50,7 +50,11 @@ describe("DataViewer heatmap tests", () => { setTimeout(() => { result.update(); - clickMainMenuButton(result, "Heat Map"); + let heatMapBtn = findMainMenuButton(result, "By Col", "div.btn-group"); + heatMapBtn + .find("button") + .first() + .simulate("click"); result.update(); let dv = result.find(ReactDataViewer).instance().state; t.ok( @@ -67,10 +71,27 @@ describe("DataViewer heatmap tests", () => { .find(ReactDataViewer) .find("div.headerCell") .map(hc => hc.text()), - ["col2"], - "should render float column headers" + ["col1", "col2"], + "should render int/float column headers" ); - clickMainMenuButton(result, "Heat Map"); + heatMapBtn = findMainMenuButton(result, "By Col", "div.btn-group"); + heatMapBtn + .find("button") + .last() + .simulate("click"); + t.deepEqual( + result + .find(ReactDataViewer) + .find("div.headerCell") + .map(hc => hc.text()), + ["col1", "col2"], + "should render int/float column headers" + ); + heatMapBtn = findMainMenuButton(result, "By Col", "div.btn-group"); + heatMapBtn + .find("button") + .last() + .simulate("click"); dv = result.find(ReactDataViewer).instance().state; t.ok(_.filter(dv.columns, { visible: true }).length, 5, "should turn all columns back on"); t.ok( diff --git a/static/__tests__/iframe/DataViewer-base-test.jsx b/static/__tests__/iframe/DataViewer-base-test.jsx index 7f4ec0a8..24840ac2 100644 --- a/static/__tests__/iframe/DataViewer-base-test.jsx +++ b/static/__tests__/iframe/DataViewer-base-test.jsx @@ -15,33 +15,7 @@ import { clickColMenuButton, clickColMenuSubButton } from "./iframe-utils"; const originalOffsetHeight = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "offsetHeight"); const originalOffsetWidth = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "offsetWidth"); -const COL_PROPS = [ - { - locked: true, - width: 70, - name: "dtale_index", - dtype: "int64", - visible: true, - }, - { locked: false, width: 20, name: "col1", dtype: "int64", visible: true }, - { - locked: false, - width: 20, - name: "col2", - dtype: "float64", - visible: true, - min: 2.5, - max: 5.5, - }, - { locked: false, width: 20, name: "col3", dtype: "object", visible: true }, - { - locked: false, - width: 20, - name: "col4", - dtype: "datetime64[ns]", - visible: true, - }, -]; +const COL_PROPS = _.map(reduxUtils.DATA.columns, (c, i) => _.assignIn({ width: i == 0 ? 70 : 20, locked: i == 0 }, c)); class MockDateInput extends React.Component { render() { @@ -178,7 +152,7 @@ describe("DataViewer iframe tests", () => { ); t.deepEqual( colMenu.find("ul li span.font-weight-bold").map(s => s.text()), - ["Lock", "Hide", "Describe", "Column Analysis", "Formats"], + ["Lock", "Hide", "Delete", "Describe", "Column Analysis", "Formats"], "Should render column menu options" ); clickColMenuSubButton(result, "Asc"); diff --git a/static/__tests__/iframe/DataViewer-delete-test.jsx b/static/__tests__/iframe/DataViewer-delete-test.jsx new file mode 100644 index 00000000..72398395 --- /dev/null +++ b/static/__tests__/iframe/DataViewer-delete-test.jsx @@ -0,0 +1,86 @@ +import { mount } from "enzyme"; +import $ from "jquery"; +import React from "react"; +import { Provider } from "react-redux"; + +import mockPopsicle from "../MockPopsicle"; +import * as t from "../jest-assertions"; +import reduxUtils from "../redux-test-utils"; +import { buildInnerHTML, withGlobalJquery } from "../test-utils"; +import { clickColMenuButton } from "./iframe-utils"; + +const originalOffsetHeight = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "offsetHeight"); +const originalOffsetWidth = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "offsetWidth"); + +describe("DataViewer iframe tests", () => { + const { post } = $; + + beforeAll(() => { + Object.defineProperty(HTMLElement.prototype, "offsetHeight", { + configurable: true, + value: 500, + }); + Object.defineProperty(HTMLElement.prototype, "offsetWidth", { + configurable: true, + value: 500, + }); + + const mockBuildLibs = withGlobalJquery(() => + mockPopsicle.mock(url => { + const { urlFetcher } = require("../redux-test-utils").default; + return urlFetcher(url); + }) + ); + + const mockChartUtils = withGlobalJquery(() => (ctx, cfg) => { + const chartCfg = { ctx, cfg, data: cfg.data, destroyed: false }; + chartCfg.destroy = () => (chartCfg.destroyed = true); + chartCfg.getElementsAtXAxis = _evt => [{ _index: 0 }]; + return chartCfg; + }); + + jest.mock("popsicle", () => mockBuildLibs); + jest.mock("chart.js", () => mockChartUtils); + jest.mock("chartjs-plugin-zoom", () => ({})); + jest.mock("chartjs-chart-box-and-violin-plot/build/Chart.BoxPlot.js", () => ({})); + }); + + afterAll(() => { + Object.defineProperty(HTMLElement.prototype, "offsetHeight", originalOffsetHeight); + Object.defineProperty(HTMLElement.prototype, "offsetWidth", originalOffsetWidth); + $.post = post; + }); + + test("DataViewer: hiding a column", done => { + const { DataViewer } = require("../../dtale/DataViewer"); + + const store = reduxUtils.createDtaleStore(); + buildInnerHTML({ settings: "", iframe: "True" }, store); + const result = mount( + + + , + { + attachTo: document.getElementById("content"), + } + ); + + setTimeout(() => { + result.update(); + result + .find(".main-grid div.headerCell div") + .last() + .simulate("click"); + clickColMenuButton(result, "Delete"); + setTimeout(() => { + result.update(); + t.deepEqual( + result.find(".main-grid div.headerCell").map(hc => hc.text()), + ["col1", "col2", "col3"], + "should render column headers" + ); + done(); + }, 400); + }, 600); + }); +}); diff --git a/static/__tests__/popups/Instances-test.jsx b/static/__tests__/popups/Instances-test.jsx index 18202068..0b94efeb 100644 --- a/static/__tests__/popups/Instances-test.jsx +++ b/static/__tests__/popups/Instances-test.jsx @@ -199,7 +199,7 @@ describe("Instances tests", () => { assignSpy.mockRestore(); global.window = origWindow; result - .find(".ico-remove-circle") + .find(".ico-delete") .first() .simulate("click"); setTimeout(() => { diff --git a/static/__tests__/redux-test-utils.jsx b/static/__tests__/redux-test-utils.jsx index 131b95ec..5268a200 100644 --- a/static/__tests__/redux-test-utils.jsx +++ b/static/__tests__/redux-test-utils.jsx @@ -22,7 +22,7 @@ const DATA = { ], columns: [ { name: "dtale_index", dtype: "int64", visible: true }, - { name: "col1", dtype: "int64", visible: true }, + { name: "col1", dtype: "int64", min: 2, max: 5, visible: true }, { name: "col2", dtype: "float64", min: 2.5, max: 5.5, visible: true }, { name: "col3", dtype: "object", visible: true }, { name: "col4", dtype: "datetime64[ns]", visible: true }, @@ -179,7 +179,10 @@ function urlFetcher(url) { return chartsData; } else if ( _.find( - ["/dtale/update-visibility", "/dtale/update-settings", "/dtale/update-locked", "/dtale/update-column-position"], + _.concat( + ["/dtale/update-visibility", "/dtale/update-settings", "/dtale/update-locked", "/dtale/update-column-position"], + ["/dtale/delete-col"] + ), prefix => _.startsWith(url, prefix) ) ) { @@ -235,4 +238,5 @@ function createDtaleStore() { export default { urlFetcher, createDtaleStore, + DATA, }; diff --git a/static/dtale/DataViewer.jsx b/static/dtale/DataViewer.jsx index 035d0844..dea6e41e 100644 --- a/static/dtale/DataViewer.jsx +++ b/static/dtale/DataViewer.jsx @@ -19,7 +19,6 @@ import { Header } from "./Header"; import { MeasureText } from "./MeasureText"; import * as gu from "./gridUtils"; import { ColumnMenu } from "./iframe/ColumnMenu"; -import {getActiveCols} from "./gridUtils"; require("./DataViewer.css"); const URL_PROPS = ["ids", "sortInfo"]; diff --git a/static/dtale/DataViewerMenu.jsx b/static/dtale/DataViewerMenu.jsx index 9334bbe1..dd6d0757 100644 --- a/static/dtale/DataViewerMenu.jsx +++ b/static/dtale/DataViewerMenu.jsx @@ -100,14 +100,14 @@ class ReactDataViewerMenu extends React.Component {
{Descriptions.charts}
-
  • +
  • - + {"Heat Map"} -
    +
    {_.map( [ ["By Col", "col"], @@ -119,7 +119,7 @@ class ReactDataViewerMenu extends React.Component { style={{ color: "#565b68" }} className="btn btn-primary font-weight-bold" onClick={toggleHeatMap(mode)}> - {mode === this.props.heatMapMode && ({label})} + {mode === this.props.heatMapMode && {label}} {mode !== this.props.heatMapMode && label} ) @@ -132,9 +132,7 @@ class ReactDataViewerMenu extends React.Component { @@ -142,7 +140,7 @@ class ReactDataViewerMenu extends React.Component {
  • -
  • -
  • +
  • diff --git a/static/dtale/iframe/ColumnMenu.jsx b/static/dtale/iframe/ColumnMenu.jsx index 479218a8..7f00f0dd 100644 --- a/static/dtale/iframe/ColumnMenu.jsx +++ b/static/dtale/iframe/ColumnMenu.jsx @@ -125,6 +125,11 @@ class ReactColumnMenu extends React.Component { }; serverState.toggleVisibility(dataId, selectedCol, hideCallback); }; + const deleteCol = () => + this.props.propagateState( + { columns: _.reject(this.props.columns, { name: selectedCol }) }, + serverState.deleteColumn(dataId, selectedCol) + ); return (
  • +
  • + + + +