Skip to content

Commit

Permalink
#726: added option to turn off header editor
Browse files Browse the repository at this point in the history
  • Loading branch information
aschonfeld committed Feb 2, 2023
1 parent a6c3aa7 commit 8e773a2
Show file tree
Hide file tree
Showing 29 changed files with 215 additions and 12 deletions.
1 change: 1 addition & 0 deletions dtale/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
ALLOW_CELL_EDITS = True
HIDE_SHUTDOWN = False
GITHUB_FORK = False
HIDE_HEADER_EDITOR = False

# flake8: NOQA
from dtale.app import show, get_instance, instances, offline_chart # isort:skip
Expand Down
5 changes: 3 additions & 2 deletions dtale/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -681,8 +681,8 @@ def show(data=None, data_loader=None, name=None, context_vars=None, **options):
:type github_fork: bool, optional
:param hide_drop_rows: If true, this will hide the "Drop Rows" button from users
:type hide_drop_rows: bool, optional
:param hide_shutdown: If true, this will hide the "Shutdown" buton from users
:type hide_shutdown: bool, optional
:param hide_header_editor: If true, this will hide the header editor when editing cells
:type hide_header_editor: bool, optional
:param column_edit_options: The options to allow on the front-end when editing a cell for the columns specified
:type column_edit_options: dict, optional
:param auto_hide_empty_columns: if True, then auto-hide any columns on the front-end that are comprised entirely of
Expand Down Expand Up @@ -770,6 +770,7 @@ def show(data=None, data_loader=None, name=None, context_vars=None, **options):
column_edit_options=final_options.get("column_edit_options"),
auto_hide_empty_columns=final_options.get("auto_hide_empty_columns"),
highlight_filter=final_options.get("highlight_filter"),
hide_header_editor=final_options.get("hide_header_editor"),
)
instance.started_with_open_browser = final_options["open_browser"]
is_active = not running_with_flask_debug() and is_up(app_url)
Expand Down
5 changes: 5 additions & 0 deletions dtale/cli/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ def validate_allow_cell_edits(ctx, param, value):
"--locked",
help="Comma-separated string of column names you would like locked on the left-hand side of your grid on load",
)
@click.option(
"--hide-header-editor",
is_flag=True,
help="flag to hide header editor when editing cells",
)
@setup_loader_options()
@click.option("--log", "logfile", help="Log file name")
@click.option(
Expand Down
9 changes: 9 additions & 0 deletions dtale/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ def load_app_settings(config):
query_engine = get_config_val(
config, curr_app_settings, "query_engine", section="app"
)
hide_header_editor = get_config_val(
config,
curr_app_settings,
"hide_header_editor",
section="app",
getter="getboolean",
)
open_custom_filter_on_startup = get_config_val(
config,
curr_app_settings,
Expand Down Expand Up @@ -116,6 +123,7 @@ def load_app_settings(config):
open_custom_filter_on_startup=open_custom_filter_on_startup,
open_predefined_filters_on_startup=open_predefined_filters_on_startup,
hide_drop_rows=hide_drop_rows,
hide_header_editor=hide_header_editor,
)
)

Expand Down Expand Up @@ -185,6 +193,7 @@ def build_show_options(options=None):
column_edit_options=None,
auto_hide_empty_columns=False,
highlight_filter=False,
hide_header_editor=False,
)
config_options = {}
config = get_config()
Expand Down
1 change: 1 addition & 0 deletions dtale/global_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"open_custom_filter_on_startup": False,
"open_predefined_filters_on_startup": False,
"hide_drop_rows": False,
"hide_header_editor": False,
}

