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}