From 99b522b32de21d1fe47928028b3ced6ed3658cf0 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 27 Jun 2024 14:23:05 +0100 Subject: [PATCH] Optimise condition evaluation performance and add support for conditionally setting text color --- .../controls/CellConditionEditor.svelte | 24 +++- .../src/components/grid/cells/GridCell.svelte | 9 +- .../src/components/grid/layout/Grid.svelte | 1 + .../grid/layout/StickyColumn.svelte | 2 +- .../src/components/grid/stores/conditions.js | 125 ++++++++++++++++++ .../src/components/grid/stores/index.js | 2 + .../src/components/grid/stores/rows.js | 78 +---------- .../src/components/grid/stores/viewport.js | 29 +++- 8 files changed, 185 insertions(+), 85 deletions(-) create mode 100644 packages/frontend-core/src/components/grid/stores/conditions.js diff --git a/packages/builder/src/components/design/settings/controls/CellConditionEditor.svelte b/packages/builder/src/components/design/settings/controls/CellConditionEditor.svelte index eb7e70f887..5f19739f9d 100644 --- a/packages/builder/src/components/design/settings/controls/CellConditionEditor.svelte +++ b/packages/builder/src/components/design/settings/controls/CellConditionEditor.svelte @@ -24,6 +24,16 @@ const dispatch = createEventDispatcher() const flipDuration = 130 + const conditionOptions = [ + { + label: "Update background color", + value: "backgroundColor", + }, + { + label: "Update text color", + value: "textColor", + }, + ] let tempValue = [] let drawer @@ -57,6 +67,7 @@ const addCondition = () => { const condition = { id: generate(), + metadataKey: conditionOptions[0].value, operator: Constants.OperatorOptions.Equals.value, valueType: FieldType.STRING, } @@ -132,10 +143,15 @@ > - Set background color to + { + const conditionMetadata = writable({}) + return { + conditionMetadata, + } +} + +export const deriveStores = context => { + const { columns } = context + + // Derive and memoize the conditions present in our columns so that we only + // recompute condition metdata when absolutely necessary + const conditions = derivedMemo(columns, $columns => { + let newConditions = [] + for (let column of $columns) { + for (let condition of column.conditions || []) { + newConditions.push({ + ...condition, + column: column.name, + type: column.schema.type, + }) + } + } + return newConditions + }) + + return { + conditions, + } +} + +export const initialise = context => { + const { conditionMetadata, conditions, rows } = context + + // Evaluates an array of conditions against a certain row and returns the + // resultant metadata + const evaluateConditions = (row, conditions) => { + let metadata = { version: row._rev } + for (let condition of conditions) { + try { + let { + column, + type, + referenceValue, + operator, + metadataKey, + metadataValue, + } = condition + let value = row[column] + + // Coerce values into correct types for primitives + if (type === "number") { + referenceValue = parseFloat(referenceValue) + value = parseFloat(value) + } else if (type === "datetime") { + if (referenceValue) { + referenceValue = new Date(referenceValue).toISOString() + } + if (value) { + value = new Date(value).toISOString() + } + } else if (type === "boolean") { + referenceValue = `${referenceValue}`.toLowerCase() === "true" + value = `${value}`.toLowerCase() === "true" + } + + // Build lucene compatible condition expression + const luceneFilter = { + operator, + type, + field: "value", + value: referenceValue, + } + const query = QueryUtils.buildQuery([luceneFilter]) + const result = QueryUtils.runQuery([{ value }], query) + if (result.length > 0) { + if (!metadata[column]) { + metadata[column] = {} + } + metadata[column][metadataKey] = metadataValue + } + } catch { + // Swallow + } + } + return metadata + } + + // Recompute all metadata if conditions change + conditions.subscribe($conditions => { + console.log("recomputing all conditions") + let metadata = {} + if ($conditions.length) { + for (let row of get(rows)) { + metadata[row._id] = evaluateConditions(row, $conditions) + } + } + conditionMetadata.set(metadata) + }) + + // Recompute specific rows when they change + rows.subscribe($rows => { + const $conditions = get(conditions) + if (!$conditions.length) { + return + } + const metadata = get(conditionMetadata) + let updates = {} + for (let row of $rows) { + if (!row._rev || metadata[row._id]?.version !== row._rev) { + console.log("recompute row", row._id) + updates[row._id] = evaluateConditions(row, $conditions) + } + } + if (Object.keys(updates).length) { + conditionMetadata.update(state => ({ + ...state, + ...updates, + })) + } + }) +} diff --git a/packages/frontend-core/src/components/grid/stores/index.js b/packages/frontend-core/src/components/grid/stores/index.js index f086f0a51b..6671cc729c 100644 --- a/packages/frontend-core/src/components/grid/stores/index.js +++ b/packages/frontend-core/src/components/grid/stores/index.js @@ -20,6 +20,7 @@ import * as Table from "./datasources/table" import * as ViewV2 from "./datasources/viewV2" import * as NonPlus from "./datasources/nonPlus" import * as Cache from "./cache" +import * as Conditions from "./conditions" const DependencyOrderedStores = [ Sort, @@ -33,6 +34,7 @@ const DependencyOrderedStores = [ Scroll, Validation, Rows, + Conditions, UI, Resize, Viewport, diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index 1c6ea68c2b..fdd91d2270 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -5,56 +5,6 @@ import { getCellID, parseCellID } from "../lib/utils" import { tick } from "svelte" import { Helpers } from "@budibase/bbui" import { sleep } from "../../../utils/utils" -import { QueryUtils } from "../../../utils" - -const evaluateConditions = (row, column) => { - if (!column.conditions?.length) { - return - } - for (let condition of column.conditions) { - try { - const type = column.schema.type - let value = row[column.name] - let referenceValue = condition.referenceValue - - // Coerce values into correct types for primitives - if (type === "number") { - referenceValue = parseFloat(referenceValue) - value = parseFloat(value) - } else if (type === "datetime") { - if (referenceValue) { - referenceValue = new Date(referenceValue).toISOString() - } - if (value) { - value = new Date(value).toISOString() - } - } else if (type === "boolean") { - referenceValue = `${referenceValue}`.toLowerCase() === "true" - value = `${value}`.toLowerCase() === "true" - } - - // Build lucene compatible condition expression - const luceneFilter = { - operator: condition.operator, - type, - field: "value", - value: referenceValue, - } - const query = QueryUtils.buildQuery([luceneFilter]) - const result = QueryUtils.runQuery([{ value }], query) - if (result.length > 0) { - if (!row.__metadata) { - row.__metadata = {} - } - row.__metadata[column.name] = { - background: condition.color, - } - } - } catch { - // Swallow - } - } -} export const createStores = () => { const rows = writable([]) @@ -91,29 +41,15 @@ export const createStores = () => { } export const deriveStores = context => { - const { rows, columns, rowChangeCache } = context + const { rows } = context // Enrich rows with an index property and any pending changes - const enrichedRows = derived( - [rows, rowChangeCache, columns], - ([$rows, $rowChangeCache, $columns]) => { - if (!$rows?.length || !$columns?.length) { - return [] - } - console.log("ENRICH ROWS", $rows, $rowChangeCache, $columns) - return $rows.map((row, idx) => { - let enriched = { - ...row, - ...$rowChangeCache[row._id], - __idx: idx, - } - for (let column of $columns) { - evaluateConditions(enriched, column) - } - return enriched - }) - } - ) + const enrichedRows = derived(rows, $rows => { + return $rows.map((row, idx) => ({ + ...row, + __idx: idx, + })) + }) // Generate a lookup map to quick find a row by ID const rowLookupMap = derived(enrichedRows, $enrichedRows => { diff --git a/packages/frontend-core/src/components/grid/stores/viewport.js b/packages/frontend-core/src/components/grid/stores/viewport.js index a5079d3c11..93eb7df0a2 100644 --- a/packages/frontend-core/src/components/grid/stores/viewport.js +++ b/packages/frontend-core/src/components/grid/stores/viewport.js @@ -10,6 +10,8 @@ export const deriveStores = context => { scrollLeft, width, height, + rowChangeCache, + conditionMetadata, } = context // Derive visible rows @@ -30,12 +32,27 @@ export const deriveStores = context => { 0 ) const renderedRows = derived( - [rows, scrolledRowCount, visualRowCapacity], - ([$rows, $scrolledRowCount, $visualRowCapacity]) => { - return $rows.slice( - $scrolledRowCount, - $scrolledRowCount + $visualRowCapacity - ) + [ + rows, + scrolledRowCount, + visualRowCapacity, + rowChangeCache, + conditionMetadata, + ], + ([ + $rows, + $scrolledRowCount, + $visualRowCapacity, + $rowChangeCache, + $conditionMetadata, + ]) => { + return $rows + .slice($scrolledRowCount, $scrolledRowCount + $visualRowCapacity) + .map(row => ({ + ...row, + ...$rowChangeCache[row._id], + __metadata: $conditionMetadata[row._id], + })) }, [] )