AUTH_SETTINGS = {
Expand Down
1 change: 1 addition & 0 deletions dtale/templates/dtale/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<input type="hidden" id="settings" value="{{settings}}" />
<input type="hidden" id="version" value="{{version}}" />
<input type="hidden" id="hide_shutdown" value="{{hide_shutdown}}" />
<input type="hidden" id="hide_header_editor" value="{{hide_header_editor}}" />
<input type="hidden" id="allow_cell_edits" value="{{allow_cell_edits}}" />
<input type="hidden" id="hide_drop_rows" value="{{hide_drop_rows}}" />
<input type="hidden" id="is_vscode" value="{{is_vscode}}" />
Expand Down
1 change: 1 addition & 0 deletions dtale/templates/dtale/html_export.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<input type="hidden" id="settings" value="{{settings}}" />
<input type="hidden" id="version" value="{{version}}" />
<input type="hidden" id="hide_shutdown" value="{{hide_shutdown}}" />
<input type="hidden" id="hide_header_editor" value="{{hide_header_editor}}" />
<input type="hidden" id="allow_cell_edits" value="{{allow_cell_edits}}" />
<input type="hidden" id="hide_drop_rows" value="{{hide_drop_rows}}" />
<input type="hidden" id="is_vscode" value="{{is_vscode}}" />
Expand Down
7 changes: 6 additions & 1 deletion dtale/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,9 @@ def update_settings(self, **updates):
* column_edit_options - the options to allow on the front-end when editing a cell for the columns specified
* highlight_filter - if True, then highlight rows on the frontend which will be filtered when applying a filter
rather than hiding them from the dataframe
* hide_shutdown - if true, this will hide the "Shutdown" buton from users
* hide_shutdown - if true, this will hide the "Shutdown" button from users
* nan_display - if value in dataframe is :attr:`numpy:numpy.nan` then return this value on the frontend
* hide_header_editor - if true, this will hide header editor when editing cells on the frontend
After applying please refresh any open browsers!
"""
Expand Down Expand Up @@ -952,6 +953,7 @@ def startup(
column_edit_options=None,
auto_hide_empty_columns=False,
highlight_filter=False,
hide_header_editor=False,
):
"""
Loads and stores data globally
Expand Down Expand Up @@ -1073,6 +1075,7 @@ def startup(
column_edit_options=column_edit_options,
auto_hide_empty_columns=auto_hide_empty_columns,
highlight_filter=highlight_filter,
hide_header_editor=hide_header_editor,
)

global_state.set_dataset(instance._data_id, data)
Expand Down Expand Up @@ -1180,9 +1183,11 @@ def base_render_template(template, data_id, **kwargs):
hide_shutdown = global_state.load_flag(data_id, "hide_shutdown", False)
allow_cell_edits = global_state.load_flag(data_id, "allow_cell_edits", True)
github_fork = global_state.load_flag(data_id, "github_fork", False)
hide_header_editor = global_state.load_flag(data_id, "hide_header_editor", False)
app_overrides = dict(
allow_cell_edits=allow_cell_edits,
hide_shutdown=hide_shutdown,
hide_header_editor=hide_header_editor,
github_fork=github_fork,
)
return render_template(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,4 +191,12 @@ describe('DataViewerInfo tests', () => {
]);
expect(loadFilterDataSpy).not.toHaveBeenCalled();
});

it('hides cell editor when "hide_header_editor" is true', async () => {
const loadFilterDataSpy = jest.spyOn(ColumnFilterRepository, 'loadFilterData');
loadFilterDataSpy.mockResolvedValue({ success: true, hasMissing: false, uniques: ['a', 'b', 'c'] });
await buildInfo('2|1', { column_edit_options: { baz: ['foo', 'bar', 'bizzle'] }, hide_header_editor: true });
expect(result.getElementsByClassName('edited-cell-info')).toHaveLength(0);
expect(loadFilterDataSpy).not.toHaveBeenCalled();
});
});
2 changes: 2 additions & 0 deletions frontend/static/__tests__/dtale/info/DataViewerInfo-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ describe('DataViewerInfo tests', () => {
precision: 2,
verticalHeaders: false,
predefinedFilters: {},
hide_header_editor: false,
});
const filterMenuToggle = result.querySelector('div.filter-menu-toggle')!;
const filterLink = filterMenuToggle.querySelector('span.pointer')!;
Expand Down Expand Up @@ -123,6 +124,7 @@ describe('DataViewerInfo tests', () => {
precision: 2,
verticalHeaders: false,
predefinedFilters: {},
hide_header_editor: false,
});
const sortMenuToggle = result.querySelector('div.sort-menu-toggle')!;
const sortLink = sortMenuToggle.querySelector('span.pointer')!;
Expand Down
66 changes: 66 additions & 0 deletions frontend/static/__tests__/dtale/menu/HideHeaderEditor-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { act, fireEvent, render } from '@testing-library/react';
import * as React from 'react';
import { Provider } from 'react-redux';
import { Store } from 'redux';

