Optimise condition evaluation performance and add support for conditionally setting text color
This commit is contained in:
parent
c9bcda0bd5
commit
99b522b32d
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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],
|
||||
}))
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue