diff --git a/packages/builder/cypress/integration/createView.spec.js b/packages/builder/cypress/integration/createView.spec.js
index 06fac6da33..bdc89bec53 100644
--- a/packages/builder/cypress/integration/createView.spec.js
+++ b/packages/builder/cypress/integration/createView.spec.js
@@ -28,7 +28,7 @@ context("Create a View", () => {
const headers = Array.from($headers).map(header =>
header.textContent.trim()
)
- expect(headers).to.deep.eq(["group", "age", "rating"])
+ expect(headers).to.deep.eq([ 'rating', 'age', 'group' ])
})
})
@@ -53,27 +53,20 @@ context("Create a View", () => {
cy.wait(50)
cy.get(".menu-container").find("select").eq(1).select("age")
cy.contains("Save").click()
+ cy.wait(100)
cy.get(".ag-center-cols-viewport").scrollTo("100%")
cy.get("[data-cy=table-header]").then($headers => {
expect($headers).to.have.length(7)
const headers = Array.from($headers).map(header =>
header.textContent.trim()
)
- expect(headers).to.deep.eq([
- "field",
- "sum",
- "min",
- "max",
- "count",
- "sumsqr",
- "avg",
- ])
+ expect(headers).to.deep.eq([ 'avg', 'sumsqr', 'count', 'max', 'min', 'sum', 'field' ])
})
cy.get(".ag-cell").then($values => {
- const values = Array.from($values).map(header =>
+ let values = Array.from($values).map(header =>
header.textContent.trim()
)
- expect(values).to.deep.eq(["age", "155", "20", "49", "5", "5347", "31"])
+ expect(values).to.deep.eq([ '31', '5347', '5', '49', '20', '155', 'age' ])
})
})
@@ -92,15 +85,7 @@ context("Create a View", () => {
.find(".ag-cell")
.then($values => {
const values = Array.from($values).map(value => value.textContent)
- expect(values).to.deep.eq([
- "Students",
- "70",
- "20",
- "25",
- "3",
- "1650",
- "23.333333333333332",
- ])
+ expect(values).to.deep.eq([ 'Students', '23.333333333333332', '1650', '3', '25', '20', '70' ])
})
})
diff --git a/packages/builder/package.json b/packages/builder/package.json
index 0d940cfac0..94ef8dc3c4 100644
--- a/packages/builder/package.json
+++ b/packages/builder/package.json
@@ -63,7 +63,7 @@
}
},
"dependencies": {
- "@budibase/bbui": "^1.58.5",
+ "@budibase/bbui": "^1.58.8",
"@budibase/client": "^0.7.8",
"@budibase/colorpicker": "1.0.1",
"@budibase/string-templates": "^0.7.8",
diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js
index abd8fdce85..55a6328ca3 100644
--- a/packages/builder/src/builderStore/dataBinding.js
+++ b/packages/builder/src/builderStore/dataBinding.js
@@ -11,21 +11,24 @@ const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g
/**
* Gets all bindable data context fields and instance fields.
*/
-export const getBindableProperties = (rootComponent, componentId) => {
- return getContextBindings(rootComponent, componentId)
+export const getBindableProperties = (asset, componentId) => {
+ const contextBindings = getContextBindings(asset, componentId)
+ const userBindings = getUserBindings()
+ const urlBindings = getUrlBindings(asset, componentId)
+ return [...contextBindings, ...userBindings, ...urlBindings]
}
/**
* Gets all data provider components above a component.
*/
-export const getDataProviderComponents = (rootComponent, componentId) => {
- if (!rootComponent || !componentId) {
+export const getDataProviderComponents = (asset, componentId) => {
+ if (!asset || !componentId) {
return []
}
// Get the component tree leading up to this component, ignoring the component
// itself
- const path = findComponentPath(rootComponent, componentId)
+ const path = findComponentPath(asset.props, componentId)
path.pop()
// Filter by only data provider components
@@ -38,18 +41,14 @@ export const getDataProviderComponents = (rootComponent, componentId) => {
/**
* Gets all data provider components above a component.
*/
-export const getActionProviderComponents = (
- rootComponent,
- componentId,
- actionType
-) => {
- if (!rootComponent || !componentId) {
+export const getActionProviderComponents = (asset, componentId, actionType) => {
+ if (!asset || !componentId) {
return []
}
// Get the component tree leading up to this component, ignoring the component
// itself
- const path = findComponentPath(rootComponent, componentId)
+ const path = findComponentPath(asset.props, componentId)
path.pop()
// Filter by only data provider components
@@ -92,13 +91,12 @@ export const getDatasourceForProvider = component => {
}
/**
- * Gets all bindable data contexts. These are fields of schemas of data contexts
- * provided by data provider components, such as lists or row detail components.
+ * Gets all bindable data properties from component data contexts.
*/
-export const getContextBindings = (rootComponent, componentId) => {
+const getContextBindings = (asset, componentId) => {
// Extract any components which provide data contexts
- const dataProviders = getDataProviderComponents(rootComponent, componentId)
- let contextBindings = []
+ const dataProviders = getDataProviderComponents(asset, componentId)
+ let bindings = []
// Create bindings for each data provider
dataProviders.forEach(component => {
@@ -109,7 +107,7 @@ export const getContextBindings = (rootComponent, componentId) => {
// Forms are an edge case which do not need table schemas
if (isForm) {
schema = buildFormSchema(component)
- tableName = "Schema"
+ tableName = "Fields"
} else {
if (!datasource) {
return
@@ -143,7 +141,7 @@ export const getContextBindings = (rootComponent, componentId) => {
runtimeBoundKey = `${key}_first`
}
- contextBindings.push({
+ bindings.push({
type: "context",
runtimeBinding: `${makePropSafe(component._id)}.${makePropSafe(
runtimeBoundKey
@@ -157,7 +155,14 @@ export const getContextBindings = (rootComponent, componentId) => {
})
})
- // Add logged in user bindings
+ return bindings
+}
+
+/**
+ * Gets all bindable properties from the logged in user.
+ */
+const getUserBindings = () => {
+ let bindings = []
const tables = get(backendUiStore).tables
const userTable = tables.find(table => table._id === TableNames.USERS)
const schema = {
@@ -176,7 +181,7 @@ export const getContextBindings = (rootComponent, componentId) => {
runtimeBoundKey = `${key}_first`
}
- contextBindings.push({
+ bindings.push({
type: "context",
runtimeBinding: `user.${runtimeBoundKey}`,
readableBinding: `Current User.${key}`,
@@ -187,7 +192,26 @@ export const getContextBindings = (rootComponent, componentId) => {
})
})
- return contextBindings
+ return bindings
+}
+
+/**
+ * Gets all bindable properties from URL parameters.
+ */
+const getUrlBindings = asset => {
+ const url = asset?.routing?.route ?? ""
+ const split = url.split("/")
+ let params = []
+ split.forEach(part => {
+ if (part.startsWith(":") && part.length > 1) {
+ params.push(part.replace(/:/g, "").replace(/\?/g, ""))
+ }
+ })
+ return params.map(param => ({
+ type: "context",
+ runtimeBinding: `url.${param}`,
+ readableBinding: `URL.${param}`,
+ }))
}
/**
diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js
index 4c127fbe0b..ee7a4da0fd 100644
--- a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js
+++ b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js
@@ -179,6 +179,10 @@ export function makeDatasourceFormComponents(datasource) {
let fields = Object.keys(schema || {})
fields.forEach(field => {
const fieldSchema = schema[field]
+ // skip autocolumns
+ if (fieldSchema.autocolumn) {
+ return
+ }
const fieldType =
typeof fieldSchema === "object" ? fieldSchema.type : fieldSchema
const componentType = fieldTypeToComponentMap[fieldType]
diff --git a/packages/builder/src/builderStore/utils.js b/packages/builder/src/builderStore/utils.js
new file mode 100644
index 0000000000..3ddf6fb667
--- /dev/null
+++ b/packages/builder/src/builderStore/utils.js
@@ -0,0 +1,55 @@
+import { TableNames } from "../constants"
+import {
+ AUTO_COLUMN_DISPLAY_NAMES,
+ AUTO_COLUMN_SUB_TYPES,
+ FIELDS,
+ isAutoColumnUserRelationship,
+} from "../constants/backend"
+
+export function getAutoColumnInformation(enabled = true) {
+ let info = {}
+ for (let [key, subtype] of Object.entries(AUTO_COLUMN_SUB_TYPES)) {
+ info[subtype] = { enabled, name: AUTO_COLUMN_DISPLAY_NAMES[key] }
+ }
+ return info
+}
+
+export function buildAutoColumn(tableName, name, subtype) {
+ let type, constraints
+ switch (subtype) {
+ case AUTO_COLUMN_SUB_TYPES.UPDATED_BY:
+ case AUTO_COLUMN_SUB_TYPES.CREATED_BY:
+ type = FIELDS.LINK.type
+ constraints = FIELDS.LINK.constraints
+ break
+ case AUTO_COLUMN_SUB_TYPES.AUTO_ID:
+ type = FIELDS.NUMBER.type
+ constraints = FIELDS.NUMBER.constraints
+ break
+ case AUTO_COLUMN_SUB_TYPES.UPDATED_AT:
+ case AUTO_COLUMN_SUB_TYPES.CREATED_AT:
+ type = FIELDS.DATETIME.type
+ constraints = FIELDS.DATETIME.constraints
+ break
+ default:
+ type = FIELDS.STRING.type
+ constraints = FIELDS.STRING.constraints
+ break
+ }
+ if (Object.values(AUTO_COLUMN_SUB_TYPES).indexOf(subtype) === -1) {
+ throw "Cannot build auto column with supplied subtype"
+ }
+ const base = {
+ name,
+ type,
+ subtype,
+ icon: "ri-magic-line",
+ autocolumn: true,
+ constraints,
+ }
+ if (isAutoColumnUserRelationship(subtype)) {
+ base.tableId = TableNames.USERS
+ base.fieldName = `${tableName}-${name}`
+ }
+ return base
+}
diff --git a/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte b/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte
index 43fa4b8bf1..7b010f2ecb 100644
--- a/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte
+++ b/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte
@@ -30,20 +30,22 @@
{#if schemaFields.length}
{#each schemaFields as [field, schema]}
- {#if schemaHasOptions(schema)}
-
- {:else if schema.type === 'string' || schema.type === 'number'}
-
+ {#if !schema.autocolumn}
+ {#if schemaHasOptions(schema)}
+
+ {:else if schema.type === 'string' || schema.type === 'number'}
+
+ {/if}
{/if}
{/each}
diff --git a/packages/builder/src/components/backend/DataTable/DataTable.svelte b/packages/builder/src/components/backend/DataTable/DataTable.svelte
index 577fda62a8..5f6f230210 100644
--- a/packages/builder/src/components/backend/DataTable/DataTable.svelte
+++ b/packages/builder/src/components/backend/DataTable/DataTable.svelte
@@ -6,15 +6,16 @@
import ExportButton from "./buttons/ExportButton.svelte"
import EditRolesButton from "./buttons/EditRolesButton.svelte"
import ManageAccessButton from "./buttons/ManageAccessButton.svelte"
+ import HideAutocolumnButton from "./buttons/HideAutocolumnButton.svelte"
import * as api from "./api"
import Table from "./Table.svelte"
import { TableNames } from "constants"
import CreateEditUser from "./modals/CreateEditUser.svelte"
import CreateEditRow from "./modals/CreateEditRow.svelte"
+ let hideAutocolumns = true
let data = []
let loading = false
-
$: isUsersTable = $backendUiStore.selectedTable?._id === TableNames.USERS
$: title = $backendUiStore.selectedTable.name
$: schema = $backendUiStore.selectedTable.schema
@@ -41,6 +42,7 @@
tableId={$backendUiStore.selectedTable?._id}
{data}
allowEditing={true}
+ bind:hideAutocolumns
{loading}>
{#if schema && Object.keys(schema).length > 0}
@@ -49,9 +51,11 @@
modalContentComponent={isUsersTable ? CreateEditUser : CreateEditRow} />
+ {#if isUsersTable}
+
+ {/if}
+
+
{/if}
- {#if isUsersTable}
-
- {/if}
diff --git a/packages/builder/src/components/backend/DataTable/Table.svelte b/packages/builder/src/components/backend/DataTable/Table.svelte
index 070ebec080..195876cf05 100644
--- a/packages/builder/src/components/backend/DataTable/Table.svelte
+++ b/packages/builder/src/components/backend/DataTable/Table.svelte
@@ -24,6 +24,7 @@
export let allowEditing = false
export let loading = false
export let theme = "alpine"
+ export let hideAutocolumns
let columnDefs = []
let selectedRows = []
@@ -85,8 +86,12 @@
return !(isUsersTable && ["email", "roleId"].includes(key))
}
- Object.entries(schema || {}).forEach(([key, value]) => {
- result.push({
+ for (let [key, value] of Object.entries(schema || {})) {
+ // skip autocolumns if hiding
+ if (hideAutocolumns && value.autocolumn) {
+ continue
+ }
+ let config = {
headerCheckboxSelection: false,
headerComponent: TableHeader,
headerComponentParams: {
@@ -107,9 +112,14 @@
autoHeight: true,
resizable: true,
minWidth: 200,
- })
- })
-
+ }
+ // sort auto-columns to the end if they are present
+ if (value.autocolumn) {
+ result.push(config)
+ } else {
+ result.unshift(config)
+ }
+ }
columnDefs = result
}
@@ -150,13 +160,15 @@
-
(selectedRows = detail)} />
+ {#key columnDefs.length}
+ (selectedRows = detail)} />
+ {/key}
diff --git a/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte b/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte
index f875fa8849..4ac5ec80a8 100644
--- a/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte
+++ b/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte
@@ -7,8 +7,10 @@
import FilterButton from "./buttons/FilterButton.svelte"
import ExportButton from "./buttons/ExportButton.svelte"
import ManageAccessButton from "./buttons/ManageAccessButton.svelte"
+ import HideAutocolumnButton from "./buttons/HideAutocolumnButton.svelte"
export let view = {}
+ let hideAutocolumns = true
let data = []
let loading = false
@@ -48,12 +50,18 @@
}
-
+
{#if view.calculation}
{/if}
+
diff --git a/packages/builder/src/components/backend/DataTable/buttons/HideAutocolumnButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/HideAutocolumnButton.svelte
new file mode 100644
index 0000000000..fc50bfbfef
--- /dev/null
+++ b/packages/builder/src/components/backend/DataTable/buttons/HideAutocolumnButton.svelte
@@ -0,0 +1,27 @@
+
+
+
+
+ {#if hideAutocolumns}
+
+ Show Auto Columns
+ {:else} Hide Auto Columns{/if}
+
+
+
+
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
index bc18b7559c..8ae492226f 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
@@ -10,12 +10,14 @@
import { cloneDeep } from "lodash/fp"
import { backendUiStore } from "builderStore"
import { TableNames, UNEDITABLE_USER_FIELDS } from "constants"
- import { FIELDS } from "constants/backend"
+ import { FIELDS, AUTO_COLUMN_SUB_TYPES } from "constants/backend"
+ import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils"
import { notifier } from "builderStore/store/notifications"
import ValuesList from "components/common/ValuesList.svelte"
import DatePicker from "components/common/DatePicker.svelte"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
+ const AUTO_COL = "auto"
let fieldDefinitions = cloneDeep(FIELDS)
export let onClosed
@@ -43,7 +45,23 @@
$backendUiStore.selectedTable?._id === TableNames.USERS &&
UNEDITABLE_USER_FIELDS.includes(field.name)
+ // used to select what different options can be displayed for column type
+ $: canBeSearched =
+ field.type !== "link" &&
+ field.subtype !== AUTO_COLUMN_SUB_TYPES.CREATED_BY &&
+ field.subtype !== AUTO_COLUMN_SUB_TYPES.UPDATED_BY
+ $: canBeDisplay = field.type !== "link" && field.type !== AUTO_COL
+ $: canBeRequired =
+ field.type !== "link" && !uneditable && field.type !== AUTO_COL
+
async function saveColumn() {
+ if (field.type === AUTO_COL) {
+ field = buildAutoColumn(
+ $backendUiStore.draftTable.name,
+ field.name,
+ field.subtype
+ )
+ }
backendUiStore.update(state => {
backendUiStore.actions.tables.saveField({
originalName,
@@ -67,11 +85,12 @@
}
function handleFieldConstraints(event) {
- const { type, constraints } = fieldDefinitions[
- event.target.value.toUpperCase()
- ]
- field.type = type
- field.constraints = constraints
+ const definition = fieldDefinitions[event.target.value.toUpperCase()]
+ if (!definition) {
+ return
+ }
+ field.type = definition.type
+ field.constraints = definition.constraints
}
function onChangeRequired(e) {
@@ -124,9 +143,10 @@
{#each Object.values(fieldDefinitions) as field}
{/each}
+
- {#if field.type !== 'link' && !uneditable}
+ {#if canBeRequired}
{/if}
- {#if field.type !== 'link'}
+ {#if canBeDisplay}
+ {/if}
+ {#if canBeSearched}
+ {:else if field.type === AUTO_COL}
+
{/if}