Merge pull request #13527 from Budibase/prefill-cards

Prefill card blocks
This commit is contained in:
Andrew Kingston 2024-05-22 08:22:57 +01:00 committed by GitHub
commit 868989d4c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 168 additions and 14 deletions

View File

@ -20,7 +20,7 @@ import {
previewStore, previewStore,
tables, tables,
componentTreeNodesStore, componentTreeNodesStore,
} from "stores/builder/index" } from "stores/builder"
import { buildFormSchema, getSchemaForDatasource } from "dataBinding" import { buildFormSchema, getSchemaForDatasource } from "dataBinding"
import { import {
BUDIBASE_INTERNAL_DB_ID, BUDIBASE_INTERNAL_DB_ID,
@ -30,6 +30,7 @@ import {
} from "constants/backend" } from "constants/backend"
import BudiStore from "../BudiStore" import BudiStore from "../BudiStore"
import { Utils } from "@budibase/frontend-core" import { Utils } from "@budibase/frontend-core"
import { FieldType } from "@budibase/types"
export const INITIAL_COMPONENTS_STATE = { export const INITIAL_COMPONENTS_STATE = {
components: {}, components: {},
@ -296,6 +297,80 @@ export class ComponentStore extends BudiStore {
} }
} }
}) })
// Add default bindings to card blocks
if (component._component.endsWith("/cardsblock")) {
// Only proceed if the card is empty, i.e. we just changed datasource or
// just created the card
const cardKeys = ["cardTitle", "cardSubtitle", "cardDescription"]
if (cardKeys.every(key => !component[key]) && !component.cardImageURL) {
const { _id, dataSource } = component
if (dataSource) {
const { schema, table } = getSchemaForDatasource(screen, dataSource)
// Finds fields by types from the schema of the configured datasource
const findFieldTypes = fieldTypes => {
if (!Array.isArray(fieldTypes)) {
fieldTypes = [fieldTypes]
}
return Object.entries(schema || {})
.filter(([name, fieldSchema]) => {
return (
fieldTypes.includes(fieldSchema.type) &&
!fieldSchema.autoColumn &&
name !== table?.primaryDisplay &&
!name.startsWith("_")
)
})
.map(([name]) => name)
}
// Inserts a card binding for a certain setting
const addBinding = (key, fallback, ...parts) => {
if (parts.some(x => x == null)) {
component[key] = fallback
} else {
parts.unshift(`${_id}-repeater`)
component[key] = `{{ ${parts.map(safe).join(".")} }}`
}
}
// Extract good field candidates to prefill our cards with.
// Use the primary display as the best field, if it exists.
const shortFields = [
...findFieldTypes(FieldType.STRING),
...findFieldTypes(FieldType.OPTIONS),
...findFieldTypes(FieldType.ARRAY),
...findFieldTypes(FieldType.NUMBER),
]
const longFields = findFieldTypes(FieldType.LONGFORM)
if (schema?.[table?.primaryDisplay]) {
shortFields.unshift(table.primaryDisplay)
}
// Fill title and subtitle with short fields
addBinding("cardTitle", "Title", shortFields[0])
addBinding("cardSubtitle", "Subtitle", shortFields[1])
// Fill description with a long field if possible
const longField = longFields[0] ?? shortFields[2]
addBinding("cardDescription", "Description", longField)
// Attempt to fill the image setting.
// Check single attachment fields first.
let imgField = findFieldTypes(FieldType.ATTACHMENT_SINGLE)[0]
if (imgField) {
addBinding("cardImageURL", null, imgField, "url")
} else {
// Then try multi-attachment fields if no single ones exist
imgField = findFieldTypes(FieldType.ATTACHMENTS)[0]
if (imgField) {
addBinding("cardImageURL", null, imgField, 0, "url")
}
}
}
}
}
} }
/** /**
@ -324,21 +399,21 @@ export class ComponentStore extends BudiStore {
...presetProps, ...presetProps,
} }
// Enrich empty settings // Standard post processing
this.enrichEmptySettings(instance, { this.enrichEmptySettings(instance, {
parent, parent,
screen: get(selectedScreen), screen: get(selectedScreen),
useDefaultValues: true, useDefaultValues: true,
}) })
// Migrate nested component settings
this.migrateSettings(instance) this.migrateSettings(instance)
// Add any extra properties the component needs // Custom post processing for creation only
let extras = {} let extras = {}
if (definition.hasChildren) { if (definition.hasChildren) {
extras._children = [] extras._children = []
} }
// Add step name to form steps
if (componentName.endsWith("/formstep")) { if (componentName.endsWith("/formstep")) {
const parentForm = findClosestMatchingComponent( const parentForm = findClosestMatchingComponent(
get(selectedScreen).props, get(selectedScreen).props,
@ -351,6 +426,7 @@ export class ComponentStore extends BudiStore {
extras.step = formSteps.length + 1 extras.step = formSteps.length + 1
extras._instanceName = `Step ${formSteps.length + 1}` extras._instanceName = `Step ${formSteps.length + 1}`
} }
return { return {
...cloneDeep(instance), ...cloneDeep(instance),
...extras, ...extras,
@ -463,7 +539,6 @@ export class ComponentStore extends BudiStore {
if (!componentId || !screenId) { if (!componentId || !screenId) {
const state = get(this.store) const state = get(this.store)
componentId = componentId || state.selectedComponentId componentId = componentId || state.selectedComponentId
const screenState = get(screenStore) const screenState = get(screenStore)
screenId = screenId || screenState.selectedScreenId screenId = screenId || screenState.selectedScreenId
} }
@ -471,7 +546,6 @@ export class ComponentStore extends BudiStore {
return return
} }
const patchScreen = screen => { const patchScreen = screen => {
// findComponent looks in the tree not comp.settings[0]
let component = findComponent(screen.props, componentId) let component = findComponent(screen.props, componentId)
if (!component) { if (!component) {
return false return false
@ -480,7 +554,7 @@ export class ComponentStore extends BudiStore {
// Mutates the fetched component with updates // Mutates the fetched component with updates
const patchResult = patchFn(component, screen) const patchResult = patchFn(component, screen)
// Mutates the component with any required settings updates // Post processing
const migrated = this.migrateSettings(component) const migrated = this.migrateSettings(component)
// Returning an explicit false signifies that we should skip this // Returning an explicit false signifies that we should skip this

View File

@ -23,6 +23,7 @@ import {
DB_TYPE_EXTERNAL, DB_TYPE_EXTERNAL,
DEFAULT_BB_DATASOURCE_ID, DEFAULT_BB_DATASOURCE_ID,
} from "constants/backend" } from "constants/backend"
import { makePropSafe as safe } from "@budibase/string-templates"
// Could move to fixtures // Could move to fixtures
const COMP_PREFIX = "@budibase/standard-components" const COMP_PREFIX = "@budibase/standard-components"
@ -360,8 +361,30 @@ describe("Component store", () => {
resourceId: internalTableDoc._id, resourceId: internalTableDoc._id,
type: "table", type: "table",
}) })
return comp
} }
it("enrichEmptySettings - initialise cards blocks with correct fields", async ctx => {
const comp = enrichSettingsDS("cardsblock", ctx)
const expectBinding = (setting, ...parts) => {
expect(comp[setting]).toStrictEqual(
`{{ ${safe(`${comp._id}-repeater`)}.${parts.map(safe).join(".")} }}`
)
}
expectBinding("cardTitle", internalTableDoc.schema.MediaTitle.name)
expectBinding("cardSubtitle", internalTableDoc.schema.MediaVersion.name)
expectBinding(
"cardDescription",
internalTableDoc.schema.MediaDescription.name
)
expectBinding(
"cardImageURL",
internalTableDoc.schema.MediaImage.name,
"url"
)
})
it("enrichEmptySettings - set default datasource for 'table' setting type", async ctx => { it("enrichEmptySettings - set default datasource for 'table' setting type", async ctx => {
enrichSettingsDS("formblock", ctx) enrichSettingsDS("formblock", ctx)
}) })

View File

@ -8,6 +8,7 @@ import {
DB_TYPE_EXTERNAL, DB_TYPE_EXTERNAL,
DEFAULT_BB_DATASOURCE_ID, DEFAULT_BB_DATASOURCE_ID,
} from "constants/backend" } from "constants/backend"
import { FieldType } from "@budibase/types"
const getDocId = () => { const getDocId = () => {
return v4().replace(/-/g, "") return v4().replace(/-/g, "")
@ -45,6 +46,52 @@ export const COMPONENT_DEFINITIONS = {
}, },
], ],
}, },
cardsblock: {
block: true,
name: "Cards Block",
settings: [
{
type: "dataSource",
label: "Data",
key: "dataSource",
required: true,
},
{
section: true,
name: "Cards",
settings: [
{
type: "text",
key: "cardTitle",
label: "Title",
nested: true,
resetOn: "dataSource",
},
{
type: "text",
key: "cardSubtitle",
label: "Subtitle",
nested: true,
resetOn: "dataSource",
},
{
type: "text",
key: "cardDescription",
label: "Description",
nested: true,
resetOn: "dataSource",
},
{
type: "text",
key: "cardImageURL",
label: "Image URL",
nested: true,
resetOn: "dataSource",
},
],
},
],
},
container: { container: {
name: "Container", name: "Container",
}, },
@ -262,14 +309,23 @@ export const internalTableDoc = {
name: "Media", name: "Media",
sourceId: BUDIBASE_INTERNAL_DB_ID, sourceId: BUDIBASE_INTERNAL_DB_ID,
sourceType: DB_TYPE_INTERNAL, sourceType: DB_TYPE_INTERNAL,
primaryDisplay: "MediaTitle",
schema: { schema: {
MediaTitle: { MediaTitle: {
name: "MediaTitle", name: "MediaTitle",
type: "string", type: FieldType.STRING,
}, },
MediaVersion: { MediaVersion: {
name: "MediaVersion", name: "MediaVersion",
type: "string", type: FieldType.STRING,
},
MediaDescription: {
name: "MediaDescription",
type: FieldType.LONGFORM,
},
MediaImage: {
name: "MediaImage",
type: FieldType.ATTACHMENT_SINGLE,
}, },
}, },
} }

View File

@ -6243,27 +6243,28 @@
"key": "cardTitle", "key": "cardTitle",
"label": "Title", "label": "Title",
"nested": true, "nested": true,
"defaultValue": "Title" "resetOn": "dataSource"
}, },
{ {
"type": "text", "type": "text",
"key": "cardSubtitle", "key": "cardSubtitle",
"label": "Subtitle", "label": "Subtitle",
"nested": true, "nested": true,
"defaultValue": "Subtitle" "resetOn": "dataSource"
}, },
{ {
"type": "text", "type": "text",
"key": "cardDescription", "key": "cardDescription",
"label": "Description", "label": "Description",
"nested": true, "nested": true,
"defaultValue": "Description" "resetOn": "dataSource"
}, },
{ {
"type": "text", "type": "text",
"key": "cardImageURL", "key": "cardImageURL",
"label": "Image URL", "label": "Image URL",
"nested": true "nested": true,
"resetOn": "dataSource"
}, },
{ {
"type": "boolean", "type": "boolean",