Merge master.

This commit is contained in:
Sam Rose 2024-09-30 09:54:36 +01:00
commit 62e1e66ce9
No known key found for this signature in database
30 changed files with 390 additions and 159 deletions

View File

@ -277,5 +277,5 @@ export const flags = new FlagSet({
AUTOMATION_BRANCHING: Flag.boolean(env.isDev()),
SQS: Flag.boolean(env.isDev()),
[FeatureFlag.AI_CUSTOM_CONFIGS]: Flag.boolean(env.isDev()),
[FeatureFlag.ENRICHED_RELATIONSHIPS]: Flag.boolean(false),
[FeatureFlag.ENRICHED_RELATIONSHIPS]: Flag.boolean(env.isDev()),
})

View File

@ -6,7 +6,7 @@
import { TableNames, UNEDITABLE_USER_FIELDS } from "constants"
import RoleCell from "./cells/RoleCell.svelte"
import { createEventDispatcher } from "svelte"
import { canBeSortColumn } from "@budibase/shared-core"
import { canBeSortColumn } from "@budibase/frontend-core"
export let schema = {}
export let data = []
@ -31,7 +31,7 @@
acc[key] =
typeof schema[key] === "string" ? { type: schema[key] } : schema[key]
if (!canBeSortColumn(acc[key].type)) {
if (!canBeSortColumn(acc[key])) {
acc[key].sortable = false
}
return acc

View File

@ -1,6 +1,6 @@
<script>
import { viewsV2 } from "stores/builder"
import { admin } from "stores/portal"
import { admin, themeStore } from "stores/portal"
import { Grid } from "@budibase/frontend-core"
import { API } from "api"
import GridCreateEditRowModal from "components/backend/DataTable/modals/grid/GridCreateEditRowModal.svelte"
@ -16,6 +16,9 @@
tableId: $viewsV2.selected?.tableId,
}
$: currentTheme = $themeStore?.theme
$: darkMode = !currentTheme.includes("light")
const handleGridViewUpdate = async e => {
viewsV2.replaceView(id, e.detail)
}
@ -25,6 +28,7 @@
<Grid
{API}
{datasource}
{darkMode}
allowAddRows
allowDeleteRows
showAvatars={false}

View File

@ -19,7 +19,6 @@
helpers,
PROTECTED_INTERNAL_COLUMNS,
PROTECTED_EXTERNAL_COLUMNS,
canBeDisplayColumn,
canHaveDefaultColumn,
} from "@budibase/shared-core"
import { createEventDispatcher, getContext, onMount } from "svelte"
@ -43,7 +42,7 @@
SourceName,
} from "@budibase/types"
import RelationshipSelector from "components/common/RelationshipSelector.svelte"
import { RowUtils } from "@budibase/frontend-core"
import { RowUtils, canBeDisplayColumn } from "@budibase/frontend-core"
import ServerBindingPanel from "components/common/bindings/ServerBindingPanel.svelte"
import OptionsEditor from "./OptionsEditor.svelte"
import { isEnabled } from "helpers/featureFlags"
@ -166,7 +165,7 @@
: availableAutoColumns
// used to select what different options can be displayed for column type
$: canBeDisplay =
canBeDisplayColumn(editableColumn.type) && !editableColumn.autocolumn
canBeDisplayColumn(editableColumn) && !editableColumn.autocolumn
$: canHaveDefault =
isEnabled("DEFAULT_VALUES") && canHaveDefaultColumn(editableColumn.type)
$: canBeRequired =

View File

@ -1,7 +1,8 @@
<script>
import { Select, Icon } from "@budibase/bbui"
import { FIELDS } from "constants/backend"
import { canBeDisplayColumn, utils } from "@budibase/shared-core"
import { utils } from "@budibase/shared-core"
import { canBeDisplayColumn } from "@budibase/frontend-core"
import { API } from "api"
import { parseFile } from "./utils"
@ -100,10 +101,10 @@
let rawRows = []
$: displayColumnOptions = Object.keys(schema || {}).filter(column => {
return validation[column] && canBeDisplayColumn(schema[column].type)
return validation[column] && canBeDisplayColumn(schema[column])
})
$: if (displayColumn && !canBeDisplayColumn(schema[displayColumn].type)) {
$: if (displayColumn && !canBeDisplayColumn(schema[displayColumn])) {
displayColumn = null
}

View File

@ -1,4 +1,5 @@
<script>
import { enrichSchemaWithRelColumns } from "@budibase/frontend-core"
import { getDatasourceForProvider, getSchemaForDatasource } from "dataBinding"
import { selectedScreen, componentStore } from "stores/builder"
import DraggableList from "../DraggableList/DraggableList.svelte"
@ -27,7 +28,8 @@
delete schema._rev
}
return schema
const result = enrichSchemaWithRelColumns(schema)
return result
}
$: datasource = getDatasourceForProvider($selectedScreen, componentInstance)