import HideHeaderEditor from '../../../dtale/menu/HideHeaderEditor';
import * as serverState from '../../../dtale/serverStateManagement';
import reduxUtils from '../../redux-test-utils';
import { buildInnerHTML } from '../../test-utils';

describe('HideHeaderEditor tests', () => {
let result: Element;
let store: Store;
let updateSettingsSpy: jest.SpyInstance;

const setupOption = async (settings = ''): Promise<void> => {
store = reduxUtils.createDtaleStore();
buildInnerHTML({ settings }, store);
result = await act(() => {
return render(
<Provider store={store}>
<HideHeaderEditor />,
</Provider>,
{
container: document.getElementById('content') ?? undefined,
},
).container;
});
};

beforeEach(() => {
updateSettingsSpy = jest.spyOn(serverState, 'updateSettings');
updateSettingsSpy.mockImplementation(() => undefined);
});

afterEach(jest.resetAllMocks);

afterAll(jest.restoreAllMocks);

it('renders successfully with defaults', async () => {
await setupOption();
expect(result.getElementsByClassName('ico-check-box-outline-blank')).toHaveLength(1);
});

it('renders successfully with specified value', async () => {
await setupOption('{&quot;hide_header_editor&quot;:&quot;True&quot;}');
expect(result.getElementsByClassName('ico-check-box')).toHaveLength(1);
});

it('handles changes to checkbox', async () => {
await setupOption();
await act(async () => {
await fireEvent.click(result.getElementsByClassName('ico-check-box-outline-blank')[0]);
});
expect(updateSettingsSpy).toBeCalledTimes(1);
expect(store.getState().settings).toEqual(expect.objectContaining({ hide_header_editor: true }));
await act(async () => {
await fireEvent.click(result.getElementsByClassName('ico-check-box')[0]);
});
expect(updateSettingsSpy).toBeCalledTimes(2);
expect(updateSettingsSpy.mock.calls[1][0]).toEqual({
hide_header_editor: false,
});
expect(store.getState().settings).toEqual(expect.objectContaining({ hide_header_editor: false }));
});
});
1 change: 1 addition & 0 deletions frontend/static/__tests__/dtale/reduxGridUtils-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe('reduxGridUtils', () => {
precision: 2,
verticalHeaders: false,
predefinedFilters: {},
hide_header_editor: false,
};
reduxUtils.handleReduxState(
columns,
Expand Down
2 changes: 2 additions & 0 deletions frontend/static/__tests__/reducers/dtale-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ describe('reducer tests', () => {
const state = {
chartData: { visible: false, type: PopupType.HIDDEN },
hideShutdown: false,
hideHeaderEditor: false,
hideDropRows: false,
iframe: false,
columnMenuOpen: false,
Expand All @@ -42,6 +43,7 @@ describe('reducer tests', () => {
precision: 2,
predefinedFilters: {},
verticalHeaders: false,
hide_header_editor: false,
},
pythonVersion: null,
isPreview: false,
Expand Down
1 change: 1 addition & 0 deletions frontend/static/__tests__/test-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export const buildInnerHTML = (props: Record<string, string | undefined> = {}, s
buildHidden('main_title', props.mainTitle ?? ''),
buildHidden('main_title_font', props.mainTitleFont ?? ''),
buildHidden('query_engine', props.queryEngine ?? 'python'),
buildHidden('hide_header_editor', props.hideHeaderEditor ?? HIDE_SHUTDOWN),
BASE_HTML,
].join('');
store?.dispatch(actions.init());
Expand Down
7 changes: 6 additions & 1 deletion frontend/static/dtale/edited/EditedCellInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ const EditedCellInfo: React.FC<EditedCellInfoProps & WithTranslation> = ({
rowCount,
t,
}) => {
const { dataId, editedCell, settings, maxColumnWidth } = useSelector((state: AppState) => ({
const { dataId, editedCell, settings, maxColumnWidth, hideHeaderEditor } = useSelector((state: AppState) => ({
dataId: state.dataId,
editedCell: state.editedCell,
settings: state.settings,
maxColumnWidth: state.maxColumnWidth,
hideHeaderEditor: state.settings?.hide_header_editor ?? state.hideHeaderEditor,
}));
const dispatch = useDispatch();
const openChart = (chartData: Popups): OpenChartAction => dispatch(chartActions.openChart(chartData));
Expand Down Expand Up @@ -159,6 +160,10 @@ const EditedCellInfo: React.FC<EditedCellInfoProps & WithTranslation> = ({
);
};

if (hideHeaderEditor) {
return null;
}

return (
<div className={`row edited-cell-info${editedCell ? ' is-expanded' : ''}`}>
{cell && (
Expand Down
1 change: 1 addition & 0 deletions frontend/static/dtale/export/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ store.dispatch(actions.init());
actions.loadBackgroundMode(store);
actions.loadHideShutdown(store);
actions.loadAllowCellEdits(store);
actions.loadHideHeaderEditor(store);
const root = ReactDOMClient.createRoot(document.getElementById('content')!);
root.render(
<Provider store={store}>
Expand Down
46 changes: 46 additions & 0 deletions frontend/static/dtale/menu/HideHeaderEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as React from 'react';
import { withTranslation, WithTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { AnyAction } from 'redux';

import { ActionType } from '../../redux/actions/AppActions';
import * as settingsActions from '../../redux/actions/settings';
import { AppState } from '../../redux/state/AppState';
import * as serverState from '../serverStateManagement';

import { MenuItem } from './MenuItem';

const HideHeaderEditor: React.FC<WithTranslation> = ({ t }) => {
const { dataId, hideHeaderEditor } = useSelector((state: AppState) => ({
dataId: state.dataId,
hideHeaderEditor: state.settings?.hide_header_editor ?? state.hideHeaderEditor,
}));
const dispatch = useDispatch();

const setHideHeaderEditor = async (): Promise<void> => {
const updates = { hide_header_editor: !hideHeaderEditor };
await serverState.updateSettings(updates, dataId);
dispatch(settingsActions.updateSettings(updates) as any as AnyAction);
dispatch({ type: ActionType.UPDATE_HIDE_HEADER_EDITOR, value: !hideHeaderEditor });
dispatch({ type: ActionType.HIDE_RIBBON_MENU });
};

return (
<MenuItem
className="hoverable"
description={t('menu_description:hide_header_editor')}
onClick={setHideHeaderEditor}
>
<span className="toggler-action">
<button className="btn btn-plain">
<i className={`ico-check-box${hideHeaderEditor ? '' : '-outline-blank'}`} style={{ marginTop: '-.25em' }} />
<span className="font-weight-bold" style={{ fontSize: '95%' }}>
{t('Hide Header Editor', { ns: 'menu' })}
</span>
</button>
</span>
</MenuItem>
);
};

export default withTranslation(['menu', 'menu_description'])(HideHeaderEditor);
2 changes: 2 additions & 0 deletions frontend/static/dtale/ribbon/RibbonDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import ExportOption from '../menu/ExportOption';
import FilterOption from '../menu/FilterOption';
import GageRnROption from '../menu/GageRnROption';
import HeatMapOption from '../menu/HeatMapOption';
import HideHeaderEditor from '../menu/HideHeaderEditor';
import HighlightOption from '../menu/HighlightOption';
import InstancesOption from '../menu/InstancesOption';
import LanguageOption from '../menu/LanguageOption';
Expand Down Expand Up @@ -257,6 +258,7 @@ const RibbonDropdown: React.FC<RibbonDropdownProps & WithTranslation> = ({ colum
<MaxHeightOption />
<ShowNonNumericHeatmapColumns />
<VerticalColumnHeaders />
<HideHeaderEditor />
</ul>
)}
</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/static/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ let storeBuilder: () => Store = () => {
actions.loadBackgroundMode(store);
actions.loadHideShutdown(store);
actions.loadAllowCellEdits(store);
actions.loadHideHeaderEditor(store);
return store;
};
if (pathname.indexOf('/dtale/popup') === 0) {
Expand Down Expand Up @@ -146,7 +147,6 @@ if (pathname.indexOf('/dtale/popup') === 0) {
root.render(body);
} else {
const store = storeBuilder();
store.dispatch(actions.init());
if (store.getState().openPredefinedFiltersOnStartup) {
store.dispatch(actions.openPredefinedFilters());
} else if (store.getState().openCustomFilterOnStartup) {
Expand Down
Loading

0 comments on commit 8e773a2

Please sign in to comment.