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 dispatch = createEventDispatcher()
|
||||||
const flipDuration = 130
|
const flipDuration = 130
|
||||||
|
const conditionOptions = [
|
||||||
|
{
|
||||||
|
label: "Update background color",
|
||||||
|
value: "backgroundColor",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Update text color",
|
||||||
|
value: "textColor",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
let tempValue = []
|
let tempValue = []
|
||||||
let drawer
|
let drawer
|
||||||
|
@ -57,6 +67,7 @@
|
||||||
const addCondition = () => {
|
const addCondition = () => {
|
||||||
const condition = {
|
const condition = {
|
||||||
id: generate(),
|
id: generate(),
|
||||||
|
metadataKey: conditionOptions[0].value,
|
||||||
operator: Constants.OperatorOptions.Equals.value,
|
operator: Constants.OperatorOptions.Equals.value,
|
||||||
valueType: FieldType.STRING,
|
valueType: FieldType.STRING,
|
||||||
}
|
}
|
||||||
|
@ -132,10 +143,15 @@
|
||||||
>
|
>
|
||||||
<Icon name="DragHandle" size="XL" />
|
<Icon name="DragHandle" size="XL" />
|
||||||
</div>
|
</div>
|
||||||
<span>Set background color to</span>
|
<Select
|
||||||
|
placeholder={null}
|
||||||
|
options={conditionOptions}
|
||||||
|
bind:value={condition.metadataKey}
|
||||||
|
/>
|
||||||
|
<span>to</span>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
value={condition.color}
|
value={condition.metadataValue}
|
||||||
on:change={e => (condition.color = e.detail)}
|
on:change={e => (condition.metadataValue = e.detail)}
|
||||||
/>
|
/>
|
||||||
<span>if value</span>
|
<span>if value</span>
|
||||||
<Select
|
<Select
|
||||||
|
@ -197,7 +213,7 @@
|
||||||
}
|
}
|
||||||
.condition {
|
.condition {
|
||||||
display: grid;
|
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;
|
align-items: center;
|
||||||
grid-column-gap: var(--spacing-l);
|
grid-column-gap: var(--spacing-l);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,11 @@
|
||||||
if (selectedUser) {
|
if (selectedUser) {
|
||||||
style += `--user-color :${selectedUser.color};`
|
style += `--user-color :${selectedUser.color};`
|
||||||
}
|
}
|
||||||
if (metadata?.background) {
|
if (metadata?.backgroundColor) {
|
||||||
style += `--cell-background: ${metadata.background};`
|
style += `--cell-background: ${metadata.backgroundColor};`
|
||||||
|
}
|
||||||
|
if (metadata?.textColor) {
|
||||||
|
style += `--cell-font-color: ${metadata.textColor};`
|
||||||
}
|
}
|
||||||
return style
|
return style
|
||||||
}
|
}
|
||||||
|
@ -76,7 +79,7 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
color: var(--spectrum-global-color-gray-800);
|
color: var(--cell-font-color);
|
||||||
font-size: var(--cell-font-size);
|
font-size: var(--cell-font-size);
|
||||||
gap: var(--cell-spacing);
|
gap: var(--cell-spacing);
|
||||||
background: var(--cell-background);
|
background: var(--cell-background);
|
||||||
|
|
|
@ -231,6 +231,7 @@
|
||||||
--cell-spacing: 4px;
|
--cell-spacing: 4px;
|
||||||
--cell-border: 1px solid var(--spectrum-global-color-gray-200);
|
--cell-border: 1px solid var(--spectrum-global-color-gray-200);
|
||||||
--cell-font-size: 14px;
|
--cell-font-size: 14px;
|
||||||
|
--cell-font-color: var(--spectrum-global-color-gray-800);
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -166,7 +166,7 @@
|
||||||
|
|
||||||
/* Don't show borders between cells in the sticky column */
|
/* Don't show borders between cells in the sticky column */
|
||||||
.sticky-column :global(.cell:not(:last-child)) {
|
.sticky-column :global(.cell:not(:last-child)) {
|
||||||
border-right: none;
|
border-right-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.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 ViewV2 from "./datasources/viewV2"
|
||||||
import * as NonPlus from "./datasources/nonPlus"
|
import * as NonPlus from "./datasources/nonPlus"
|
||||||
import * as Cache from "./cache"
|
import * as Cache from "./cache"
|
||||||
|
import * as Conditions from "./conditions"
|
||||||
|
|
||||||
const DependencyOrderedStores = [
|
const DependencyOrderedStores = [
|
||||||
Sort,
|
Sort,
|
||||||
|
@ -33,6 +34,7 @@ const DependencyOrderedStores = [
|
||||||
Scroll,
|
Scroll,
|
||||||
Validation,
|
Validation,
|
||||||
Rows,
|
Rows,
|
||||||
|
Conditions,
|
||||||
UI,
|
UI,
|
||||||
Resize,
|
Resize,
|
||||||
Viewport,
|
Viewport,
|
||||||
|
|
|
@ -5,56 +5,6 @@ import { getCellID, parseCellID } from "../lib/utils"
|
||||||
import { tick } from "svelte"
|
import { tick } from "svelte"
|
||||||
import { Helpers } from "@budibase/bbui"
|
import { Helpers } from "@budibase/bbui"
|
||||||
import { sleep } from "../../../utils/utils"
|
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 = () => {
|
export const createStores = () => {
|
||||||
const rows = writable([])
|
const rows = writable([])
|
||||||
|
@ -91,29 +41,15 @@ export const createStores = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deriveStores = context => {
|
export const deriveStores = context => {
|
||||||
const { rows, columns, rowChangeCache } = context
|
const { rows } = context
|
||||||
|
|
||||||
// Enrich rows with an index property and any pending changes
|
// Enrich rows with an index property and any pending changes
|
||||||
const enrichedRows = derived(
|
const enrichedRows = derived(rows, $rows => {
|
||||||
[rows, rowChangeCache, columns],
|
return $rows.map((row, idx) => ({
|
||||||
([$rows, $rowChangeCache, $columns]) => {
|
...row,
|
||||||
if (!$rows?.length || !$columns?.length) {
|
__idx: idx,
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Generate a lookup map to quick find a row by ID
|
// Generate a lookup map to quick find a row by ID
|
||||||
const rowLookupMap = derived(enrichedRows, $enrichedRows => {
|
const rowLookupMap = derived(enrichedRows, $enrichedRows => {
|
||||||
|
|
|
@ -10,6 +10,8 @@ export const deriveStores = context => {
|
||||||
scrollLeft,
|
scrollLeft,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
rowChangeCache,
|
||||||
|
conditionMetadata,
|
||||||
} = context
|
} = context
|
||||||
|
|
||||||
// Derive visible rows
|
// Derive visible rows
|
||||||
|
@ -30,12 +32,27 @@ export const deriveStores = context => {
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
const renderedRows = derived(
|
const renderedRows = derived(
|
||||||
[rows, scrolledRowCount, visualRowCapacity],
|
[
|
||||||
([$rows, $scrolledRowCount, $visualRowCapacity]) => {
|
rows,
|
||||||
return $rows.slice(
|
scrolledRowCount,
|
||||||
$scrolledRowCount,
|
visualRowCapacity,
|
||||||
$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