From c8e73e54df2a9485a6bfb080f9260ce5fe9bea31 Mon Sep 17 00:00:00 2001 From: BSd3v Date: Mon, 23 Jan 2023 17:13:55 -0500 Subject: [PATCH 1/5] More additional functions and security measures: - allowing for strings of functions to be passed as parameters to `valueGetterFunction`, `valueFormatterFunction` - this allows for functions to be parsed even when the app is completely locked down, (meta tags, etc) - added row conditional formatting via `getRowStyle` acts similar to `cellStyles` - added ability for custom parsing functions to be passed via the namespace `window.dashAgGridFunctions` - allowed for `null` to be passed to `columnSize`, to prevent the fit to width or autosize being the only options - fixed props issue for `enableAddRows` --- src/lib/components/AgGrid.react.js | 17 ++++- src/lib/fragments/AgGrid.react.js | 97 +++++++++++++++++++++++----- src/lib/renderers/customFunctions.js | 3 + 3 files changed, 100 insertions(+), 17 deletions(-) create mode 100644 src/lib/renderers/customFunctions.js diff --git a/src/lib/components/AgGrid.react.js b/src/lib/components/AgGrid.react.js index 459b63ef..77624e6a 100644 --- a/src/lib/components/AgGrid.react.js +++ b/src/lib/components/AgGrid.react.js @@ -158,7 +158,7 @@ DashAgGrid.propTypes = { * If true, the internal method addRows() will be called */ enableAddRows: PropTypes.oneOfType([ - PropTypes.bool, PropTypes.Object + PropTypes.bool, PropTypes.arrayOf(PropTypes.any) ]), /** @@ -250,7 +250,7 @@ DashAgGrid.propTypes = { /** * Size the columns automatically or to fit their contents */ - columnSize: PropTypes.oneOf(['sizeToFit', 'autoSizeAll']), + columnSize: PropTypes.oneOf(['sizeToFit', 'autoSizeAll', null]), /** * Use this with Dash Enterprise only. Sets the ag-grid theme. Use ddk for dark themes. @@ -270,6 +270,19 @@ DashAgGrid.propTypes = { defaultStyle: PropTypes.object, }), + /** + * Object used to perform the row styling. See AG-Grid Row Style. + */ + getRowStyle: PropTypes.shape({ + styleConditions: PropTypes.arrayOf( + PropTypes.shape({ + condition: PropTypes.string.isRequired, + style: PropTypes.object.isRequired, + }) + ), + defaultStyle: PropTypes.object, + }), + /** * Infinite Scroll, Datasource interface * See https://www.ag-grid.com/react-grid/infinite-scrolling/#datasource-interface diff --git a/src/lib/fragments/AgGrid.react.js b/src/lib/fragments/AgGrid.react.js index d497bbae..2b833950 100644 --- a/src/lib/fragments/AgGrid.react.js +++ b/src/lib/fragments/AgGrid.react.js @@ -6,6 +6,7 @@ import {propTypes, defaultProps} from '../components/AgGrid.react'; import MarkdownRenderer from '../renderers/markdownRenderer'; import RowMenuRenderer from '../renderers/rowMenuRenderer'; +import * as customFunctions from '../renderers/customFunctions'; import 'ag-grid-community'; import { AgGridReact } from 'ag-grid-react'; @@ -57,10 +58,12 @@ export default class DashAgGrid extends Component { this.onGridSizeChanged = this.onGridSizeChanged.bind(this); this.updateColumnWidths = this.updateColumnWidths.bind(this); this.handleDynamicCellStyle = this.handleDynamicCellStyle.bind(this); + this.handleDynamicRowStyle = this.handleDynamicRowStyle.bind(this); this.generateRenderer = this.generateRenderer.bind(this); this.resetColumnState = this.resetColumnState.bind(this); this.exportDataAsCsv = this.exportDataAsCsv.bind(this); this.setSelection = this.setSelection.bind(this); + this.parseParamFunction = this.parseParamFunction.bind(this); //Additional Exposure this.setUpCols = this.setUpCols.bind(this); @@ -72,6 +75,7 @@ export default class DashAgGrid extends Component { this.deleteSelectedRows = this.deleteSelectedRows.bind(this); this.addRows = this.addRows.bind(this); this.getRowData = this.getRowData.bind(this); + this.fixCols = this.fixCols.bind(this); this.selectionEventFired = false; @@ -93,6 +97,36 @@ export default class DashAgGrid extends Component { } } + fixCols(columnDef, templateMessage) { + const test = (base, target) => { + if (target in columnDef) { + if (!(columnDef['dangerously_allow_html'] + && this.state.dangerously_allow_html)) { + if (typeof columnDef[target] !== 'function') { + console.error({field: columnDef['field'], message: templateMessage}) + columnDef[target] = '' + } + } + } + if (base in columnDef) { + const newFunc = (params) => this.parseParamFunction({params}, columnDef[base]) + columnDef[target] = newFunc + } + } + if ("headerComponentParams" in columnDef) { + if ('template' in columnDef['headerComponentParams'] && !(columnDef['dangerously_allow_html'] + && this.state.dangerously_allow_html)) { + columnDef['headerComponentParams']['template'] = '
' + console.error({field: columnDef['field'], message: templateMessage}) + } + } + + test('valueGetterFunction','valueGetter') + test('valueFormatterFunction','valueFormatter') + + return columnDef + } + setUpCols(cellStyle) { const templateMessage = 'you are trying to use a dangerous element that could lead to XSS' if (this.props.columnDefs) { @@ -100,13 +134,8 @@ export default class DashAgGrid extends Component { {columnDefs: this.props.columnDefs.map((columnDef) => { if ('children' in columnDef) { columnDef['children'] = columnDef['children'].map((child) => { - if ("headerComponentParams" in child) { - if ('template' in child['headerComponentParams'] && !(child['dangerously_allow_html'] - && this.state.dangerously_allow_html)) { - child['headerComponentParams']['template'] = '
' - console.error({field: child['field'], message: templateMessage}) - } - } + child = this.fixCols(child, templateMessage) + if ('cellStyle' in child) { return child } @@ -117,13 +146,9 @@ export default class DashAgGrid extends Component { } }) } - if ("headerComponentParams" in columnDef) { - if ('template' in columnDef['headerComponentParams'] && !(columnDef['dangerously_allow_html'] - && this.state.dangerously_allow_html)) { - columnDef['headerComponentParams']['template'] = '
' - console.error({field: columnDef['field'], message: templateMessage}) - } - } + + columnDef = this.fixCols(columnDef, templateMessage) + if ('cellStyle' in columnDef) { return columnDef } @@ -361,7 +386,7 @@ export default class DashAgGrid extends Component { } /** - * @params AG-Grid Cell Style rules attribute. + * @params AG-Grid Styles rules attribute. * See: https://www.ag-grid.com/react-grid/cell-styles/#cell-style-cell-class--cell-class-rules-params */ handleDynamicCellStyle({params, cellStyle = {}}) { @@ -382,6 +407,41 @@ export default class DashAgGrid extends Component { return defaultStyle ? defaultStyle : null; } + /** + * @params AG-Grid Styles rules attribute. + * See: https://www.ag-grid.com/react-grid/row-styles/#row-style-row-class--row-class-rules-params + */ + handleDynamicRowStyle({params, getRowStyle = {}}) { + const {styleConditions, defaultStyle} = getRowStyle; + + if (styleConditions && styleConditions.length > 0) { + for (const styleCondition of styleConditions) { + const {condition, style} = styleCondition; + const parsedCondition = esprima.parse(condition).body[0] + .expression; + + if (evaluate(parsedCondition, {...params})) { + return style; + } + } + } + + return defaultStyle ? defaultStyle : null; + } + + parseParamFunction({params}, tempFunction) { + try { + const parsedCondition = esprima.parse(tempFunction).body[0] + .expression; + const value = evaluate(parsedCondition, {...params, ...customFunctions, ...window.dashAgGridFunctions}) + return value + } catch (err) { + console.log(err) + } + //const value = evaluate(parsedCondition, {...params}) + return '' + } + generateRenderer(Renderer) { const {setProps} = this.props; @@ -478,6 +538,7 @@ export default class DashAgGrid extends Component { const { id, cellStyle, + getRowStyle, style, theme, className, @@ -505,6 +566,11 @@ export default class DashAgGrid extends Component { } this.setUpCols(cellStyle) + + let newRowStyle; + if (getRowStyle) { + newRowStyle = (params) => this.handleDynamicRowStyle({params, getRowStyle}) + } const cols = []; @@ -585,6 +651,7 @@ export default class DashAgGrid extends Component { > Date: Tue, 24 Jan 2023 12:13:45 -0500 Subject: [PATCH 2/5] Update src/lib/renderers/customFunctions.js Co-authored-by: Alex Johnson --- src/lib/renderers/customFunctions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/renderers/customFunctions.js b/src/lib/renderers/customFunctions.js index d108cca2..6129e4f0 100644 --- a/src/lib/renderers/customFunctions.js +++ b/src/lib/renderers/customFunctions.js @@ -1,3 +1,3 @@ export function Round(v, a=2) { - return Math.round(v * (10*a)) / (10*a) + return Math.round(v * (10**a)) / (10**a) } \ No newline at end of file From ff1282d41f382476cea5b55521309f291345f75a Mon Sep 17 00:00:00 2001 From: BSd3v Date: Tue, 24 Jan 2023 12:17:40 -0500 Subject: [PATCH 3/5] Updating CHANGELOG.md --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16b1c901..42b00a24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,34 @@ All notable changes to `dash-ag-grid` will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). +## [Unreleased] - 2023-01-23 +_More additional functions and security measures_: +- allowing for strings of functions to be passed as parameters to `valueGetterFunction`, `valueFormatterFunction` + - this allows for functions to be parsed even when the app is completely locked down, (meta tags, etc) +- added row conditional formatting via `getRowStyle` acts similar to `cellStyles` +- added ability for custom parsing functions to be passed via the namespace `window.dashAgGridFunctions` +- allowed for `null` to be passed to `columnSize`, to prevent the fit to width or autosize being the only options +- fixed props issue for `enableAddRows` + + +## [Unreleased] - 2023-01-20 +_Major overhaul of dash-ag-grid_: +- bringing ag-grid from version v27.x to v29.x+ +- added secondary `agGridEnterprise.react.js` as additional importing `ag-grid-enterprise` due to all-modules no longer supported +- updating props for breaking changes due to version update +- adding props for easier user / dash manipulation (enable... props ) for creating buttons +- removing `agGridColumns` due to deprecation and removal due to v29 +- added `className` support for css customization native to ag-grid (removed hardcoded styling as well) +- added overarching `dangerously_allow_html` to grid props only provided at render, to keep `columnDefs` from receiving possible updates to show unsafe html +- added `data_previous` and `data_previous_timestamp` to allow for use with user change logs +- added `dashGridOptions` to allow for arbitrary use of props not explicitly listed +- added `setRowId` for allowing `rowData` change detection to work +- added prop `columnState` to allow for pulling the current state of the columns after user interaction, necessary for saving layouts outside of snapshots +- fixed issue where conditional formatting was not applied to nested columns +- fixed issue where columns would not take edits or adjustments due to becoming static +- updated `markdownRenderer.js` to use github markdown, and also have the ability to be passed a target for links, to avoid `dangerously_allow_html` +- updated `requirements.txt` to pull the latest packages + ## [1.3.2] - 2023-01-13 ### Updated From b31f59ea9c26de8a474f487cdda7f7543ad6f1f0 Mon Sep 17 00:00:00 2001 From: BSd3v <82055130+BSd3v@users.noreply.github.com> Date: Tue, 24 Jan 2023 12:43:36 -0500 Subject: [PATCH 4/5] Update src/lib/components/AgGrid.react.js Co-authored-by: Alex Johnson --- src/lib/components/AgGrid.react.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/AgGrid.react.js b/src/lib/components/AgGrid.react.js index 77624e6a..0d315c2f 100644 --- a/src/lib/components/AgGrid.react.js +++ b/src/lib/components/AgGrid.react.js @@ -158,7 +158,7 @@ DashAgGrid.propTypes = { * If true, the internal method addRows() will be called */ enableAddRows: PropTypes.oneOfType([ - PropTypes.bool, PropTypes.arrayOf(PropTypes.any) + PropTypes.bool, PropTypes.arrayOf(PropTypes.object) ]), /** From f20f97445377087e7d3446c38386a94444300371 Mon Sep 17 00:00:00 2001 From: BSd3v Date: Tue, 24 Jan 2023 12:51:18 -0500 Subject: [PATCH 5/5] Making suggested changes with props --- src/lib/components/AgGrid.react.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/components/AgGrid.react.js b/src/lib/components/AgGrid.react.js index 0d315c2f..acec6970 100644 --- a/src/lib/components/AgGrid.react.js +++ b/src/lib/components/AgGrid.react.js @@ -248,7 +248,9 @@ DashAgGrid.propTypes = { }), /** - * Size the columns automatically or to fit their contents + * Size the columns autoSizeAll changes the column sizes to fit the column's content, + * sizeToFit changes the column sizes to fit the width of the table + * and null bypasses the altering of the column widths */ columnSize: PropTypes.oneOf(['sizeToFit', 'autoSizeAll', null]), @@ -361,7 +363,7 @@ DashAgGrid.propTypes = { /** * Data retreived from the server */ - rowData: PropTypes.arrayOf(PropTypes.any), + rowData: PropTypes.arrayOf(PropTypes.object), /** * Current row count, if known