Optimise condition evaluation performance and add support for conditionally setting text color

This commit is contained in:
Andrew Kingston 2024-06-27 14:23:05 +01:00
parent c9bcda0bd5
commit 99b522b32d
No known key found for this signature in database
8 changed files with 185 additions and 85 deletions

View File

@ -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 @@
>
<Icon name="DragHandle" size="XL" />
</div>
<span>Set background color to</span>
<Select
placeholder={null}
options={conditionOptions}
bind:value={condition.metadataKey}
/>
<span>to</span>
<ColorPicker
value={condition.color}
on:change={e => (condition.color = e.detail)}
value={condition.metadataValue}
on:change={e => (condition.metadataValue = e.detail)}
/>
<span>if value</span>
<Select
@ -197,7 +213,7 @@
}
.condition {
display: grid;
grid-template-columns: auto auto auto auto 1fr 1fr 1fr auto auto;
grid-template-columns: auto 1fr auto auto auto 1fr 1fr 1fr auto auto;
align-items: center;
grid-column-gap: var(--spacing-l);
}

View File

@ -20,8 +20,11 @@
if (selectedUser) {
style += `--user-color :${selectedUser.color};`
}
if (metadata?.background) {
style += `--cell-background: ${metadata.background};`
if (metadata?.backgroundColor) {
style += `--cell-background: ${metadata.backgroundColor};`
}
if (metadata?.textColor) {
style += `--cell-font-color: ${metadata.textColor};`
}
return style
}
@ -76,7 +79,7 @@
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
color: var(--spectrum-global-color-gray-800);
color: var(--cell-font-color);
font-size: var(--cell-font-size);
gap: var(--cell-spacing);
background: var(--cell-background);

View File

@ -231,6 +231,7 @@
--cell-spacing: 4px;
--cell-border: 1px solid var(--spectrum-global-color-gray-200);
--cell-font-size: 14px;
--cell-font-color: var(--spectrum-global-color-gray-800);
flex: 1 1 auto;
display: flex;
flex-direction: column;

View File

@ -166,7 +166,7 @@
/* Don't show borders between cells in the sticky column */
.sticky-column :global(.cell:not(:last-child)) {
border-right: none;
border-right-color: transparent;
}
.header {

View File

@ -0,0 +1,125 @@
import { writable, get } from "svelte/store"
import { derivedMemo, QueryUtils } from "../../../utils"
export const createStores = () => {
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,
}))
}
})
}

View File

@ -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,

View File

@ -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 => {

View File

@ -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],
}))
},
[]
)