View File

@ -82,7 +82,7 @@ const toDraggableListFormat = (gridFormatColumns, createComponent, schema) => {
active: column.active,
field: column.field,
label: column.label,
columnType: schema[column.field].type,
columnType: column.columnType || schema[column.field].type,
width: column.width,
conditions: column.conditions,
},

View File

@ -3,7 +3,7 @@
import { getDatasourceForProvider, getSchemaForDatasource } from "dataBinding"
import { selectedScreen } from "stores/builder"
import { createEventDispatcher } from "svelte"
import { canBeSortColumn } from "@budibase/shared-core"
import { canBeSortColumn } from "@budibase/frontend-core"
export let componentInstance = {}
export let value = ""
@ -17,7 +17,7 @@
const getSortableFields = schema => {
return Object.entries(schema || {})
.filter(entry => canBeSortColumn(entry[1].type))
.filter(entry => canBeSortColumn(entry[1]))
.map(entry => entry[0])
}

View File

@ -2,7 +2,7 @@
import { getContext, onDestroy } from "svelte"
import { Table } from "@budibase/bbui"
import SlotRenderer from "./SlotRenderer.svelte"
import { canBeSortColumn } from "@budibase/shared-core"
import { canBeSortColumn } from "@budibase/frontend-core"
import Provider from "components/context/Provider.svelte"
export let dataProvider
@ -146,7 +146,7 @@
return
}
newSchema[columnName] = schema[columnName]
if (!canBeSortColumn(schema[columnName].type)) {
if (!canBeSortColumn(schema[columnName])) {
newSchema[columnName].sortable = false
}

View File

@ -2,6 +2,7 @@
import { onMount, getContext } from "svelte"
import { Dropzone } from "@budibase/bbui"
import GridPopover from "../overlays/GridPopover.svelte"
import { FieldType } from "@budibase/types"
export let value
export let focused = false
@ -81,7 +82,12 @@
>
{#each value || [] as attachment}
{#if isImage(attachment.extension)}
<img src={attachment.url} alt={attachment.extension} />
<img
class:light={!$props?.darkMode &&
schema.type === FieldType.SIGNATURE_SINGLE}
src={attachment.url}
alt={attachment.extension}
/>
{:else}
<div class="file" title={attachment.name}>
{attachment.extension}
@ -140,4 +146,9 @@
width: 320px;
padding: var(--cell-padding);
}
.attachment-cell img.light {
-webkit-filter: invert(100%);
filter: invert(100%);
}
</style>

View File

@ -1,6 +1,6 @@
<script>
import { getContext, onMount, tick } from "svelte"
import { canBeDisplayColumn, canBeSortColumn } from "@budibase/shared-core"
import { canBeSortColumn, canBeDisplayColumn } from "@budibase/frontend-core"
import { Icon, Menu, MenuItem, Modal } from "@budibase/bbui"
import GridCell from "./GridCell.svelte"
import { getColumnIcon } from "../lib/utils"
@ -165,7 +165,17 @@
}
const hideColumn = () => {
datasource.actions.addSchemaMutation(column.name, { visible: false })
const { related } = column
const mutation = { visible: false }
if (!related) {
datasource.actions.addSchemaMutation(column.name, mutation)
} else {
datasource.actions.addSubSchemaMutation(
related.subField,
related.field,
mutation
)
}
datasource.actions.saveSchemaMutations()
open = false
}
@ -347,15 +357,14 @@
<MenuItem
icon="Label"
on:click={makeDisplayColumn}
disabled={column.primaryDisplay ||
!canBeDisplayColumn(column.schema.type)}
disabled={column.primaryDisplay || !canBeDisplayColumn(column.schema)}
>
Use as display column
</MenuItem>
<MenuItem
icon="SortOrderUp"
on:click={sortAscending}
disabled={!canBeSortColumn(column.schema.type) ||
disabled={!canBeSortColumn(column.schema) ||
(column.name === $sort.column && $sort.order === "ascending")}
>
Sort {sortingLabels.ascending}
@ -363,7 +372,7 @@
<MenuItem
icon="SortOrderDown"
on:click={sortDescending}
disabled={!canBeSortColumn(column.schema.type) ||
disabled={!canBeSortColumn(column.schema) ||
(column.name === $sort.column && $sort.order === "descending")}
>
Sort {sortingLabels.descending}

View File

@ -4,13 +4,15 @@
import ColumnsSettingContent from "./ColumnsSettingContent.svelte"
import { FieldPermissions } from "../../../constants"
const { columns, datasource } = getContext("grid")
const { tableColumns, datasource } = getContext("grid")
let open = false
let anchor
$: anyRestricted = $columns.filter(col => !col.visible || col.readonly).length
$: text = anyRestricted ? `Columns: (${anyRestricted} restricted)` : "Columns"
$: anyRestricted = $tableColumns.filter(
col => !col.visible || col.readonly
).length
$: text = anyRestricted ? `Columns (${anyRestricted} restricted)` : "Columns"
$: permissions =
$datasource.type === "viewV2"
? [
@ -28,12 +30,12 @@
size="M"
on:click={() => (open = !open)}
selected={open || anyRestricted}
disabled={!$columns.length}
disabled={!$tableColumns.length}
>
{text}
</ActionButton>
</div>
<Popover bind:open {anchor} align="left">
<ColumnsSettingContent columns={$columns} {permissions} />
<ColumnsSettingContent columns={$tableColumns} {permissions} />
</Popover>

View File

@ -122,8 +122,10 @@
label: name,
schema: {
type: column.type,
subtype: column.subtype,
visible: column.visible,
readonly: column.readonly,
constraints: column.constraints, // This is needed to properly display "users" column
},
}
})

View File

@ -1,7 +1,7 @@
<script>
import { getContext } from "svelte"
import { ActionButton, Popover, Select } from "@budibase/bbui"
import { canBeSortColumn } from "@budibase/shared-core"
import { canBeSortColumn } from "@budibase/frontend-core"
const { sort, columns } = getContext("grid")
@ -13,8 +13,9 @@
label: col.label || col.name,
value: col.name,
type: col.schema?.type,
related: col.related,
}))
.filter(col => canBeSortColumn(col.type))
.filter(col => canBeSortColumn(col))
$: orderOptions = getOrderOptions($sort.column, columnOptions)
const getOrderOptions = (column, columnOptions) => {

View File

@ -35,5 +35,9 @@ const TypeComponentMap = {
[FieldType.BB_REFERENCE_SINGLE]: BBReferenceSingleCell,
}
export const getCellRenderer = column => {
return TypeComponentMap[column?.schema?.type] || TextCell
return (
TypeComponentMap[column?.schema?.cellRenderType] ||
TypeComponentMap[column?.schema?.type] ||
TextCell
)
}

View File

@ -42,6 +42,11 @@ export const deriveStores = context => {
return map
})
// Derived list of columns which are direct part of the table
const tableColumns = derived(columns, $columns => {
return $columns.filter(col => !col.related)
})
// Derived list of columns which have not been explicitly hidden
const visibleColumns = derived(columns, $columns => {
return $columns.filter(col => col.visible)
@ -64,6 +69,7 @@ export const deriveStores = context => {
})
return {
tableColumns,
displayColumn,
columnLookupMap,
visibleColumns,
@ -73,16 +79,24 @@ export const deriveStores = context => {
}
export const createActions = context => {
const { columns, datasource, schema } = context
const { columns, datasource } = context
// Updates the width of all columns
const changeAllColumnWidths = async width => {
const $schema = get(schema)
let mutations = {}
Object.keys($schema).forEach(field => {
mutations[field] = { width }
const $columns = get(columns)
$columns.forEach(column => {
const { related } = column
const mutation = { width }
if (!related) {
datasource.actions.addSchemaMutation(column.name, mutation)
} else {
datasource.actions.addSubSchemaMutation(
related.subField,
related.field,
mutation
)
}
})
datasource.actions.addSchemaMutations(mutations)
await datasource.actions.saveSchemaMutations()
}
@ -136,7 +150,7 @@ export const initialise = context => {
.map(field => {
const fieldSchema = $enrichedSchema[field]
const oldColumn = $columns?.find(col => col.name === field)
let column = {
const column = {
name: field,
label: fieldSchema.displayName || field,
schema: fieldSchema,
@ -145,6 +159,7 @@ export const initialise = context => {
readonly: fieldSchema.readonly,
order: fieldSchema.order ?? oldColumn?.order,
conditions: fieldSchema.conditions,
related: fieldSchema.related,
}
// Override a few properties for primary display
if (field === primaryDisplay) {

View File

@ -1,6 +1,6 @@
import { derived, get } from "svelte/store"
import { getDatasourceDefinition, getDatasourceSchema } from "../../../fetch"
import { memo } from "../../../utils"
import { enrichSchemaWithRelColumns, memo } from "../../../utils"
export const createStores = () => {
const definition = memo(null)
@ -53,10 +53,13 @@ export const deriveStores = context => {
if (!$schema) {
return null
}
let enrichedSchema = {}
Object.keys($schema).forEach(field => {
const schemaWithRelatedColumns = enrichSchemaWithRelColumns($schema)
const enrichedSchema = {}
Object.keys(schemaWithRelatedColumns).forEach(field => {
enrichedSchema[field] = {
...$schema[field],
...schemaWithRelatedColumns[field],
...$schemaOverrides?.[field],
...$schemaMutations[field],
}
@ -202,24 +205,6 @@ export const createActions = context => {
})
}
// Adds schema mutations for multiple fields at once
const addSchemaMutations = mutations => {
const fields = Object.keys(mutations || {})
if (!fields.length) {
return
}
schemaMutations.update($schemaMutations => {
let newSchemaMutations = { ...$schemaMutations }
fields.forEach(field => {
newSchemaMutations[field] = {
...newSchemaMutations[field],
...mutations[field],
}
})
return newSchemaMutations
})
}
// Saves schema changes to the server, if possible
const saveSchemaMutations = async () => {
// If we can't save schema changes then we just want to keep this in memory
@ -309,7 +294,6 @@ export const createActions = context => {
changePrimaryDisplay,
addSchemaMutation,
addSubSchemaMutation,
addSchemaMutations,
saveSchemaMutations,
resetSchemaMutations,
},

View File

@ -120,17 +120,22 @@ export const initialise = context => {
// When sorting changes, ensure view definition is kept up to date
unsubscribers.push(
sort.subscribe(async $sort => {
// If we can mutate schema then update the view definition
if (get(config).canSaveSchema) {
// Ensure we're updating the correct view
const $view = get(definition)
if ($view?.id !== $datasource.id) {
return
}
// Skip if nothing actually changed
if (
$sort?.column !== $view.sort?.field ||
$sort?.order !== $view.sort?.order
$sort?.column === $view.sort?.field &&
$sort?.order === $view.sort?.order
) {
return
}
// If we can mutate schema then update the view definition
if (get(config).canSaveSchema) {
await datasource.actions.saveDefinition({
...$view,
sort: {
@ -139,7 +144,6 @@ export const initialise = context => {
},
})
}
}
// Also update the fetch to ensure the new sort is respected.
// Ensure we're updating the correct fetch.

View File

@ -214,11 +214,20 @@ export const createActions = context => {
})
// Extract new orders as schema mutations
let mutations = {}
get(columns).forEach((column, idx) => {
mutations[column.name] = { order: idx }
const { related } = column
const mutation = { order: idx }
if (!related) {
datasource.actions.addSchemaMutation(column.name, mutation)
} else {
datasource.actions.addSubSchemaMutation(
related.subField,
related.field,
mutation
)
}
})
datasource.actions.addSchemaMutations(mutations)
await datasource.actions.saveSchemaMutations()
}

View File

@ -38,6 +38,7 @@ export const createActions = context => {
initialWidth: column.width,
initialMouseX: x,
column: column.name,
related: column.related,
})
// Add mouse event listeners to handle resizing
@ -50,7 +51,7 @@ export const createActions = context => {
// Handler for moving the mouse to resize columns
const onResizeMouseMove = e => {
const { initialMouseX, initialWidth, width, column } = get(resize)
const { initialMouseX, initialWidth, width, column, related } = get(resize)
const { x } = parseEventLocation(e)
const dx = x - initialMouseX
const newWidth = Math.round(Math.max(MinColumnWidth, initialWidth + dx))
@ -61,7 +62,13 @@ export const createActions = context => {
}
// Update column state
if (!related) {
datasource.actions.addSchemaMutation(column, { width })
} else {
datasource.actions.addSubSchemaMutation(related.subField, related.field, {
width,
})
}
// Update state
resize.update(state => ({

View File

@ -6,6 +6,7 @@ import { tick } from "svelte"
import { Helpers } from "@budibase/bbui"
import { sleep } from "../../../utils/utils"
import { FieldType } from "@budibase/types"
import { getRelatedTableValues } from "../../../utils"
export const createStores = () => {
const rows = writable([])
@ -42,15 +43,26 @@ export const createStores = () => {
}
export const deriveStores = context => {
const { rows } = context
const { rows, enrichedSchema } = context
// Enrich rows with an index property and any pending changes
const enrichedRows = derived(rows, $rows => {
const enrichedRows = derived(
[rows, enrichedSchema],
([$rows, $enrichedSchema]) => {
const customColumns = Object.values($enrichedSchema || {}).filter(
f => f.related
)
return $rows.map((row, idx) => ({
...row,
__idx: idx,
...customColumns.reduce((map, column) => {
const fromField = $enrichedSchema[column.related.field]
map[column.name] = getRelatedTableValues(row, column, fromField)
return map
}, {}),
}))
})
}
)
// Generate a lookup map to quick find a row by ID
const rowLookupMap = derived(enrichedRows, $enrichedRows => {

View File

@ -10,3 +10,5 @@ export { createWebsocket } from "./websocket"
export * from "./download"
export * from "./theme"
export * from "./settings"
export * from "./relatedColumns"
export * from "./table"

View File

@ -0,0 +1,107 @@
import { FieldType, RelationshipType } from "@budibase/types"
import { Helpers } from "@budibase/bbui"
const columnTypeManyTypeOverrides = {
[FieldType.DATETIME]: FieldType.STRING,
[FieldType.BOOLEAN]: FieldType.STRING,
[FieldType.SIGNATURE_SINGLE]: FieldType.ATTACHMENTS,
}
const columnTypeManyParser = {
[FieldType.DATETIME]: (value, field) => {
function parseDate(value) {
const { timeOnly, dateOnly, ignoreTimezones } = field || {}
const enableTime = !dateOnly
const parsedValue = Helpers.parseDate(value, {
timeOnly,
enableTime,
ignoreTimezones,
})
const parsed = Helpers.getDateDisplayValue(parsedValue, {
enableTime,
timeOnly,
})
return parsed
}
return value?.map(v => parseDate(v))
},
[FieldType.BOOLEAN]: value => value?.map(v => !!v),
[FieldType.BB_REFERENCE_SINGLE]: value => [
...new Map(value.map(i => [i._id, i])).values(),
],
[FieldType.BB_REFERENCE]: value => [
...new Map(value.map(i => [i._id, i])).values(),
],
[FieldType.ARRAY]: value => Array.from(new Set(value)),
}
export function enrichSchemaWithRelColumns(schema) {
if (!schema) {
return
}
const result = Object.keys(schema).reduce((result, fieldName) => {
const field = schema[fieldName]
result[fieldName] = field
if (field.visible !== false && field.columns) {
const fromSingle =
field?.relationshipType === RelationshipType.ONE_TO_MANY
for (const relColumn of Object.keys(field.columns)) {
const relField = field.columns[relColumn]
if (!relField.visible) {
continue
}
const name = `${field.name}.${relColumn}`
result[name] = {
...relField,
name,
related: { field: fieldName, subField: relColumn },
cellRenderType:
(!fromSingle && columnTypeManyTypeOverrides[relField.type]) ||
relField.type,
}
}
}
return result
}, {})
return result
}
export function getRelatedTableValues(row, field, fromField) {
const fromSingle =
fromField?.relationshipType === RelationshipType.ONE_TO_MANY
let result = ""
if (fromSingle) {
result = row[field.related.field]?.[0]?.[field.related.subField]
} else {
const parser = columnTypeManyParser[field.type] || (value => value)
result = parser(
row[field.related.field]
?.flatMap(r => r[field.related.subField])
?.filter(i => i !== undefined && i !== null),
field
)
if (
[
FieldType.STRING,
FieldType.NUMBER,
FieldType.BIGINT,
FieldType.BOOLEAN,
FieldType.DATETIME,
FieldType.LONGFORM,
FieldType.BARCODEQR,
].includes(field.type)
) {
result = result?.join(", ")
}
}
return result
}

View File

@ -0,0 +1,27 @@
import * as sharedCore from "@budibase/shared-core"
export function canBeDisplayColumn(column) {
if (!sharedCore.canBeDisplayColumn(column.type)) {
return false
}
if (column.related) {
// If it's a related column (only available in the frontend), don't allow using it as display column
return false
}
return true
}
export function canBeSortColumn(column) {
if (!sharedCore.canBeSortColumn(column.type)) {
return false
}
if (column.related) {
// If it's a related column (only available in the frontend), don't allow using it as display column
return false
}
return true
}

View File

@ -2690,7 +2690,7 @@ describe.each([
async (__, retrieveDelegate) => {
await withCoreEnv(
{
TENANT_FEATURE_FLAGS: ``,
TENANT_FEATURE_FLAGS: `*:!${FeatureFlag.ENRICHED_RELATIONSHIPS}`,
},
async () => {
const otherRows = _.sampleSize(auxData, 5)

View File

@ -32,25 +32,25 @@ describe("Branching automations", () => {
steps: stepBuilder =>
stepBuilder.serverLog({ text: "Branch 1.1" }),
condition: {
equal: { "steps.1.success": true },
equal: { "{{steps.1.success}}": true },
},
},
branch2: {
steps: stepBuilder =>
stepBuilder.serverLog({ text: "Branch 1.2" }),
condition: {
equal: { "steps.1.success": false },
equal: { "{{steps.1.success}}": false },
},
},
}),
condition: {
equal: { "steps.1.success": true },
equal: { "{{steps.1.success}}": true },
},
},
topLevelBranch2: {
steps: stepBuilder => stepBuilder.serverLog({ text: "Branch 2" }),
condition: {
equal: { "steps.1.success": false },
equal: { "{{steps.1.success}}": false },
},
},
})
@ -70,14 +70,14 @@ describe("Branching automations", () => {
activeBranch: {
steps: stepBuilder => stepBuilder.serverLog({ text: "Active user" }),
condition: {
equal: { "trigger.fields.status": "active" },
equal: { "{{trigger.fields.status}}": "active" },
},
},
inactiveBranch: {
steps: stepBuilder =>
stepBuilder.serverLog({ text: "Inactive user" }),
condition: {
equal: { "trigger.fields.status": "inactive" },
equal: { "{{trigger.fields.status}}": "inactive" },
},
},
})
@ -102,8 +102,8 @@ describe("Branching automations", () => {
condition: {
$and: {
conditions: [
{ equal: { "trigger.fields.status": "active" } },
{ equal: { "trigger.fields.role": "admin" } },
{ equal: { "{{trigger.fields.status}}": "active" } },
{ equal: { "{{trigger.fields.role}}": "admin" } },
],
},
},
@ -111,7 +111,7 @@ describe("Branching automations", () => {
otherBranch: {
steps: stepBuilder => stepBuilder.serverLog({ text: "Other user" }),
condition: {
notEqual: { "trigger.fields.status": "active" },
notEqual: { "{{trigger.fields.status}}": "active" },
},
},
})
@ -133,8 +133,8 @@ describe("Branching automations", () => {
condition: {
$or: {
conditions: [
{ equal: { "trigger.fields.status": "test" } },
{ equal: { "trigger.fields.role": "admin" } },
{ equal: { "{{trigger.fields.status}}": "test" } },
{ equal: { "{{trigger.fields.role}}": "admin" } },
],
},
},
@ -144,8 +144,8 @@ describe("Branching automations", () => {
condition: {
$and: {
conditions: [
{ notEqual: { "trigger.fields.status": "active" } },
{ notEqual: { "trigger.fields.role": "admin" } },
{ notEqual: { "{{trigger.fields.status}}": "active" } },
{ notEqual: { "{{trigger.fields.role}}": "admin" } },
],
},
},
@ -170,8 +170,8 @@ describe("Branching automations", () => {
condition: {
$or: {
conditions: [
{ equal: { "trigger.fields.status": "new" } },
{ equal: { "trigger.fields.role": "admin" } },
{ equal: { "{{trigger.fields.status}}": "new" } },
{ equal: { "{{trigger.fields.role}}": "admin" } },
],
},
},
@ -181,8 +181,8 @@ describe("Branching automations", () => {
condition: {
$and: {
conditions: [
{ equal: { "trigger.fields.status": "active" } },
{ equal: { "trigger.fields.role": "admin" } },
{ equal: { "{{trigger.fields.status}}": "active" } },
{ equal: { "{{trigger.fields.role}}": "admin" } },
],
},
},

View File

@ -10,7 +10,10 @@ import flatten from "lodash/flatten"
import { USER_METDATA_PREFIX } from "../utils"
import partition from "lodash/partition"
import { getGlobalUsersFromMetadata } from "../../utilities/global"
import { outputProcessing, processFormulas } from "../../utilities/rowProcessor"
import {
coreOutputProcessing,
processFormulas,
} from "../../utilities/rowProcessor"
import { context, features } from "@budibase/backend-core"
import {
ContextUser,
@ -157,9 +160,6 @@ export async function updateLinks(args: {
/**
* Given a table and a list of rows this will retrieve all of the attached docs and enrich them into the row.
* This is required for formula fields, this may only be utilised internally (for now).
* @param table The table from which the rows originated.
* @param rows The rows which are to be enriched.
* @param opts optional - options like passing in a base row to use for enrichment.
* @return returns the rows with all of the enriched relationships on it.
*/
export async function attachFullLinkedDocs(
@ -251,9 +251,8 @@ export type SquashTableFields = Record<string, { visibleFieldNames: string[] }>
* This function will take the given enriched rows and squash the links to only
* contain the primary display field.
*
* @param source The table or view from which the rows originated.
* @param enriched The pre-enriched rows (full docs) which are to be squashed.
* @returns The rows after having their links squashed to only contain the ID and primary display.
* @returns The rows after having their links squashed to only contain the ID
* and primary display.
*/
export async function squashLinks<T = Row[] | Row>(
source: Table | ViewV2,
@ -292,21 +291,18 @@ export async function squashLinks<T = Row[] | Row>(
if (schema.type !== FieldType.LINK || !Array.isArray(row[column])) {
continue
}
const newLinks = []
for (const link of row[column]) {
const linkTblId =
link.tableId || getRelatedTableForField(table.schema, column)
const linkedTable = await getLinkedTable(linkTblId!, linkedTables)
const relatedTable = await getLinkedTable(schema.tableId, linkedTables)
if (viewSchema[column]?.columns) {
row[column] = await coreOutputProcessing(relatedTable, row[column])
}
row[column] = row[column].map((link: Row) => {
const obj: any = { _id: link._id }
obj.primaryDisplay = getPrimaryDisplayValue(link, linkedTable)
obj.primaryDisplay = getPrimaryDisplayValue(link, relatedTable)
if (viewSchema[column]?.columns) {
const enrichedLink = await outputProcessing(linkedTable, link, {
squash: false,
})
const squashFields = Object.entries(viewSchema[column].columns)
const squashFields = Object.entries(viewSchema[column].columns || {})
.filter(([columnName, viewColumnConfig]) => {
const tableColumn = linkedTable.schema[columnName]
const tableColumn = relatedTable.schema[columnName]
if (!tableColumn) {
return false
}
@ -324,13 +320,14 @@ export async function squashLinks<T = Row[] | Row>(
.map(([columnName]) => columnName)
for (const relField of squashFields) {
obj[relField] = enrichedLink[relField]
if (link[relField] != null) {
obj[relField] = link[relField]
}
}
}
newLinks.push(obj)
}
row[column] = newLinks
return obj
})
}
}
return (isArray ? enrichedArray : enrichedArray[0]) as T

View File

@ -516,10 +516,8 @@ class Orchestrator {
filter => {
Object.entries(filter).forEach(([_, value]) => {
Object.entries(value).forEach(([field, _]) => {
const fromContext = processStringSync(
`{{ literal ${field} }}`,
this.context
)
const updatedField = field.replace("{{", "{{ literal ")
const fromContext = processStringSync(updatedField, this.context)
toFilter[field] = fromContext
})
})

View File

@ -284,6 +284,7 @@ export async function outputProcessing<T extends Row[] | Row>(
table = source
}
// SQS returns the rows with full relationship contents
// attach any linked row information
let enriched = !opts.preserveLinks
? await linkRows.attachFullLinkedDocs(table.schema, safeRows, {
@ -291,11 +292,46 @@ export async function outputProcessing<T extends Row[] | Row>(
})
: safeRows
// make sure squash is enabled if needed
if (!opts.squash && utils.hasCircularStructure(rows)) {
opts.squash = true
}
enriched = await coreOutputProcessing(source, enriched, opts)
if (opts.squash) {
enriched = await linkRows.squashLinks(source, enriched)
}
return (wasArray ? enriched : enriched[0]) as T
}
/**
* This function is similar to the outputProcessing function above, it makes
* sure that all the provided rows are ready for output, but does not have
* enrichment for squash capabilities which can cause performance issues.
* outputProcessing should be used when responding from the API, while this
* should be used when internally processing rows for any reason (like part of
* view operations).
*/
export async function coreOutputProcessing(
source: Table | ViewV2,
rows: Row[],
opts: {
preserveLinks?: boolean
skipBBReferences?: boolean
fromViewId?: string
} = {
preserveLinks: false,
skipBBReferences: false,
}
): Promise<Row[]> {
let table: Table
if (sdk.views.isView(source)) {
table = await sdk.views.getTable(source.id)
} else {
table = source
}
// process complex types: attachments, bb references...
for (const [property, column] of Object.entries(table.schema)) {
if (
@ -303,7 +339,7 @@ export async function outputProcessing<T extends Row[] | Row>(
column.type === FieldType.ATTACHMENT_SINGLE ||
column.type === FieldType.SIGNATURE_SINGLE
) {
for (const row of enriched) {
for (const row of rows) {
if (row[property] == null) {
continue
}
@ -328,7 +364,7 @@ export async function outputProcessing<T extends Row[] | Row>(
!opts.skipBBReferences &&
column.type == FieldType.BB_REFERENCE
) {
for (const row of enriched) {
for (const row of rows) {
row[property] = await processOutputBBReferences(
row[property],
column.subtype
@ -338,14 +374,14 @@ export async function outputProcessing<T extends Row[] | Row>(
!opts.skipBBReferences &&
column.type == FieldType.BB_REFERENCE_SINGLE
) {
for (const row of enriched) {
for (const row of rows) {
row[property] = await processOutputBBReference(
row[property],
column.subtype
)
}
} else if (column.type === FieldType.DATETIME && column.timeOnly) {
for (const row of enriched) {
for (const row of rows) {
if (row[property] instanceof Date) {
const hours = row[property].getUTCHours().toString().padStart(2, "0")
const minutes = row[property]
@ -360,7 +396,7 @@ export async function outputProcessing<T extends Row[] | Row>(
}
}
} else if (column.type === FieldType.LINK) {
for (let row of enriched) {
for (let row of rows) {
// if relationship is empty - remove the array, this has been part of the API for some time
if (Array.isArray(row[property]) && row[property].length === 0) {
delete row[property]
@ -370,16 +406,12 @@ export async function outputProcessing<T extends Row[] | Row>(
}
// process formulas after the complex types had been processed
enriched = await processFormulas(table, enriched, { dynamic: true })
if (opts.squash) {
enriched = await linkRows.squashLinks(source, enriched)
}
rows = await processFormulas(table, rows, { dynamic: true })
// remove null properties to match internal API
const isExternal = isExternalTableID(table._id!)
if (isExternal || (await features.flags.isEnabled("SQS"))) {
for (const row of enriched) {
for (const row of rows) {
for (const key of Object.keys(row)) {
if (row[key] === null) {
delete row[key]
@ -416,7 +448,7 @@ export async function outputProcessing<T extends Row[] | Row>(
}
}
for (const row of enriched) {
for (const row of rows) {
for (const key of Object.keys(row)) {
if (!fields.includes(key.toLowerCase())) {
delete row[key]
@ -425,5 +457,5 @@ export async function outputProcessing<T extends Row[] | Row>(
}
}
return (wasArray ? enriched : enriched[0]) as T
return rows
}

View File

@ -2066,7 +2066,7 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@2.32.5":
"@budibase/backend-core@2.32.8":
version "0.0.0"
dependencies:
"@budibase/nano" "10.1.5"
@ -2147,14 +2147,15 @@
through2 "^2.0.0"
"@budibase/pro@npm:@budibase/pro@latest":
version "2.32.5"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.32.5.tgz#2beecf566da972a92200faddc97bc152ea2bbdea"
integrity sha512-afrklI2A8P7pfl/3KxysqO2Sjr0l2yQ1+jyuouEZliEklLxV8AFlzrODr4V2SK3J8E1xk8wG5ztYQS2uT7TnuA==
version "2.32.8"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.32.8.tgz#1b42da2ca7a496ba1ea8687cde6e7f3a8da47e48"
integrity sha512-NlY8DkD54FVcy6sL4T+wBJr/KQjXv6CGlqcHyjWVRBd8k/xLTzivrC5gVryDrkja/5ZxIm0qBKVlE81H6urLNA==
dependencies:
"@budibase/backend-core" "2.32.5"
"@budibase/shared-core" "2.32.5"
"@budibase/string-templates" "2.32.5"
"@budibase/types" "2.32.5"
"@anthropic-ai/sdk" "^0.27.3"
"@budibase/backend-core" "2.32.8"
"@budibase/shared-core" "2.32.8"
"@budibase/string-templates" "2.32.8"
"@budibase/types" "2.32.8"
"@koa/router" "8.0.8"
bull "4.10.1"
dd-trace "5.2.0"
@ -2163,16 +2164,17 @@
lru-cache "^7.14.1"
memorystream "^0.3.1"
node-fetch "2.6.7"
openai "4.59.0"
scim-patch "^0.8.1"
scim2-parse-filter "^0.2.8"
"@budibase/shared-core@2.32.5":
"@budibase/shared-core@2.32.8":
version "0.0.0"
dependencies:
"@budibase/types" "0.0.0"
cron-validate "1.4.5"
"@budibase/string-templates@2.32.5":
"@budibase/string-templates@2.32.8":
version "0.0.0"
dependencies:
"@budibase/handlebars-helpers" "^0.13.2"
@ -2180,7 +2182,7 @@
handlebars "^4.7.8"
lodash.clonedeep "^4.5.0"
"@budibase/types@2.32.5":
"@budibase/types@2.32.8":
version "0.0.0"
dependencies:
scim-patch "^0.8.1"