Changing record -> row in this update, completing the update of renaming in the builder, this release needs further testing.
This commit is contained in:
parent
fdaa69ee7f
commit
5d49d529e3
|
@ -145,7 +145,7 @@ The HTML and CSS for your apps runtime pages, as well as the budibase client lib
|
|||
|
||||
#### Backend
|
||||
|
||||
The backend schema, tables and records are stored using PouchDB when developing locally, and in [CouchDB](https://pouchdb.com/) when running in production.
|
||||
/he backend schema, tables and rows are stored using PouchDB when developing locally, and in [CouchDB](https://pouchdb.com/) when running in production.
|
||||
|
||||
### Publishing Budibase to NPM
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ context("Create a automation", () => {
|
|||
cy.contains("automate").click()
|
||||
cy.contains("Create New Automation").click()
|
||||
cy.get(".modal").within(() => {
|
||||
cy.get("input").type("Add Record")
|
||||
cy.get("input").type("Add Row")
|
||||
cy.get(".buttons")
|
||||
.contains("Create")
|
||||
.click()
|
||||
|
@ -24,7 +24,7 @@ context("Create a automation", () => {
|
|||
|
||||
// Add trigger
|
||||
cy.get("[data-cy=add-automation-component]").click()
|
||||
cy.get("[data-cy=RECORD_SAVED]").click()
|
||||
cy.get("[data-cy=ROW_SAVED]").click()
|
||||
cy.get("[data-cy=automation-block-setup]").within(() => {
|
||||
cy.get("select")
|
||||
.first()
|
||||
|
@ -32,7 +32,7 @@ context("Create a automation", () => {
|
|||
})
|
||||
|
||||
// Create action
|
||||
cy.get("[data-cy=CREATE_RECORD]").click()
|
||||
cy.get("[data-cy=CREATE_ROW]").click()
|
||||
cy.get("[data-cy=automation-block-setup]").within(() => {
|
||||
cy.get("select")
|
||||
.first()
|
||||
|
@ -53,9 +53,9 @@ context("Create a automation", () => {
|
|||
cy.get(".stop-button.highlighted").should("be.visible")
|
||||
})
|
||||
|
||||
it("should add record when a new record is added", () => {
|
||||
it("should add row when a new row is added", () => {
|
||||
cy.contains("backend").click()
|
||||
cy.addRecord(["Rover", 15])
|
||||
cy.addRow(["Rover", 15])
|
||||
cy.reload()
|
||||
cy.contains("goodboy").should("have.text", "goodboy")
|
||||
})
|
||||
|
|
|
@ -6,7 +6,7 @@ xcontext('Create Components', () => {
|
|||
// https://on.cypress.io/type
|
||||
cy.createApp('Model App', 'Model App Description')
|
||||
cy.createTable('dog', 'name', 'age')
|
||||
cy.addRecord('bob', '15')
|
||||
cy.addRow('bob', '15')
|
||||
})
|
||||
|
||||
// https://on.cypress.io/interacting-with-elements
|
||||
|
|
|
@ -16,8 +16,8 @@ context("Create a Table", () => {
|
|||
cy.contains("name").should("be.visible")
|
||||
})
|
||||
|
||||
it("creates a record in the table", () => {
|
||||
cy.addRecord(["Rover"])
|
||||
it("creates a row in the table", () => {
|
||||
cy.addRow(["Rover"])
|
||||
cy.contains("Rover").should("be.visible")
|
||||
})
|
||||
|
||||
|
@ -32,7 +32,7 @@ context("Create a Table", () => {
|
|||
cy.contains("nameupdated").should("have.text", "nameupdated")
|
||||
})
|
||||
|
||||
it("edits a record", () => {
|
||||
it("edits a row", () => {
|
||||
cy.get("tbody .ri-more-line").click()
|
||||
cy.get("[data-cy=edit-row]").click()
|
||||
cy.get(".modal input").type("Updated")
|
||||
|
@ -40,7 +40,7 @@ context("Create a Table", () => {
|
|||
cy.contains("RoverUpdated").should("have.text", "RoverUpdated")
|
||||
})
|
||||
|
||||
it("deletes a record", () => {
|
||||
it("deletes a row", () => {
|
||||
cy.get("tbody .ri-more-line").click()
|
||||
cy.get("[data-cy=delete-row]").click()
|
||||
cy.contains("Delete Row").click()
|
||||
|
|
|
@ -7,13 +7,13 @@ context("Create a View", () => {
|
|||
cy.addColumn("data", "age", "Number")
|
||||
cy.addColumn("data", "rating", "Number")
|
||||
|
||||
// 6 Records
|
||||
cy.addRecord(["Students", 25, 1])
|
||||
cy.addRecord(["Students", 20, 3])
|
||||
cy.addRecord(["Students", 18, 6])
|
||||
cy.addRecord(["Students", 25, 2])
|
||||
cy.addRecord(["Teachers", 49, 5])
|
||||
cy.addRecord(["Teachers", 36, 3])
|
||||
// 6 Rows
|
||||
cy.addRow(["Students", 25, 1])
|
||||
cy.addRow(["Students", 20, 3])
|
||||
cy.addRow(["Students", 18, 6])
|
||||
cy.addRow(["Students", 25, 2])
|
||||
cy.addRow(["Teachers", 49, 5])
|
||||
cy.addRow(["Teachers", 36, 3])
|
||||
})
|
||||
|
||||
it("creates a view", () => {
|
||||
|
|
|
@ -92,7 +92,7 @@ Cypress.Commands.add("addColumn", (tableName, columnName, type) => {
|
|||
})
|
||||
})
|
||||
|
||||
Cypress.Commands.add("addRecord", values => {
|
||||
Cypress.Commands.add("addRow", values => {
|
||||
cy.contains("Create New Row").click()
|
||||
|
||||
cy.get(".modal").within(() => {
|
||||
|
|
|
@ -26,11 +26,11 @@ export default {
|
|||
],
|
||||
trigger: {
|
||||
id: "iRzYMOqND",
|
||||
name: "Record Saved",
|
||||
event: "record:save",
|
||||
name: "Row Saved",
|
||||
event: "row:save",
|
||||
icon: "ri-save-line",
|
||||
tagline: "Record is added to <b>{{table.name}}</b>",
|
||||
description: "Fired when a record is saved to your database.",
|
||||
tagline: "Row is added to <b>{{table.name}}</b>",
|
||||
description: "Fired when a row is saved to your database.",
|
||||
params: { table: "table" },
|
||||
type: "TRIGGER",
|
||||
args: {
|
||||
|
@ -65,7 +65,7 @@ export default {
|
|||
_rev: "7-b8aa1ce0b53e88928bb88fc11bdc0aff",
|
||||
},
|
||||
},
|
||||
stepId: "RECORD_SAVED",
|
||||
stepId: "ROW_SAVED",
|
||||
},
|
||||
},
|
||||
type: "automation",
|
||||
|
|
|
@ -27,7 +27,7 @@ export const getBackendUiStore = () => {
|
|||
})
|
||||
},
|
||||
},
|
||||
records: {
|
||||
rows: {
|
||||
save: () =>
|
||||
store.update(state => {
|
||||
state.selectedView = state.selectedView
|
||||
|
@ -38,9 +38,9 @@ export const getBackendUiStore = () => {
|
|||
state.selectedView = state.selectedView
|
||||
return state
|
||||
}),
|
||||
select: record =>
|
||||
select: row =>
|
||||
store.update(state => {
|
||||
state.selectedRecord = record
|
||||
state.selectedRow = row
|
||||
return state
|
||||
}),
|
||||
},
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
function enrichInputs(inputs) {
|
||||
let enrichedInputs = { ...inputs, enriched: {} }
|
||||
const tableId = inputs.tableId || inputs.record?.tableId
|
||||
const tableId = inputs.tableId || inputs.row?.tableId
|
||||
if (tableId) {
|
||||
enrichedInputs.enriched.table = $backendUiStore.tables.find(
|
||||
table => table._id === tableId
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import TableSelector from "./ParamInputs/TableSelector.svelte"
|
||||
import RecordSelector from "./ParamInputs/RecordSelector.svelte"
|
||||
import RowSelector from "./ParamInputs/RowSelector.svelte"
|
||||
import { Input, TextArea, Select, Label } from "@budibase/bbui"
|
||||
import { automationStore } from "builderStore"
|
||||
import BindableInput from "../../userInterface/BindableInput.svelte"
|
||||
|
@ -62,8 +62,8 @@
|
|||
<Input type="password" thin bind:value={block.inputs[key]} />
|
||||
{:else if value.customType === 'table'}
|
||||
<TableSelector bind:value={block.inputs[key]} />
|
||||
{:else if value.customType === 'record'}
|
||||
<RecordSelector bind:value={block.inputs[key]} {bindings} />
|
||||
{:else if value.customType === 'row'}
|
||||
<RowSelector bind:value={block.inputs[key]} {bindings} />
|
||||
{:else if value.type === 'string' || value.type === 'number'}
|
||||
<BindableInput
|
||||
type="string"
|
||||
|
|
|
@ -17,12 +17,12 @@
|
|||
name: $backendUiStore.selectedView.name,
|
||||
}
|
||||
|
||||
// Fetch records for specified table
|
||||
// Fetch rows for specified table
|
||||
$: {
|
||||
if ($backendUiStore.selectedView?.name?.startsWith("all_")) {
|
||||
loading = true
|
||||
api.fetchDataForView($backendUiStore.selectedView).then(records => {
|
||||
data = records || []
|
||||
api.fetchDataForView($backendUiStore.selectedView).then(rows => {
|
||||
data = rows || []
|
||||
loading = false
|
||||
})
|
||||
}
|
||||
|
|
|
@ -5,36 +5,36 @@
|
|||
import { backendUiStore } from "builderStore"
|
||||
|
||||
export let tableId
|
||||
export let recordId
|
||||
export let rowId
|
||||
export let fieldName
|
||||
|
||||
let record
|
||||
let row
|
||||
let title
|
||||
|
||||
$: data = record?.[fieldName] ?? []
|
||||
$: data = row?.[fieldName] ?? []
|
||||
$: linkedTableId = data?.length ? data[0].tableId : null
|
||||
$: linkedTable = $backendUiStore.tables.find(
|
||||
table => table._id === linkedTableId
|
||||
)
|
||||
$: schema = linkedTable?.schema
|
||||
$: table = $backendUiStore.tables.find(table => table._id === tableId)
|
||||
$: fetchData(tableId, recordId)
|
||||
$: fetchData(tableId, rowId)
|
||||
$: {
|
||||
let recordLabel = record?.[table?.primaryDisplay]
|
||||
if (recordLabel) {
|
||||
title = `${recordLabel} - ${fieldName}`
|
||||
let rowLabel = row?.[table?.primaryDisplay]
|
||||
if (rowLabel) {
|
||||
title = `${rowLabel} - ${fieldName}`
|
||||
} else {
|
||||
title = fieldName
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchData(tableId, recordId) {
|
||||
const QUERY_VIEW_URL = `/api/${tableId}/${recordId}/enrich`
|
||||
async function fetchData(tableId, rowId) {
|
||||
const QUERY_VIEW_URL = `/api/${tableId}/${rowId}/enrich`
|
||||
const response = await api.get(QUERY_VIEW_URL)
|
||||
record = await response.json()
|
||||
row = await response.json()
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if record && record._id === recordId}
|
||||
{#if row && row._id === rowId}
|
||||
<Table {title} {schema} {data} />
|
||||
{/if}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { Input, Select, Label, DatePicker, Toggle } from "@budibase/bbui"
|
||||
import Dropzone from "components/common/Dropzone.svelte"
|
||||
import { capitalise } from "../../../helpers"
|
||||
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte"
|
||||
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
|
||||
|
||||
export let meta
|
||||
export let value = meta.type === "boolean" ? false : ""
|
||||
|
@ -28,7 +28,7 @@
|
|||
{:else if type === 'boolean'}
|
||||
<Toggle text={label} bind:checked={value} data-cy="{meta.name}-input" />
|
||||
{:else if type === 'link'}
|
||||
<LinkedRecordSelector bind:linkedRecords={value} schema={meta} />
|
||||
<LinkedRowSelector bind:linkedRows={value} schema={meta} />
|
||||
{:else}
|
||||
<Input thin {label} data-cy="{meta.name}-input" {type} bind:value />
|
||||
{/if}
|
|
@ -10,7 +10,7 @@
|
|||
import ActionButton from "components/common/ActionButton.svelte"
|
||||
import AttachmentList from "./AttachmentList.svelte"
|
||||
import TablePagination from "./TablePagination.svelte"
|
||||
import CreateEditRecordModal from "./modals/CreateEditRecordModal.svelte"
|
||||
import CreateEditRowModal from "./modals/CreateEditRowModal.svelte"
|
||||
import RowPopover from "./buttons/CreateRowButton.svelte"
|
||||
import ColumnPopover from "./buttons/CreateColumnButton.svelte"
|
||||
import ViewPopover from "./buttons/CreateViewButton.svelte"
|
||||
|
@ -41,12 +41,12 @@
|
|||
: []
|
||||
$: tableId = data?.length ? data[0].tableId : null
|
||||
|
||||
function selectRelationship(record, fieldName) {
|
||||
if (!record?.[fieldName]?.length) {
|
||||
function selectRelationship(row, fieldName) {
|
||||
if (!row?.[fieldName]?.length) {
|
||||
return
|
||||
}
|
||||
$goto(
|
||||
`/${$params.application}/backend/table/${tableId}/relationship/${record._id}/${fieldName}`
|
||||
`/${$params.application}/backend/table/${tableId}/relationship/${row._id}/${fieldName}`
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
$: name = view.name
|
||||
|
||||
// Fetch records for specified view
|
||||
// Fetch rows for specified view
|
||||
$: {
|
||||
if (!name.startsWith("all_")) {
|
||||
fetchViewData(name, view.field, view.groupBy)
|
||||
|
|
|
@ -6,22 +6,22 @@ export async function createUser(user) {
|
|||
return await response.json()
|
||||
}
|
||||
|
||||
export async function saveRecord(record, tableId) {
|
||||
const SAVE_RECORDS_URL = `/api/${tableId}/records`
|
||||
const response = await api.post(SAVE_RECORDS_URL, record)
|
||||
export async function saveRow(row, tableId) {
|
||||
const SAVE_ROWS_URL = `/api/${tableId}/rows`
|
||||
const response = await api.post(SAVE_ROWS_URL, row)
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
export async function deleteRecord(record) {
|
||||
const DELETE_RECORDS_URL = `/api/${record.tableId}/records/${record._id}/${record._rev}`
|
||||
const response = await api.delete(DELETE_RECORDS_URL)
|
||||
export async function deleteRow(row) {
|
||||
const DELETE_ROWS_URL = `/api/${row.tableId}/rows/${row._id}/${row._rev}`
|
||||
const response = await api.delete(DELETE_ROWS_URL)
|
||||
return response
|
||||
}
|
||||
|
||||
export async function fetchDataForView(view) {
|
||||
const FETCH_RECORDS_URL = `/api/views/${view.name}`
|
||||
const FETCH_ROWS_URL = `/api/views/${view.name}`
|
||||
|
||||
const response = await api.get(FETCH_RECORDS_URL)
|
||||
const response = await api.get(FETCH_ROWS_URL)
|
||||
return await response.json()
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { TextButton as Button, Icon, Modal } from "@budibase/bbui"
|
||||
import CreateEditRecordModal from "../modals/CreateEditRecordModal.svelte"
|
||||
import CreateEditRowModal from "../modals/CreateEditRowModal.svelte"
|
||||
|
||||
let modal
|
||||
</script>
|
||||
|
@ -12,5 +12,5 @@
|
|||
</Button>
|
||||
</div>
|
||||
<Modal bind:this={modal}>
|
||||
<CreateEditRecordModal />
|
||||
<CreateEditRowModal />
|
||||
</Modal>
|
||||
|
|
|
@ -1,46 +1,46 @@
|
|||
<script>
|
||||
import { backendUiStore } from "builderStore"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import RecordFieldControl from "../RecordFieldControl.svelte"
|
||||
import RowFieldControl from "../RowFieldControl.svelte"
|
||||
import * as api from "../api"
|
||||
import { ModalContent } from "@budibase/bbui"
|
||||
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
||||
|
||||
export let record = {}
|
||||
export let row = {}
|
||||
|
||||
let errors = []
|
||||
|
||||
$: creating = record?._id == null
|
||||
$: table = record.tableId
|
||||
? $backendUiStore.tables.find(table => table._id === record?.tableId)
|
||||
$: creating = row?._id == null
|
||||
$: table = row.tableId
|
||||
? $backendUiStore.tables.find(table => table._id === row?.tableId)
|
||||
: $backendUiStore.selectedTable
|
||||
$: tableSchema = Object.entries(table?.schema ?? {})
|
||||
|
||||
async function saveRecord() {
|
||||
const recordResponse = await api.saveRecord(
|
||||
{ ...record, tableId: table._id },
|
||||
async function saveRow() {
|
||||
const rowResponse = await api.saveRow(
|
||||
{ ...row, tableId: table._id },
|
||||
table._id
|
||||
)
|
||||
if (recordResponse.errors) {
|
||||
errors = Object.keys(recordResponse.errors)
|
||||
.map(k => ({ dataPath: k, message: recordResponse.errors[k] }))
|
||||
if (rowResponse.errors) {
|
||||
errors = Object.keys(rowResponse.errors)
|
||||
.map(k => ({ dataPath: k, message: rowResponse.errors[k] }))
|
||||
.flat()
|
||||
// Prevent modal closing if there were errors
|
||||
return false
|
||||
}
|
||||
notifier.success("Record saved successfully.")
|
||||
backendUiStore.actions.records.save(recordResponse)
|
||||
notifier.success("Row saved successfully.")
|
||||
backendUiStore.actions.rows.save(rowResponse)
|
||||
}
|
||||
</script>
|
||||
|
||||
<ModalContent
|
||||
title={creating ? 'Create Row' : 'Edit Row'}
|
||||
confirmText={creating ? 'Create Row' : 'Save Row'}
|
||||
onConfirm={saveRecord}>
|
||||
onConfirm={saveRow}>
|
||||
<ErrorsBox {errors} />
|
||||
{#each tableSchema as [key, meta]}
|
||||
<div>
|
||||
<RecordFieldControl {meta} bind:value={record[key]} />
|
||||
<RowFieldControl {meta} bind:value={row[key]} />
|
||||
</div>
|
||||
{/each}
|
||||
</ModalContent>
|
|
@ -19,7 +19,7 @@
|
|||
import Checkbox from "components/common/Checkbox.svelte"
|
||||
import ActionButton from "components/common/ActionButton.svelte"
|
||||
import DatePicker from "components/common/DatePicker.svelte"
|
||||
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte"
|
||||
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
|
||||
import * as api from "../api"
|
||||
|
||||
let fieldDefinitions = cloneDeep(FIELDS)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { backendUiStore } from "builderStore"
|
||||
import { DropdownMenu, Icon, Modal } from "@budibase/bbui"
|
||||
import CreateEditRecordModal from "../modals/CreateEditRecordModal.svelte"
|
||||
import CreateEditRowModal from "../modals/CreateEditRowModal.svelte"
|
||||
import * as api from "../api"
|
||||
import { notifier } from "builderStore/store/notifications"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
|
@ -24,9 +24,9 @@
|
|||
}
|
||||
|
||||
async function deleteRow() {
|
||||
await api.deleteRecord(row)
|
||||
notifier.success("Record deleted")
|
||||
backendUiStore.actions.records.delete(row)
|
||||
await api.deleteRow(row)
|
||||
notifier.success("Row deleted")
|
||||
backendUiStore.actions.rows.delete(row)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -52,7 +52,7 @@
|
|||
onOk={deleteRow}
|
||||
title="Confirm Delete" />
|
||||
<Modal bind:this={modal}>
|
||||
<CreateEditRecordModal record={row} />
|
||||
<CreateEditRowModal row={row} />
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -6,30 +6,30 @@
|
|||
import { capitalise } from "../../helpers"
|
||||
|
||||
export let schema
|
||||
export let linkedRecords = []
|
||||
export let linkedRows = []
|
||||
|
||||
let records = []
|
||||
let rows = []
|
||||
|
||||
$: label = capitalise(schema.name)
|
||||
$: linkedTableId = schema.tableId
|
||||
$: linkedTable = $backendUiStore.tables.find(
|
||||
table => table._id === linkedTableId
|
||||
)
|
||||
$: fetchRecords(linkedTableId)
|
||||
$: fetchRows(linkedTableId)
|
||||
|
||||
async function fetchRecords(linkedTableId) {
|
||||
const FETCH_RECORDS_URL = `/api/${linkedTableId}/records`
|
||||
async function fetchRows(linkedTableId) {
|
||||
const FETCH_ROWS_URL = `/api/${linkedTableId}/rows`
|
||||
try {
|
||||
const response = await api.get(FETCH_RECORDS_URL)
|
||||
records = await response.json()
|
||||
const response = await api.get(FETCH_ROWS_URL)
|
||||
rows = await response.json()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
records = []
|
||||
rows = []
|
||||
}
|
||||
}
|
||||
|
||||
function getPrettyName(record) {
|
||||
return record[linkedTable.primaryDisplay || "_id"]
|
||||
function getPrettyName(row) {
|
||||
return row[linkedTable.primaryDisplay || "_id"]
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -43,11 +43,11 @@
|
|||
{:else}
|
||||
<Multiselect
|
||||
secondary
|
||||
bind:value={linkedRecords}
|
||||
bind:value={linkedRows}
|
||||
{label}
|
||||
placeholder="Choose some options">
|
||||
{#each records as record}
|
||||
<option value={record._id}>{getPrettyName(record)}</option>
|
||||
{#each rows as row}
|
||||
<option value={row._id}>{getPrettyName(row)}</option>
|
||||
{/each}
|
||||
</Multiselect>
|
||||
{/if}
|
|
@ -18,25 +18,25 @@
|
|||
})
|
||||
|
||||
let idFields
|
||||
let recordId
|
||||
let rowId
|
||||
$: {
|
||||
idFields = bindableProperties.filter(
|
||||
bindable =>
|
||||
bindable.type === "context" && bindable.runtimeBinding.endsWith("._id")
|
||||
)
|
||||
// ensure recordId is always defaulted - there is usually only one option
|
||||
// ensure rowId is always defaulted - there is usually only one option
|
||||
if (idFields.length > 0 && !parameters._id) {
|
||||
recordId = idFields[0].runtimeBinding
|
||||
rowId = idFields[0].runtimeBinding
|
||||
parameters = parameters
|
||||
} else if (!recordId && parameters._id) {
|
||||
recordId = parameters._id
|
||||
} else if (!rowId && parameters._id) {
|
||||
rowId = parameters._id
|
||||
.replace("{{", "")
|
||||
.replace("}}", "")
|
||||
.trim()
|
||||
}
|
||||
}
|
||||
|
||||
$: parameters._id = `{{ ${recordId} }}`
|
||||
$: parameters._id = `{{ ${rowId} }}`
|
||||
|
||||
// just wraps binding in {{ ... }}
|
||||
const toBindingExpression = bindingPath => `{{ ${bindingPath} }}`
|
||||
|
@ -44,11 +44,11 @@
|
|||
// finds the selected idBinding, then reads the table/view
|
||||
// from the component instance that it belongs to.
|
||||
// then returns the field names for that schema
|
||||
const schemaFromIdBinding = recordId => {
|
||||
if (!recordId) return []
|
||||
const schemaFromIdBinding = rowId => {
|
||||
if (!rowId) return []
|
||||
|
||||
const idBinding = bindableProperties.find(
|
||||
prop => prop.runtimeBinding === recordId
|
||||
prop => prop.runtimeBinding === rowId
|
||||
)
|
||||
if (!idBinding) return []
|
||||
|
||||
|
@ -71,8 +71,8 @@
|
|||
|
||||
let schemaFields
|
||||
$: {
|
||||
if (parameters && recordId) {
|
||||
schemaFields = schemaFromIdBinding(recordId)
|
||||
if (parameters && rowId) {
|
||||
schemaFields = schemaFromIdBinding(rowId)
|
||||
} else {
|
||||
schemaFields = []
|
||||
}
|
||||
|
@ -86,12 +86,12 @@
|
|||
<div class="root">
|
||||
{#if idFields.length === 0}
|
||||
<div class="cannot-use">
|
||||
Update record can only be used within a component that provides data, such
|
||||
Update row can only be used within a component that provides data, such
|
||||
as a List
|
||||
</div>
|
||||
{:else}
|
||||
<Label size="m" color="dark">Record Id</Label>
|
||||
<Select secondary bind:value={recordId}>
|
||||
<Label size="m" color="dark">Row Id</Label>
|
||||
<Select secondary bind:value={rowId}>
|
||||
<option value="" />
|
||||
{#each idFields as idField}
|
||||
<option value={idField.runtimeBinding}>
|
||||
|
@ -101,7 +101,7 @@
|
|||
</Select>
|
||||
{/if}
|
||||
|
||||
{#if recordId}
|
||||
{#if rowId}
|
||||
<SaveFields
|
||||
parameterFields={parameters.fields}
|
||||
{schemaFields}
|
|
@ -1,6 +1,6 @@
|
|||
import NavigateTo from "./NavigateTo.svelte"
|
||||
import UpdateRecord from "./UpdateRecord.svelte"
|
||||
import CreateRecord from "./CreateRecord.svelte"
|
||||
import UpdateRow from "./UpdateRow.svelte"
|
||||
import CreateRow from "./CreateRow.svelte"
|
||||
|
||||
// defines what actions are available, when adding a new one
|
||||
// the component is the setup panel for the action
|
||||
|
@ -9,15 +9,15 @@ import CreateRecord from "./CreateRecord.svelte"
|
|||
|
||||
export default [
|
||||
{
|
||||
name: "Create Record",
|
||||
component: CreateRecord,
|
||||
name: "Create Row",
|
||||
component: CreateRow,
|
||||
},
|
||||
{
|
||||
name: "Navigate To",
|
||||
component: NavigateTo,
|
||||
},
|
||||
{
|
||||
name: "Update Record",
|
||||
component: UpdateRecord,
|
||||
name: "Update Row",
|
||||
component: UpdateRow,
|
||||
},
|
||||
]
|
||||
|
|
|
@ -4568,8 +4568,8 @@ export default [
|
|||
label: "receipt",
|
||||
},
|
||||
{
|
||||
value: "fas fa-record-vinyl",
|
||||
label: "record-vinyl",
|
||||
value: "fas fa-row-vinyl",
|
||||
label: "row-vinyl",
|
||||
},
|
||||
{
|
||||
value: "fas fa-recycle",
|
||||
|
|
|
@ -299,7 +299,7 @@ export default {
|
|||
{
|
||||
name: "List",
|
||||
_component: "@budibase/standard-components/list",
|
||||
description: "Renders all children once per record, of a given table",
|
||||
description: "Renders all children once per row, of a given table",
|
||||
icon: "ri-file-list-line",
|
||||
properties: {
|
||||
design: { ...all },
|
||||
|
@ -1125,10 +1125,10 @@ export default {
|
|||
// children: [],
|
||||
// },
|
||||
{
|
||||
name: "Record Detail",
|
||||
_component: "@budibase/standard-components/recorddetail",
|
||||
name: "Row Detail",
|
||||
_component: "@budibase/standard-components/rowdetail",
|
||||
description:
|
||||
"Loads a record, using an id from the URL, which can be used with {{ context }}, in children",
|
||||
"Loads a row, using an id from the URL, which can be used with {{ context }}, in children",
|
||||
icon: "ri-profile-line",
|
||||
properties: {
|
||||
design: { ...all },
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
|
||||
<RelationshipDataTable
|
||||
tableId={$params.selectedTable}
|
||||
recordId={$params.selectedRecord}
|
||||
rowId={$params.selectedRow}
|
||||
fieldName={decodeURI($params.selectedField)} />
|
|
@ -29,8 +29,8 @@ export const componentsAndScreens = () => ({
|
|||
},
|
||||
},
|
||||
{
|
||||
_instanceName: "Record View",
|
||||
tags: ["record"],
|
||||
_instanceName: "Row View",
|
||||
tags: ["row"],
|
||||
props: {
|
||||
data: "state",
|
||||
},
|
||||
|
|
|
@ -52,22 +52,22 @@ const apiOpts = {
|
|||
delete: del,
|
||||
}
|
||||
|
||||
const createRecord = async params =>
|
||||
const createRow = async params =>
|
||||
await post({
|
||||
url: `/api/${params.tableId}/records`,
|
||||
body: makeRecordRequestBody(params),
|
||||
url: `/api/${params.tableId}/rows`,
|
||||
body: makeRowRequestBody(params),
|
||||
})
|
||||
|
||||
const updateRecord = async params => {
|
||||
const record = makeRecordRequestBody(params)
|
||||
record._id = params._id
|
||||
const updateRow = async params => {
|
||||
const row = makeRowRequestBody(params)
|
||||
row._id = params._id
|
||||
await patch({
|
||||
url: `/api/${params.tableId}/records/${params._id}`,
|
||||
body: record,
|
||||
url: `/api/${params.tableId}/rows/${params._id}`,
|
||||
body: row,
|
||||
})
|
||||
}
|
||||
|
||||
const makeRecordRequestBody = parameters => {
|
||||
const makeRowRequestBody = parameters => {
|
||||
const body = {}
|
||||
for (let fieldName in parameters.fields) {
|
||||
const field = parameters.fields[fieldName]
|
||||
|
@ -95,6 +95,6 @@ const makeRecordRequestBody = parameters => {
|
|||
|
||||
export default {
|
||||
authenticate: authenticate(apiOpts),
|
||||
createRecord,
|
||||
updateRecord,
|
||||
createRow,
|
||||
updateRow,
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ export const EVENT_TYPE_MEMBER_NAME = "##eventHandlerType"
|
|||
export const eventHandlers = routeTo => {
|
||||
const handlers = {
|
||||
"Navigate To": param => routeTo(param && param.url),
|
||||
"Create Record": api.createRecord,
|
||||
"Update Record": api.updateRecord,
|
||||
"Create Row": api.createRow,
|
||||
"Update Row": api.updateRow,
|
||||
"Trigger Workflow": api.triggerWorkflow,
|
||||
}
|
||||
|
||||
|
|
|
@ -24,14 +24,14 @@ async function run() {
|
|||
quotaReset: Date.now() + 2592000000,
|
||||
usageQuota: {
|
||||
automationRuns: 0,
|
||||
records: 0,
|
||||
rows: 0,
|
||||
storage: 0,
|
||||
users: 0,
|
||||
views: 0,
|
||||
},
|
||||
usageLimits: {
|
||||
automationRuns: 10,
|
||||
records: 10,
|
||||
rows: 10,
|
||||
storage: 1000,
|
||||
users: 10,
|
||||
views: 10,
|
||||
|
@ -48,8 +48,8 @@ async function run() {
|
|||
|
||||
run()
|
||||
.then(() => {
|
||||
console.log("Records should have been created.")
|
||||
console.log("Rows should have been created.")
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("Cannot create records - " + err)
|
||||
console.error("Cannot create rows - " + err)
|
||||
})
|
||||
|
|
|
@ -2,7 +2,7 @@ const fs = require("fs")
|
|||
const CouchDB = require("../../db")
|
||||
const client = require("../../db/clientDb")
|
||||
const newid = require("../../db/newid")
|
||||
const { createLinkView } = require("../../db/linkedRecords")
|
||||
const { createLinkView } = require("../../db/linkedRows")
|
||||
const { join } = require("../../utilities/centralPath")
|
||||
const { downloadTemplate } = require("../../utilities/templates")
|
||||
|
||||
|
@ -27,7 +27,7 @@ exports.create = async function(ctx) {
|
|||
// https://docs.couchdb.org/en/master/ddocs/views/collation.html#collation-specification
|
||||
views: {},
|
||||
})
|
||||
// add view for linked records
|
||||
// add view for linked rows
|
||||
await createLinkView(instanceId)
|
||||
|
||||
// Add the new instance under the app clientDB
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
const CouchDB = require("../../db")
|
||||
const validateJs = require("validate.js")
|
||||
const linkRecords = require("../../db/linkedRecords")
|
||||
const linkRows = require("../../db/linkedRows")
|
||||
const {
|
||||
getRecordParams,
|
||||
generateRecordID,
|
||||
getRowParams,
|
||||
generateRowID,
|
||||
DocumentTypes,
|
||||
SEPARATOR,
|
||||
} = require("../../db/utils")
|
||||
|
@ -24,18 +24,18 @@ validateJs.extend(validateJs.validators.datetime, {
|
|||
exports.patch = async function(ctx) {
|
||||
const instanceId = ctx.user.instanceId
|
||||
const db = new CouchDB(instanceId)
|
||||
let record = await db.get(ctx.params.id)
|
||||
const table = await db.get(record.tableId)
|
||||
let row = await db.get(ctx.params.id)
|
||||
const table = await db.get(row.tableId)
|
||||
const patchfields = ctx.request.body
|
||||
record = coerceRecordValues(record, table)
|
||||
row = coerceRowValues(row, table)
|
||||
|
||||
for (let key of Object.keys(patchfields)) {
|
||||
if (!table.schema[key]) continue
|
||||
record[key] = patchfields[key]
|
||||
row[key] = patchfields[key]
|
||||
}
|
||||
|
||||
const validateResult = await validate({
|
||||
record,
|
||||
row,
|
||||
table,
|
||||
})
|
||||
|
||||
|
@ -48,21 +48,21 @@ exports.patch = async function(ctx) {
|
|||
return
|
||||
}
|
||||
|
||||
// returned record is cleaned and prepared for writing to DB
|
||||
record = await linkRecords.updateLinks({
|
||||
// returned row is cleaned and prepared for writing to DB
|
||||
row = await linkRows.updateLinks({
|
||||
instanceId,
|
||||
eventType: linkRecords.EventType.RECORD_UPDATE,
|
||||
record,
|
||||
tableId: record.tableId,
|
||||
eventType: linkRows.EventType.ROW_UPDATE,
|
||||
row,
|
||||
tableId: row.tableId,
|
||||
table,
|
||||
})
|
||||
const response = await db.put(record)
|
||||
record._rev = response.rev
|
||||
record.type = "record"
|
||||
const response = await db.put(row)
|
||||
row._rev = response.rev
|
||||
row.type = "row"
|
||||
|
||||
ctx.eventEmitter &&
|
||||
ctx.eventEmitter.emitRecord(`record:update`, instanceId, record, table)
|
||||
ctx.body = record
|
||||
ctx.eventEmitter.emitRow(`row:update`, instanceId, row, table)
|
||||
ctx.body = row
|
||||
ctx.status = 200
|
||||
ctx.message = `${table.name} updated successfully.`
|
||||
}
|
||||
|
@ -70,22 +70,22 @@ exports.patch = async function(ctx) {
|
|||
exports.save = async function(ctx) {
|
||||
const instanceId = ctx.user.instanceId
|
||||
const db = new CouchDB(instanceId)
|
||||
let record = ctx.request.body
|
||||
record.tableId = ctx.params.tableId
|
||||
let row = ctx.request.body
|
||||
row.tableId = ctx.params.tableId
|
||||
|
||||
if (!record._rev && !record._id) {
|
||||
record._id = generateRecordID(record.tableId)
|
||||
if (!row._rev && !row._id) {
|
||||
row._id = generateRowID(row.tableId)
|
||||
}
|
||||
|
||||
// if the record obj had an _id then it will have been retrieved
|
||||
const existingRecord = ctx.preExisting
|
||||
// if the row obj had an _id then it will have been retrieved
|
||||
const existingRow = ctx.preExisting
|
||||
|
||||
const table = await db.get(record.tableId)
|
||||
const table = await db.get(row.tableId)
|
||||
|
||||
record = coerceRecordValues(record, table)
|
||||
row = coerceRowValues(row, table)
|
||||
|
||||
const validateResult = await validate({
|
||||
record,
|
||||
row,
|
||||
table,
|
||||
})
|
||||
|
||||
|
@ -98,32 +98,32 @@ exports.save = async function(ctx) {
|
|||
return
|
||||
}
|
||||
|
||||
// make sure link records are up to date
|
||||
record = await linkRecords.updateLinks({
|
||||
// make sure link rows are up to date
|
||||
row = await linkRows.updateLinks({
|
||||
instanceId,
|
||||
eventType: linkRecords.EventType.RECORD_SAVE,
|
||||
record,
|
||||
tableId: record.tableId,
|
||||
eventType: linkRows.EventType.ROW_SAVE,
|
||||
row,
|
||||
tableId: row.tableId,
|
||||
table,
|
||||
})
|
||||
|
||||
if (existingRecord) {
|
||||
const response = await db.put(record)
|
||||
record._rev = response.rev
|
||||
record.type = "record"
|
||||
ctx.body = record
|
||||
if (existingRow) {
|
||||
const response = await db.put(row)
|
||||
row._rev = response.rev
|
||||
row.type = "row"
|
||||
ctx.body = row
|
||||
ctx.status = 200
|
||||
ctx.message = `${table.name} updated successfully.`
|
||||
return
|
||||
}
|
||||
|
||||
record.type = "record"
|
||||
const response = await db.post(record)
|
||||
record._rev = response.rev
|
||||
row.type = "row"
|
||||
const response = await db.post(row)
|
||||
row._rev = response.rev
|
||||
|
||||
ctx.eventEmitter &&
|
||||
ctx.eventEmitter.emitRecord(`record:save`, instanceId, record, table)
|
||||
ctx.body = record
|
||||
ctx.eventEmitter.emitRow(`row:save`, instanceId, row, table)
|
||||
ctx.body = row
|
||||
ctx.status = 200
|
||||
ctx.message = `${table.name} created successfully`
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ exports.fetchView = async function(ctx) {
|
|||
// if this is a table view being looked for just transfer to that
|
||||
if (viewName.indexOf(TABLE_VIEW_BEGINS_WITH) === 0) {
|
||||
ctx.params.tableId = viewName.substring(4)
|
||||
await exports.fetchTableRecords(ctx)
|
||||
await exports.fetchTableRows(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -157,19 +157,19 @@ exports.fetchView = async function(ctx) {
|
|||
response.rows = response.rows.map(row => row.doc)
|
||||
}
|
||||
|
||||
ctx.body = await linkRecords.attachLinkInfo(instanceId, response.rows)
|
||||
ctx.body = await linkRows.attachLinkInfo(instanceId, response.rows)
|
||||
}
|
||||
|
||||
exports.fetchTableRecords = async function(ctx) {
|
||||
exports.fetchTableRows = async function(ctx) {
|
||||
const instanceId = ctx.user.instanceId
|
||||
const db = new CouchDB(instanceId)
|
||||
const response = await db.allDocs(
|
||||
getRecordParams(ctx.params.tableId, null, {
|
||||
getRowParams(ctx.params.tableId, null, {
|
||||
include_docs: true,
|
||||
})
|
||||
)
|
||||
ctx.body = response.rows.map(row => row.doc)
|
||||
ctx.body = await linkRecords.attachLinkInfo(
|
||||
ctx.body = await linkRows.attachLinkInfo(
|
||||
instanceId,
|
||||
response.rows.map(row => row.doc)
|
||||
)
|
||||
|
@ -182,7 +182,7 @@ exports.search = async function(ctx) {
|
|||
include_docs: true,
|
||||
...ctx.request.body,
|
||||
})
|
||||
ctx.body = await linkRecords.attachLinkInfo(
|
||||
ctx.body = await linkRows.attachLinkInfo(
|
||||
instanceId,
|
||||
response.rows.map(row => row.doc)
|
||||
)
|
||||
|
@ -191,48 +191,48 @@ exports.search = async function(ctx) {
|
|||
exports.find = async function(ctx) {
|
||||
const instanceId = ctx.user.instanceId
|
||||
const db = new CouchDB(instanceId)
|
||||
const record = await db.get(ctx.params.recordId)
|
||||
if (record.tableId !== ctx.params.tableId) {
|
||||
ctx.throw(400, "Supplied tableId does not match the records tableId")
|
||||
const row = await db.get(ctx.params.rowId)
|
||||
if (row.tableId !== ctx.params.tableId) {
|
||||
ctx.throw(400, "Supplied tableId does not match the rows tableId")
|
||||
return
|
||||
}
|
||||
ctx.body = await linkRecords.attachLinkInfo(instanceId, record)
|
||||
ctx.body = await linkRows.attachLinkInfo(instanceId, row)
|
||||
}
|
||||
|
||||
exports.destroy = async function(ctx) {
|
||||
const instanceId = ctx.user.instanceId
|
||||
const db = new CouchDB(instanceId)
|
||||
const record = await db.get(ctx.params.recordId)
|
||||
if (record.tableId !== ctx.params.tableId) {
|
||||
ctx.throw(400, "Supplied tableId doesn't match the record's tableId")
|
||||
const row = await db.get(ctx.params.rowId)
|
||||
if (row.tableId !== ctx.params.tableId) {
|
||||
ctx.throw(400, "Supplied tableId doesn't match the row's tableId")
|
||||
return
|
||||
}
|
||||
await linkRecords.updateLinks({
|
||||
await linkRows.updateLinks({
|
||||
instanceId,
|
||||
eventType: linkRecords.EventType.RECORD_DELETE,
|
||||
record,
|
||||
tableId: record.tableId,
|
||||
eventType: linkRows.EventType.ROW_DELETE,
|
||||
row,
|
||||
tableId: row.tableId,
|
||||
})
|
||||
ctx.body = await db.remove(ctx.params.recordId, ctx.params.revId)
|
||||
ctx.body = await db.remove(ctx.params.rowId, ctx.params.revId)
|
||||
ctx.status = 200
|
||||
|
||||
// for automations include the record that was deleted
|
||||
ctx.record = record
|
||||
// for automations include the row that was deleted
|
||||
ctx.row = row
|
||||
ctx.eventEmitter &&
|
||||
ctx.eventEmitter.emitRecord(`record:delete`, instanceId, record)
|
||||
ctx.eventEmitter.emitRow(`row:delete`, instanceId, row)
|
||||
}
|
||||
|
||||
exports.validate = async function(ctx) {
|
||||
const errors = await validate({
|
||||
instanceId: ctx.user.instanceId,
|
||||
tableId: ctx.params.tableId,
|
||||
record: ctx.request.body,
|
||||
row: ctx.request.body,
|
||||
})
|
||||
ctx.status = 200
|
||||
ctx.body = errors
|
||||
}
|
||||
|
||||
async function validate({ instanceId, tableId, record, table }) {
|
||||
async function validate({ instanceId, tableId, row, table }) {
|
||||
if (!table) {
|
||||
const db = new CouchDB(instanceId)
|
||||
table = await db.get(tableId)
|
||||
|
@ -240,7 +240,7 @@ async function validate({ instanceId, tableId, record, table }) {
|
|||
const errors = {}
|
||||
for (let fieldName of Object.keys(table.schema)) {
|
||||
const res = validateJs.single(
|
||||
record[fieldName],
|
||||
row[fieldName],
|
||||
table.schema[fieldName].constraints
|
||||
)
|
||||
if (res) errors[fieldName] = res
|
||||
|
@ -248,12 +248,12 @@ async function validate({ instanceId, tableId, record, table }) {
|
|||
return { valid: Object.keys(errors).length === 0, errors }
|
||||
}
|
||||
|
||||
exports.fetchEnrichedRecord = async function(ctx) {
|
||||
exports.fetchEnrichedRow = async function(ctx) {
|
||||
const instanceId = ctx.user.instanceId
|
||||
const db = new CouchDB(instanceId)
|
||||
const tableId = ctx.params.tableId
|
||||
const recordId = ctx.params.recordId
|
||||
if (instanceId == null || tableId == null || recordId == null) {
|
||||
const rowId = ctx.params.rowId
|
||||
if (instanceId == null || tableId == null || rowId == null) {
|
||||
ctx.status = 400
|
||||
ctx.body = {
|
||||
status: 400,
|
||||
|
@ -262,51 +262,51 @@ exports.fetchEnrichedRecord = async function(ctx) {
|
|||
}
|
||||
return
|
||||
}
|
||||
// need table to work out where links go in record
|
||||
const [table, record] = await Promise.all([db.get(tableId), db.get(recordId)])
|
||||
// need table to work out where links go in row
|
||||
const [table, row] = await Promise.all([db.get(tableId), db.get(rowId)])
|
||||
// get the link docs
|
||||
const linkVals = await linkRecords.getLinkDocuments({
|
||||
const linkVals = await linkRows.getLinkDocuments({
|
||||
instanceId,
|
||||
tableId,
|
||||
recordId,
|
||||
rowId,
|
||||
})
|
||||
// look up the actual records based on the ids
|
||||
// look up the actual rows based on the ids
|
||||
const response = await db.allDocs({
|
||||
include_docs: true,
|
||||
keys: linkVals.map(linkVal => linkVal.id),
|
||||
})
|
||||
// need to include the IDs in these records for any links they may have
|
||||
let linkedRecords = await linkRecords.attachLinkInfo(
|
||||
// need to include the IDs in these rows for any links they may have
|
||||
let linkedRows = await linkRows.attachLinkInfo(
|
||||
instanceId,
|
||||
response.rows.map(row => row.doc)
|
||||
)
|
||||
// insert the link records in the correct place throughout the main record
|
||||
// insert the link rows in the correct place throughout the main row
|
||||
for (let fieldName of Object.keys(table.schema)) {
|
||||
let field = table.schema[fieldName]
|
||||
if (field.type === "link") {
|
||||
record[fieldName] = linkedRecords.filter(
|
||||
linkRecord => linkRecord.tableId === field.tableId
|
||||
row[fieldName] = linkedRows.filter(
|
||||
linkRow => linkRow.tableId === field.tableId
|
||||
)
|
||||
}
|
||||
}
|
||||
ctx.body = record
|
||||
ctx.body = row
|
||||
ctx.status = 200
|
||||
}
|
||||
|
||||
function coerceRecordValues(rec, table) {
|
||||
const record = cloneDeep(rec)
|
||||
for (let [key, value] of Object.entries(record)) {
|
||||
function coerceRowValues(rec, table) {
|
||||
const row = cloneDeep(rec)
|
||||
for (let [key, value] of Object.entries(row)) {
|
||||
const field = table.schema[key]
|
||||
if (!field) continue
|
||||
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (TYPE_TRANSFORM_MAP[field.type].hasOwnProperty(value)) {
|
||||
record[key] = TYPE_TRANSFORM_MAP[field.type][value]
|
||||
row[key] = TYPE_TRANSFORM_MAP[field.type][value]
|
||||
} else if (TYPE_TRANSFORM_MAP[field.type].parse) {
|
||||
record[key] = TYPE_TRANSFORM_MAP[field.type].parse(value)
|
||||
row[key] = TYPE_TRANSFORM_MAP[field.type].parse(value)
|
||||
}
|
||||
}
|
||||
return record
|
||||
return row
|
||||
}
|
||||
|
||||
const TYPE_TRANSFORM_MAP = {
|
|
@ -1,11 +1,11 @@
|
|||
const CouchDB = require("../../db")
|
||||
const linkRecords = require("../../db/linkedRecords")
|
||||
const linkRows = require("../../db/linkedRows")
|
||||
const csvParser = require("../../utilities/csvParser")
|
||||
const {
|
||||
getRecordParams,
|
||||
getRowParams,
|
||||
getTableParams,
|
||||
generateTableID,
|
||||
generateRecordID,
|
||||
generateRowID,
|
||||
} = require("../../db/utils")
|
||||
|
||||
exports.fetch = async function(ctx) {
|
||||
|
@ -37,20 +37,20 @@ exports.save = async function(ctx) {
|
|||
// if the table obj had an _id then it will have been retrieved
|
||||
const oldTable = ctx.preExisting
|
||||
|
||||
// rename record fields when table column is renamed
|
||||
// rename row fields when table column is renamed
|
||||
const { _rename } = tableToSave
|
||||
if (_rename && tableToSave.schema[_rename.updated].type === "link") {
|
||||
throw "Cannot rename a linked field."
|
||||
} else if (_rename && tableToSave.primaryDisplay === _rename.old) {
|
||||
throw "Cannot rename the primary display field."
|
||||
} else if (_rename) {
|
||||
const records = await db.allDocs(
|
||||
getRecordParams(tableToSave._id, null, {
|
||||
const rows = await db.allDocs(
|
||||
getRowParams(tableToSave._id, null, {
|
||||
include_docs: true,
|
||||
})
|
||||
)
|
||||
|
||||
const docs = records.rows.map(({ doc }) => {
|
||||
const docs = rows.rows.map(({ doc }) => {
|
||||
doc[_rename.updated] = doc[_rename.old]
|
||||
delete doc[_rename.old]
|
||||
return doc
|
||||
|
@ -72,12 +72,12 @@ exports.save = async function(ctx) {
|
|||
const result = await db.post(tableToSave)
|
||||
tableToSave._rev = result.rev
|
||||
|
||||
// update linked records
|
||||
await linkRecords.updateLinks({
|
||||
// update linked rows
|
||||
await linkRows.updateLinks({
|
||||
instanceId,
|
||||
eventType: oldTable
|
||||
? linkRecords.EventType.TABLE_UPDATED
|
||||
: linkRecords.EventType.TABLE_SAVE,
|
||||
? linkRows.EventType.TABLE_UPDATED
|
||||
: linkRows.EventType.TABLE_SAVE,
|
||||
table: tableToSave,
|
||||
oldTable: oldTable,
|
||||
})
|
||||
|
@ -86,11 +86,11 @@ exports.save = async function(ctx) {
|
|||
ctx.eventEmitter.emitTable(`table:save`, instanceId, tableToSave)
|
||||
|
||||
if (dataImport && dataImport.path) {
|
||||
// Populate the table with records imported from CSV in a bulk update
|
||||
// Populate the table with rows imported from CSV in a bulk update
|
||||
const data = await csvParser.transform(dataImport)
|
||||
|
||||
for (let row of data) {
|
||||
row._id = generateRecordID(tableToSave._id)
|
||||
row._id = generateRowID(tableToSave._id)
|
||||
row.tableId = tableToSave._id
|
||||
}
|
||||
|
||||
|
@ -110,20 +110,20 @@ exports.destroy = async function(ctx) {
|
|||
|
||||
await db.remove(tableToDelete)
|
||||
|
||||
// Delete all records for that table
|
||||
const records = await db.allDocs(
|
||||
getRecordParams(ctx.params.tableId, null, {
|
||||
// Delete all rows for that table
|
||||
const rows = await db.allDocs(
|
||||
getRowParams(ctx.params.tableId, null, {
|
||||
include_docs: true,
|
||||
})
|
||||
)
|
||||
await db.bulkDocs(
|
||||
records.rows.map(record => ({ _id: record.id, _deleted: true }))
|
||||
rows.rows.map(row => ({ _id: row.id, _deleted: true }))
|
||||
)
|
||||
|
||||
// update linked records
|
||||
await linkRecords.updateLinks({
|
||||
// update linked rows
|
||||
await linkRows.updateLinks({
|
||||
instanceId,
|
||||
eventType: linkRecords.EventType.TABLE_DELETE,
|
||||
eventType: linkRows.EventType.TABLE_DELETE,
|
||||
table: tableToDelete,
|
||||
})
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ const fs = require("fs")
|
|||
const { join } = require("../../../utilities/centralPath")
|
||||
const os = require("os")
|
||||
const exporters = require("./exporters")
|
||||
const { fetchView } = require("../record")
|
||||
const { fetchView } = require("../row")
|
||||
|
||||
const controller = {
|
||||
fetch: async ctx => {
|
||||
|
@ -85,7 +85,7 @@ const controller = {
|
|||
const view = ctx.request.body
|
||||
const format = ctx.query.format
|
||||
|
||||
// Fetch view records
|
||||
// Fetch view rows
|
||||
ctx.params.viewName = view.name
|
||||
ctx.query.group = view.groupBy
|
||||
if (view.field) {
|
||||
|
|
|
@ -11,7 +11,7 @@ const {
|
|||
instanceRoutes,
|
||||
clientRoutes,
|
||||
applicationRoutes,
|
||||
recordRoutes,
|
||||
rowRoutes,
|
||||
tableRoutes,
|
||||
viewRoutes,
|
||||
staticRoutes,
|
||||
|
@ -77,8 +77,8 @@ router.use(viewRoutes.allowedMethods())
|
|||
router.use(tableRoutes.routes())
|
||||
router.use(tableRoutes.allowedMethods())
|
||||
|
||||
router.use(recordRoutes.routes())
|
||||
router.use(recordRoutes.allowedMethods())
|
||||
router.use(rowRoutes.routes())
|
||||
router.use(rowRoutes.allowedMethods())
|
||||
|
||||
router.use(userRoutes.routes())
|
||||
router.use(userRoutes.allowedMethods())
|
||||
|
|
|
@ -5,7 +5,7 @@ const instanceRoutes = require("./instance")
|
|||
const clientRoutes = require("./client")
|
||||
const applicationRoutes = require("./application")
|
||||
const tableRoutes = require("./table")
|
||||
const recordRoutes = require("./record")
|
||||
const rowRoutes = require("./row")
|
||||
const viewRoutes = require("./view")
|
||||
const staticRoutes = require("./static")
|
||||
const componentRoutes = require("./component")
|
||||
|
@ -24,7 +24,7 @@ module.exports = {
|
|||
instanceRoutes,
|
||||
clientRoutes,
|
||||
applicationRoutes,
|
||||
recordRoutes,
|
||||
rowRoutes,
|
||||
tableRoutes,
|
||||
viewRoutes,
|
||||
staticRoutes,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const Router = require("@koa/router")
|
||||
const recordController = require("../controllers/record")
|
||||
const rowController = require("../controllers/row")
|
||||
const authorized = require("../../middleware/authorized")
|
||||
const usage = require("../../middleware/usageQuota")
|
||||
const { READ_TABLE, WRITE_TABLE } = require("../../utilities/accessLevels")
|
||||
|
@ -8,42 +8,42 @@ const router = Router()
|
|||
|
||||
router
|
||||
.get(
|
||||
"/api/:tableId/:recordId/enrich",
|
||||
"/api/:tableId/:rowId/enrich",
|
||||
authorized(READ_TABLE, ctx => ctx.params.tableId),
|
||||
recordController.fetchEnrichedRecord
|
||||
rowController.fetchEnrichedRow
|
||||
)
|
||||
.get(
|
||||
"/api/:tableId/records",
|
||||
"/api/:tableId/rows",
|
||||
authorized(READ_TABLE, ctx => ctx.params.tableId),
|
||||
recordController.fetchTableRecords
|
||||
rowController.fetchTableRows
|
||||
)
|
||||
.get(
|
||||
"/api/:tableId/records/:recordId",
|
||||
"/api/:tableId/rows/:rowId",
|
||||
authorized(READ_TABLE, ctx => ctx.params.tableId),
|
||||
recordController.find
|
||||
rowController.find
|
||||
)
|
||||
.post("/api/records/search", recordController.search)
|
||||
.post("/api/rows/search", rowController.search)
|
||||
.post(
|
||||
"/api/:tableId/records",
|
||||
"/api/:tableId/rows",
|
||||
authorized(WRITE_TABLE, ctx => ctx.params.tableId),
|
||||
usage,
|
||||
recordController.save
|
||||
rowController.save
|
||||
)
|
||||
.patch(
|
||||
"/api/:tableId/records/:id",
|
||||
"/api/:tableId/rows/:id",
|
||||
authorized(WRITE_TABLE, ctx => ctx.params.tableId),
|
||||
recordController.patch
|
||||
rowController.patch
|
||||
)
|
||||
.post(
|
||||
"/api/:tableId/records/validate",
|
||||
"/api/:tableId/rows/validate",
|
||||
authorized(WRITE_TABLE, ctx => ctx.params.tableId),
|
||||
recordController.validate
|
||||
rowController.validate
|
||||
)
|
||||
.delete(
|
||||
"/api/:tableId/records/:recordId/:revId",
|
||||
"/api/:tableId/rows/:rowId/:revId",
|
||||
authorized(WRITE_TABLE, ctx => ctx.params.tableId),
|
||||
usage,
|
||||
recordController.destroy
|
||||
rowController.destroy
|
||||
)
|
||||
|
||||
module.exports = router
|
|
@ -126,10 +126,10 @@ describe("/automations", () => {
|
|||
|
||||
describe("create", () => {
|
||||
it("should setup the automation fully", () => {
|
||||
let trigger = TRIGGER_DEFINITIONS["RECORD_SAVED"]
|
||||
let trigger = TRIGGER_DEFINITIONS["ROW_SAVED"]
|
||||
trigger.id = "wadiawdo34"
|
||||
let createAction = ACTION_DEFINITIONS["CREATE_RECORD"]
|
||||
createAction.inputs.record = {
|
||||
let createAction = ACTION_DEFINITIONS["CREATE_ROW"]
|
||||
createAction.inputs.row = {
|
||||
name: "{{trigger.name}}",
|
||||
description: "{{trigger.description}}"
|
||||
}
|
||||
|
@ -169,7 +169,7 @@ describe("/automations", () => {
|
|||
it("trigger the automation successfully", async () => {
|
||||
let table = await createTable(request, app._id, instance._id)
|
||||
TEST_AUTOMATION.definition.trigger.inputs.tableId = table._id
|
||||
TEST_AUTOMATION.definition.steps[0].inputs.record.tableId = table._id
|
||||
TEST_AUTOMATION.definition.steps[0].inputs.row.tableId = table._id
|
||||
await createAutomation()
|
||||
// this looks a bit mad but we don't actually have a way to wait for a response from the automation to
|
||||
// know that it has finished all of its actions - this is currently the best way
|
||||
|
@ -189,7 +189,7 @@ describe("/automations", () => {
|
|||
return
|
||||
}
|
||||
}
|
||||
throw "Failed to find the records"
|
||||
throw "Failed to find the rows"
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ exports.createTable = async (request, appId, instanceId, table) => {
|
|||
|
||||
exports.getAllFromTable = async (request, appId, instanceId, tableId) => {
|
||||
const res = await request
|
||||
.get(`/api/${tableId}/records`)
|
||||
.get(`/api/${tableId}/rows`)
|
||||
.set(exports.defaultHeaders(appId, instanceId))
|
||||
return res.body
|
||||
}
|
||||
|
|
|
@ -7,12 +7,12 @@ const {
|
|||
defaultHeaders,
|
||||
} = require("./couchTestUtils");
|
||||
|
||||
describe("/records", () => {
|
||||
describe("/rows", () => {
|
||||
let request
|
||||
let server
|
||||
let instance
|
||||
let table
|
||||
let record
|
||||
let row
|
||||
let app
|
||||
|
||||
beforeAll(async () => {
|
||||
|
@ -28,7 +28,7 @@ describe("/records", () => {
|
|||
beforeEach(async () => {
|
||||
instance = await createInstance(request, app._id)
|
||||
table = await createTable(request, app._id, instance._id)
|
||||
record = {
|
||||
row = {
|
||||
name: "Test Contact",
|
||||
description: "original description",
|
||||
status: "new",
|
||||
|
@ -36,17 +36,17 @@ describe("/records", () => {
|
|||
}
|
||||
})
|
||||
|
||||
const createRecord = async r =>
|
||||
const createRow = async r =>
|
||||
await request
|
||||
.post(`/api/${r ? r.tableId : record.tableId}/records`)
|
||||
.send(r || record)
|
||||
.post(`/api/${r ? r.tableId : row.tableId}/rows`)
|
||||
.send(r || row)
|
||||
.set(defaultHeaders(app._id, instance._id))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
|
||||
const loadRecord = async id =>
|
||||
const loadRow = async id =>
|
||||
await request
|
||||
.get(`/api/${table._id}/records/${id}`)
|
||||
.get(`/api/${table._id}/rows/${id}`)
|
||||
.set(defaultHeaders(app._id, instance._id))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
|
@ -55,19 +55,19 @@ describe("/records", () => {
|
|||
describe("save, load, update, delete", () => {
|
||||
|
||||
|
||||
it("returns a success message when the record is created", async () => {
|
||||
const res = await createRecord()
|
||||
it("returns a success message when the row is created", async () => {
|
||||
const res = await createRow()
|
||||
expect(res.res.statusMessage).toEqual(`${table.name} created successfully`)
|
||||
expect(res.body.name).toEqual("Test Contact")
|
||||
expect(res.body._rev).toBeDefined()
|
||||
})
|
||||
|
||||
it("updates a record successfully", async () => {
|
||||
const rec = await createRecord()
|
||||
it("updates a row successfully", async () => {
|
||||
const rec = await createRow()
|
||||
const existing = rec.body
|
||||
|
||||
const res = await request
|
||||
.post(`/api/${table._id}/records`)
|
||||
.post(`/api/${table._id}/rows`)
|
||||
.send({
|
||||
_id: existing._id,
|
||||
_rev: existing._rev,
|
||||
|
@ -82,78 +82,78 @@ describe("/records", () => {
|
|||
expect(res.body.name).toEqual("Updated Name")
|
||||
})
|
||||
|
||||
it("should load a record", async () => {
|
||||
const rec = await createRecord()
|
||||
it("should load a row", async () => {
|
||||
const rec = await createRow()
|
||||
const existing = rec.body
|
||||
|
||||
const res = await request
|
||||
.get(`/api/${table._id}/records/${existing._id}`)
|
||||
.get(`/api/${table._id}/rows/${existing._id}`)
|
||||
.set(defaultHeaders(app._id, instance._id))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
|
||||
expect(res.body).toEqual({
|
||||
...record,
|
||||
...row,
|
||||
_id: existing._id,
|
||||
_rev: existing._rev,
|
||||
type: "record",
|
||||
type: "row",
|
||||
})
|
||||
})
|
||||
|
||||
it("should list all records for given tableId", async () => {
|
||||
const newRecord = {
|
||||
it("should list all rows for given tableId", async () => {
|
||||
const newRow = {
|
||||
tableId: table._id,
|
||||
name: "Second Contact",
|
||||
status: "new"
|
||||
}
|
||||
await createRecord()
|
||||
await createRecord(newRecord)
|
||||
await createRow()
|
||||
await createRow(newRow)
|
||||
|
||||
const res = await request
|
||||
.get(`/api/${table._id}/records`)
|
||||
.get(`/api/${table._id}/rows`)
|
||||
.set(defaultHeaders(app._id, instance._id))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
|
||||
expect(res.body.length).toBe(2)
|
||||
expect(res.body.find(r => r.name === newRecord.name)).toBeDefined()
|
||||
expect(res.body.find(r => r.name === record.name)).toBeDefined()
|
||||
expect(res.body.find(r => r.name === newRow.name)).toBeDefined()
|
||||
expect(res.body.find(r => r.name === row.name)).toBeDefined()
|
||||
})
|
||||
|
||||
it("lists records when queried by their ID", async () => {
|
||||
const newRecord = {
|
||||
it("lists rows when queried by their ID", async () => {
|
||||
const newRow = {
|
||||
tableId: table._id,
|
||||
name: "Second Contact",
|
||||
status: "new"
|
||||
}
|
||||
const record = await createRecord()
|
||||
const secondRecord = await createRecord(newRecord)
|
||||
const row = await createRow()
|
||||
const secondRow = await createRow(newRow)
|
||||
|
||||
const recordIds = [record.body._id, secondRecord.body._id]
|
||||
const rowIds = [row.body._id, secondRow.body._id]
|
||||
|
||||
const res = await request
|
||||
.post(`/api/records/search`)
|
||||
.post(`/api/rows/search`)
|
||||
.set(defaultHeaders(app._id, instance._id))
|
||||
.send({
|
||||
keys: recordIds
|
||||
keys: rowIds
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
|
||||
expect(res.body.length).toBe(2)
|
||||
expect(res.body.map(response => response._id)).toEqual(expect.arrayContaining(recordIds))
|
||||
expect(res.body.map(response => response._id)).toEqual(expect.arrayContaining(rowIds))
|
||||
})
|
||||
|
||||
it("load should return 404 when record does not exist", async () => {
|
||||
await createRecord()
|
||||
it("load should return 404 when row does not exist", async () => {
|
||||
await createRow()
|
||||
await request
|
||||
.get(`/api/${table._id}/records/not-a-valid-id`)
|
||||
.get(`/api/${table._id}/rows/not-a-valid-id`)
|
||||
.set(defaultHeaders(app._id, instance._id))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(404)
|
||||
})
|
||||
|
||||
it("record values are coerced", async () => {
|
||||
it("row values are coerced", async () => {
|
||||
const str = {type:"string", constraints: { type: "string", presence: false }}
|
||||
const attachment = {type:"attachment", constraints: { type: "array", presence: false }}
|
||||
const bool = {type:"boolean", constraints: { type: "boolean", presence: false }}
|
||||
|
@ -189,8 +189,8 @@ describe("/records", () => {
|
|||
},
|
||||
})
|
||||
|
||||
record = {
|
||||
name: "Test Record",
|
||||
row = {
|
||||
name: "Test Row",
|
||||
stringUndefined: undefined,
|
||||
stringNull: null,
|
||||
stringString: "i am a string",
|
||||
|
@ -215,9 +215,9 @@ describe("/records", () => {
|
|||
attachmentEmpty : "",
|
||||
}
|
||||
|
||||
const id = (await createRecord(record)).body._id
|
||||
const id = (await createRow(row)).body._id
|
||||
|
||||
const saved = (await loadRecord(id)).body
|
||||
const saved = (await loadRow(id)).body
|
||||
|
||||
expect(saved.stringUndefined).toBe(undefined)
|
||||
expect(saved.stringNull).toBe("")
|
||||
|
@ -230,8 +230,8 @@ describe("/records", () => {
|
|||
expect(saved.datetimeEmptyString).toBe(null)
|
||||
expect(saved.datetimeNull).toBe(null)
|
||||
expect(saved.datetimeUndefined).toBe(undefined)
|
||||
expect(saved.datetimeString).toBe(new Date(record.datetimeString).toISOString())
|
||||
expect(saved.datetimeDate).toBe(record.datetimeDate.toISOString())
|
||||
expect(saved.datetimeString).toBe(new Date(row.datetimeString).toISOString())
|
||||
expect(saved.datetimeDate).toBe(row.datetimeDate.toISOString())
|
||||
expect(saved.boolNull).toBe(null)
|
||||
expect(saved.boolEmpty).toBe(null)
|
||||
expect(saved.boolUndefined).toBe(undefined)
|
||||
|
@ -245,11 +245,11 @@ describe("/records", () => {
|
|||
|
||||
describe("patch", () => {
|
||||
it("should update only the fields that are supplied", async () => {
|
||||
const rec = await createRecord()
|
||||
const rec = await createRow()
|
||||
const existing = rec.body
|
||||
|
||||
const res = await request
|
||||
.patch(`/api/${table._id}/records/${existing._id}`)
|
||||
.patch(`/api/${table._id}/rows/${existing._id}`)
|
||||
.send({
|
||||
_id: existing._id,
|
||||
_rev: existing._rev,
|
||||
|
@ -264,18 +264,18 @@ describe("/records", () => {
|
|||
expect(res.body.name).toEqual("Updated Name")
|
||||
expect(res.body.description).toEqual(existing.description)
|
||||
|
||||
const savedRecord = await loadRecord(res.body._id)
|
||||
const savedRow = await loadRow(res.body._id)
|
||||
|
||||
expect(savedRecord.body.description).toEqual(existing.description)
|
||||
expect(savedRecord.body.name).toEqual("Updated Name")
|
||||
expect(savedRow.body.description).toEqual(existing.description)
|
||||
expect(savedRow.body.name).toEqual("Updated Name")
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
describe("validate", () => {
|
||||
it("should return no errors on valid record", async () => {
|
||||
it("should return no errors on valid row", async () => {
|
||||
const result = await request
|
||||
.post(`/api/${table._id}/records/validate`)
|
||||
.post(`/api/${table._id}/rows/validate`)
|
||||
.send({ name: "ivan" })
|
||||
.set(defaultHeaders(app._id, instance._id))
|
||||
.expect('Content-Type', /json/)
|
||||
|
@ -285,9 +285,9 @@ describe("/records", () => {
|
|||
expect(Object.keys(result.body.errors)).toEqual([])
|
||||
})
|
||||
|
||||
it("should errors on invalid record", async () => {
|
||||
it("should errors on invalid row", async () => {
|
||||
const result = await request
|
||||
.post(`/api/${table._id}/records/validate`)
|
||||
.post(`/api/${table._id}/rows/validate`)
|
||||
.send({ name: 1 })
|
||||
.set(defaultHeaders(app._id, instance._id))
|
||||
.expect('Content-Type', /json/)
|
|
@ -50,11 +50,11 @@ describe("/tables", () => {
|
|||
});
|
||||
})
|
||||
|
||||
it("renames all the record fields for a table when a schema key is renamed", async () => {
|
||||
it("renames all the row fields for a table when a schema key is renamed", async () => {
|
||||
const testTable = await createTable(request, app._id, instance._id);
|
||||
|
||||
const testRecord = await request
|
||||
.post(`/api/${testTable._id}/records`)
|
||||
const testRow = await request
|
||||
.post(`/api/${testTable._id}/rows`)
|
||||
.send({
|
||||
name: "test"
|
||||
})
|
||||
|
@ -85,7 +85,7 @@ describe("/tables", () => {
|
|||
expect(updatedTable.body.name).toEqual("TestTable");
|
||||
|
||||
const res = await request
|
||||
.get(`/api/${testTable._id}/records/${testRecord.body._id}`)
|
||||
.get(`/api/${testTable._id}/rows/${testRow.body._id}`)
|
||||
.set(defaultHeaders(app._id, instance._id))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
|
|
|
@ -27,9 +27,9 @@ describe("/views", () => {
|
|||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
|
||||
const createRecord = async record => request
|
||||
.post(`/api/${table._id}/records`)
|
||||
.send(record)
|
||||
const createRow = async row => request
|
||||
.post(`/api/${table._id}/rows`)
|
||||
.send(row)
|
||||
.set(defaultHeaders(app._id, instance._id))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
|
@ -58,7 +58,7 @@ describe("/views", () => {
|
|||
expect(res.res.statusMessage).toEqual("View TestView saved successfully.");
|
||||
})
|
||||
|
||||
it("updates the table record with the new view metadata", async () => {
|
||||
it("updates the table row with the new view metadata", async () => {
|
||||
const res = await createView()
|
||||
expect(res.res.statusMessage).toEqual("View TestView saved successfully.");
|
||||
const updatedTable = await getDocument(instance._id, table._id)
|
||||
|
@ -110,15 +110,15 @@ describe("/views", () => {
|
|||
|
||||
it("returns data for the created view", async () => {
|
||||
await createView()
|
||||
await createRecord({
|
||||
await createRow({
|
||||
tableId: table._id,
|
||||
Price: 1000
|
||||
})
|
||||
await createRecord({
|
||||
await createRow({
|
||||
tableId: table._id,
|
||||
Price: 2000
|
||||
})
|
||||
await createRecord({
|
||||
await createRow({
|
||||
tableId: table._id,
|
||||
Price: 4000
|
||||
})
|
||||
|
@ -138,17 +138,17 @@ describe("/views", () => {
|
|||
groupBy: "Category",
|
||||
tableId: table._id
|
||||
})
|
||||
await createRecord({
|
||||
await createRow({
|
||||
tableId: table._id,
|
||||
Price: 1000,
|
||||
Category: "One"
|
||||
})
|
||||
await createRecord({
|
||||
await createRow({
|
||||
tableId: table._id,
|
||||
Price: 2000,
|
||||
Category: "One"
|
||||
})
|
||||
await createRecord({
|
||||
await createRow({
|
||||
tableId: table._id,
|
||||
Price: 4000,
|
||||
Category: "Two"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const Router = require("@koa/router")
|
||||
const viewController = require("../controllers/view")
|
||||
const recordController = require("../controllers/record")
|
||||
const rowController = require("../controllers/row")
|
||||
const authorized = require("../../middleware/authorized")
|
||||
const { BUILDER, READ_VIEW } = require("../../utilities/accessLevels")
|
||||
const usage = require("../../middleware/usageQuota")
|
||||
|
@ -11,7 +11,7 @@ router
|
|||
.get(
|
||||
"/api/views/:viewName",
|
||||
authorized(READ_VIEW, ctx => ctx.params.viewName),
|
||||
recordController.fetchView
|
||||
rowController.fetchView
|
||||
)
|
||||
.get("/api/views", authorized(BUILDER), viewController.fetch)
|
||||
.delete(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const sendEmail = require("./steps/sendEmail")
|
||||
const createRecord = require("./steps/createRecord")
|
||||
const updateRecord = require("./steps/updateRecord")
|
||||
const deleteRecord = require("./steps/deleteRecord")
|
||||
const createRow = require("./steps/createRow")
|
||||
const updateRow = require("./steps/updateRow")
|
||||
const deleteRow = require("./steps/deleteRow")
|
||||
const createUser = require("./steps/createUser")
|
||||
const environment = require("../environment")
|
||||
const download = require("download")
|
||||
|
@ -17,16 +17,16 @@ const DEFAULT_DIRECTORY = ".budibase-automations"
|
|||
const AUTOMATION_MANIFEST = "manifest.json"
|
||||
const BUILTIN_ACTIONS = {
|
||||
SEND_EMAIL: sendEmail.run,
|
||||
CREATE_RECORD: createRecord.run,
|
||||
UPDATE_RECORD: updateRecord.run,
|
||||
DELETE_RECORD: deleteRecord.run,
|
||||
CREATE_ROW: createRow.run,
|
||||
UPDATE_ROW: updateRow.run,
|
||||
DELETE_ROW: deleteRow.run,
|
||||
CREATE_USER: createUser.run,
|
||||
}
|
||||
const BUILTIN_DEFINITIONS = {
|
||||
SEND_EMAIL: sendEmail.definition,
|
||||
CREATE_RECORD: createRecord.definition,
|
||||
UPDATE_RECORD: updateRecord.definition,
|
||||
DELETE_RECORD: deleteRecord.definition,
|
||||
CREATE_ROW: createRow.definition,
|
||||
UPDATE_ROW: updateRow.definition,
|
||||
DELETE_ROW: deleteRow.definition,
|
||||
CREATE_USER: createUser.definition,
|
||||
}
|
||||
|
||||
|
|
|
@ -84,33 +84,33 @@ module.exports.cleanInputValues = (inputs, schema) => {
|
|||
}
|
||||
|
||||
/**
|
||||
* Given a record input like a save or update record we need to clean the inputs against a schema that is not part of
|
||||
* Given a row input like a save or update row we need to clean the inputs against a schema that is not part of
|
||||
* the automation but is instead part of the Table/Table. This function will get the table schema and use it to instead
|
||||
* perform the cleanInputValues function on the input record.
|
||||
* perform the cleanInputValues function on the input row.
|
||||
*
|
||||
* @param {string} instanceId The instance which the Table/Table is contained under.
|
||||
* @param {string} tableId The ID of the Table/Table which the schema is to be retrieved for.
|
||||
* @param {object} record The input record structure which requires clean-up after having been through mustache statements.
|
||||
* @returns {Promise<Object>} The cleaned up records object, will should now have all the required primitive types.
|
||||
* @param {object} row The input row structure which requires clean-up after having been through mustache statements.
|
||||
* @returns {Promise<Object>} The cleaned up rows object, will should now have all the required primitive types.
|
||||
*/
|
||||
module.exports.cleanUpRecord = async (instanceId, tableId, record) => {
|
||||
module.exports.cleanUpRow = async (instanceId, tableId, row) => {
|
||||
const db = new CouchDB(instanceId)
|
||||
const table = await db.get(tableId)
|
||||
|
||||
return module.exports.cleanInputValues(record, { properties: table.schema })
|
||||
return module.exports.cleanInputValues(row, { properties: table.schema })
|
||||
}
|
||||
|
||||
/**
|
||||
* A utility function for the cleanUpRecord, which can be used if only the record ID is known (not the table ID) to clean
|
||||
* up a record after mustache statements have been replaced. This is specifically useful for the update record action.
|
||||
* A utility function for the cleanUpRow, which can be used if only the row ID is known (not the table ID) to clean
|
||||
* up a row after mustache statements have been replaced. This is specifically useful for the update row action.
|
||||
*
|
||||
* @param {string} instanceId The instance which the Table/Table is contained under.
|
||||
* @param {string} recordId The ID of the record from which the tableId will be extracted, to get the Table/Table schema.
|
||||
* @param {object} record The input record structure which requires clean-up after having been through mustache statements.
|
||||
* @returns {Promise<Object>} The cleaned up records object, which will now have all the required primitive types.
|
||||
* @param {string} rowId The ID of the row from which the tableId will be extracted, to get the Table/Table schema.
|
||||
* @param {object} row The input row structure which requires clean-up after having been through mustache statements.
|
||||
* @returns {Promise<Object>} The cleaned up rows object, which will now have all the required primitive types.
|
||||
*/
|
||||
module.exports.cleanUpRecordById = async (instanceId, recordId, record) => {
|
||||
module.exports.cleanUpRowById = async (instanceId, rowId, row) => {
|
||||
const db = new CouchDB(instanceId)
|
||||
const foundRecord = await db.get(recordId)
|
||||
return module.exports.cleanUpRecord(instanceId, foundRecord.tableId, record)
|
||||
const foundRow = await db.get(rowId)
|
||||
return module.exports.cleanUpRow(instanceId, foundRow.tableId, row)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const recordController = require("../../api/controllers/record")
|
||||
const rowController = require("../../api/controllers/row")
|
||||
const automationUtils = require("../automationUtils")
|
||||
const environment = require("../../environment")
|
||||
const usage = require("../../utilities/usageQuota")
|
||||
|
@ -9,12 +9,12 @@ module.exports.definition = {
|
|||
icon: "ri-save-3-fill",
|
||||
description: "Add a row to your database",
|
||||
type: "ACTION",
|
||||
stepId: "CREATE_RECORD",
|
||||
stepId: "CREATE_ROW",
|
||||
inputs: {},
|
||||
schema: {
|
||||
inputs: {
|
||||
properties: {
|
||||
record: {
|
||||
row: {
|
||||
type: "object",
|
||||
properties: {
|
||||
tableId: {
|
||||
|
@ -22,18 +22,18 @@ module.exports.definition = {
|
|||
customType: "table",
|
||||
},
|
||||
},
|
||||
customType: "record",
|
||||
customType: "row",
|
||||
title: "Table",
|
||||
required: ["tableId"],
|
||||
},
|
||||
},
|
||||
required: ["record"],
|
||||
required: ["row"],
|
||||
},
|
||||
outputs: {
|
||||
properties: {
|
||||
record: {
|
||||
row: {
|
||||
type: "object",
|
||||
customType: "record",
|
||||
customType: "row",
|
||||
description: "The new row",
|
||||
},
|
||||
response: {
|
||||
|
@ -60,32 +60,32 @@ module.exports.definition = {
|
|||
|
||||
module.exports.run = async function({ inputs, instanceId, apiKey }) {
|
||||
// TODO: better logging of when actions are missed due to missing parameters
|
||||
if (inputs.record == null || inputs.record.tableId == null) {
|
||||
if (inputs.row == null || inputs.row.tableId == null) {
|
||||
return
|
||||
}
|
||||
inputs.record = await automationUtils.cleanUpRecord(
|
||||
inputs.row = await automationUtils.cleanUpRow(
|
||||
instanceId,
|
||||
inputs.record.tableId,
|
||||
inputs.record
|
||||
inputs.row.tableId,
|
||||
inputs.row
|
||||
)
|
||||
// have to clean up the record, remove the table from it
|
||||
// have to clean up the row, remove the table from it
|
||||
const ctx = {
|
||||
params: {
|
||||
tableId: inputs.record.tableId,
|
||||
tableId: inputs.row.tableId,
|
||||
},
|
||||
request: {
|
||||
body: inputs.record,
|
||||
body: inputs.row,
|
||||
},
|
||||
user: { instanceId },
|
||||
}
|
||||
|
||||
try {
|
||||
if (environment.CLOUD) {
|
||||
await usage.update(apiKey, usage.Properties.RECORD, 1)
|
||||
await usage.update(apiKey, usage.Properties.ROW, 1)
|
||||
}
|
||||
await recordController.save(ctx)
|
||||
await rowController.save(ctx)
|
||||
return {
|
||||
record: inputs.record,
|
||||
row: inputs.row,
|
||||
response: ctx.body,
|
||||
id: ctx.body._id,
|
||||
revision: ctx.body._rev,
|
|
@ -1,4 +1,4 @@
|
|||
const recordController = require("../../api/controllers/record")
|
||||
const rowController = require("../../api/controllers/row")
|
||||
const environment = require("../../environment")
|
||||
const usage = require("../../utilities/usageQuota")
|
||||
|
||||
|
@ -8,7 +8,7 @@ module.exports.definition = {
|
|||
name: "Delete Row",
|
||||
tagline: "Delete a {{inputs.enriched.table.name}} row",
|
||||
type: "ACTION",
|
||||
stepId: "DELETE_RECORD",
|
||||
stepId: "DELETE_ROW",
|
||||
inputs: {},
|
||||
schema: {
|
||||
inputs: {
|
||||
|
@ -31,9 +31,9 @@ module.exports.definition = {
|
|||
},
|
||||
outputs: {
|
||||
properties: {
|
||||
record: {
|
||||
row: {
|
||||
type: "object",
|
||||
customType: "record",
|
||||
customType: "row",
|
||||
description: "The deleted row",
|
||||
},
|
||||
response: {
|
||||
|
@ -45,7 +45,7 @@ module.exports.definition = {
|
|||
description: "Whether the action was successful",
|
||||
},
|
||||
},
|
||||
required: ["record", "success"],
|
||||
required: ["row", "success"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ module.exports.run = async function({ inputs, instanceId, apiKey }) {
|
|||
let ctx = {
|
||||
params: {
|
||||
tableId: inputs.tableId,
|
||||
recordId: inputs.id,
|
||||
rowId: inputs.id,
|
||||
revId: inputs.revision,
|
||||
},
|
||||
user: { instanceId },
|
||||
|
@ -66,12 +66,12 @@ module.exports.run = async function({ inputs, instanceId, apiKey }) {
|
|||
|
||||
try {
|
||||
if (environment.CLOUD) {
|
||||
await usage.update(apiKey, usage.Properties.RECORD, -1)
|
||||
await usage.update(apiKey, usage.Properties.ROW, -1)
|
||||
}
|
||||
await recordController.destroy(ctx)
|
||||
await rowController.destroy(ctx)
|
||||
return {
|
||||
response: ctx.body,
|
||||
record: ctx.record,
|
||||
row: ctx.row,
|
||||
success: ctx.status === 200,
|
||||
}
|
||||
} catch (err) {
|
|
@ -1,35 +1,35 @@
|
|||
const recordController = require("../../api/controllers/record")
|
||||
const rowController = require("../../api/controllers/row")
|
||||
const automationUtils = require("../automationUtils")
|
||||
|
||||
module.exports.definition = {
|
||||
name: "Update Row",
|
||||
tagline: "Update a {{inputs.enriched.table.name}} record",
|
||||
tagline: "Update a {{inputs.enriched.table.name}} row",
|
||||
icon: "ri-refresh-fill",
|
||||
description: "Update a row in your database",
|
||||
type: "ACTION",
|
||||
stepId: "UPDATE_RECORD",
|
||||
stepId: "UPDATE_ROW",
|
||||
inputs: {},
|
||||
schema: {
|
||||
inputs: {
|
||||
properties: {
|
||||
record: {
|
||||
row: {
|
||||
type: "object",
|
||||
customType: "record",
|
||||
customType: "row",
|
||||
title: "Table",
|
||||
},
|
||||
recordId: {
|
||||
rowId: {
|
||||
type: "string",
|
||||
title: "Row ID",
|
||||
},
|
||||
},
|
||||
required: ["record", "recordId"],
|
||||
required: ["row", "rowId"],
|
||||
},
|
||||
outputs: {
|
||||
properties: {
|
||||
record: {
|
||||
row: {
|
||||
type: "object",
|
||||
customType: "record",
|
||||
description: "The updated record",
|
||||
customType: "row",
|
||||
description: "The updated row",
|
||||
},
|
||||
response: {
|
||||
type: "object",
|
||||
|
@ -41,11 +41,11 @@ module.exports.definition = {
|
|||
},
|
||||
id: {
|
||||
type: "string",
|
||||
description: "The identifier of the updated record",
|
||||
description: "The identifier of the updated row",
|
||||
},
|
||||
revision: {
|
||||
type: "string",
|
||||
description: "The revision of the updated record",
|
||||
description: "The revision of the updated row",
|
||||
},
|
||||
},
|
||||
required: ["success", "id", "revision"],
|
||||
|
@ -54,37 +54,37 @@ module.exports.definition = {
|
|||
}
|
||||
|
||||
module.exports.run = async function({ inputs, instanceId }) {
|
||||
if (inputs.recordId == null || inputs.record == null) {
|
||||
if (inputs.rowId == null || inputs.row == null) {
|
||||
return
|
||||
}
|
||||
|
||||
inputs.record = await automationUtils.cleanUpRecordById(
|
||||
inputs.row = await automationUtils.cleanUpRowById(
|
||||
instanceId,
|
||||
inputs.recordId,
|
||||
inputs.record
|
||||
inputs.rowId,
|
||||
inputs.row
|
||||
)
|
||||
// clear any falsy properties so that they aren't updated
|
||||
for (let propKey of Object.keys(inputs.record)) {
|
||||
if (!inputs.record[propKey] || inputs.record[propKey] === "") {
|
||||
delete inputs.record[propKey]
|
||||
for (let propKey of Object.keys(inputs.row)) {
|
||||
if (!inputs.row[propKey] || inputs.row[propKey] === "") {
|
||||
delete inputs.row[propKey]
|
||||
}
|
||||
}
|
||||
|
||||
// have to clean up the record, remove the table from it
|
||||
// have to clean up the row, remove the table from it
|
||||
const ctx = {
|
||||
params: {
|
||||
id: inputs.recordId,
|
||||
id: inputs.rowId,
|
||||
},
|
||||
request: {
|
||||
body: inputs.record,
|
||||
body: inputs.row,
|
||||
},
|
||||
user: { instanceId },
|
||||
}
|
||||
|
||||
try {
|
||||
await recordController.patch(ctx)
|
||||
await rowController.patch(ctx)
|
||||
return {
|
||||
record: ctx.body,
|
||||
row: ctx.body,
|
||||
response: ctx.message,
|
||||
id: ctx.body._id,
|
||||
revision: ctx.body._rev,
|
|
@ -11,13 +11,13 @@ const FAKE_NUMBER = 1
|
|||
const FAKE_DATETIME = "1970-01-01T00:00:00.000Z"
|
||||
|
||||
const BUILTIN_DEFINITIONS = {
|
||||
RECORD_SAVED: {
|
||||
ROW_SAVED: {
|
||||
name: "Row Saved",
|
||||
event: "record:save",
|
||||
event: "row:save",
|
||||
icon: "ri-save-line",
|
||||
tagline: "Row is added to {{inputs.enriched.table.name}}",
|
||||
description: "Fired when a row is saved to your database",
|
||||
stepId: "RECORD_SAVED",
|
||||
stepId: "ROW_SAVED",
|
||||
inputs: {},
|
||||
schema: {
|
||||
inputs: {
|
||||
|
@ -32,9 +32,9 @@ const BUILTIN_DEFINITIONS = {
|
|||
},
|
||||
outputs: {
|
||||
properties: {
|
||||
record: {
|
||||
row: {
|
||||
type: "object",
|
||||
customType: "record",
|
||||
customType: "row",
|
||||
description: "The new row that was saved",
|
||||
},
|
||||
id: {
|
||||
|
@ -46,18 +46,18 @@ const BUILTIN_DEFINITIONS = {
|
|||
description: "Revision of row",
|
||||
},
|
||||
},
|
||||
required: ["record", "id"],
|
||||
required: ["row", "id"],
|
||||
},
|
||||
},
|
||||
type: "TRIGGER",
|
||||
},
|
||||
RECORD_DELETED: {
|
||||
ROW_DELETED: {
|
||||
name: "Row Deleted",
|
||||
event: "record:delete",
|
||||
event: "row:delete",
|
||||
icon: "ri-delete-bin-line",
|
||||
tagline: "Row is deleted from {{inputs.enriched.table.name}}",
|
||||
description: "Fired when a row is deleted from your database",
|
||||
stepId: "RECORD_DELETED",
|
||||
stepId: "ROW_DELETED",
|
||||
inputs: {},
|
||||
schema: {
|
||||
inputs: {
|
||||
|
@ -72,20 +72,20 @@ const BUILTIN_DEFINITIONS = {
|
|||
},
|
||||
outputs: {
|
||||
properties: {
|
||||
record: {
|
||||
row: {
|
||||
type: "object",
|
||||
customType: "record",
|
||||
customType: "row",
|
||||
description: "The row that was deleted",
|
||||
},
|
||||
},
|
||||
required: ["record", "id"],
|
||||
required: ["row", "id"],
|
||||
},
|
||||
},
|
||||
type: "TRIGGER",
|
||||
},
|
||||
}
|
||||
|
||||
async function queueRelevantRecordAutomations(event, eventType) {
|
||||
async function queueRelevantRowAutomations(event, eventType) {
|
||||
if (event.instanceId == null) {
|
||||
throw `No instanceId specified for ${eventType} - check event emitters.`
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ async function queueRelevantRecordAutomations(event, eventType) {
|
|||
if (
|
||||
!automation.live ||
|
||||
!automationTrigger.inputs ||
|
||||
automationTrigger.inputs.tableId !== event.record.tableId
|
||||
automationTrigger.inputs.tableId !== event.row.tableId
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
@ -116,27 +116,27 @@ async function queueRelevantRecordAutomations(event, eventType) {
|
|||
}
|
||||
}
|
||||
|
||||
emitter.on("record:save", async function(event) {
|
||||
if (!event || !event.record || !event.record.tableId) {
|
||||
emitter.on("row:save", async function(event) {
|
||||
if (!event || !event.row || !event.row.tableId) {
|
||||
return
|
||||
}
|
||||
await queueRelevantRecordAutomations(event, "record:save")
|
||||
await queueRelevantRowAutomations(event, "row:save")
|
||||
})
|
||||
|
||||
emitter.on("record:delete", async function(event) {
|
||||
if (!event || !event.record || !event.record.tableId) {
|
||||
emitter.on("row:delete", async function(event) {
|
||||
if (!event || !event.row || !event.row.tableId) {
|
||||
return
|
||||
}
|
||||
await queueRelevantRecordAutomations(event, "record:delete")
|
||||
await queueRelevantRowAutomations(event, "row:delete")
|
||||
})
|
||||
|
||||
async function fillRecordOutput(automation, params) {
|
||||
async function fillRowOutput(automation, params) {
|
||||
let triggerSchema = automation.definition.trigger
|
||||
let tableId = triggerSchema.inputs.tableId
|
||||
const db = new CouchDB(params.instanceId)
|
||||
try {
|
||||
let table = await db.get(tableId)
|
||||
let record = {}
|
||||
let row = {}
|
||||
for (let schemaKey of Object.keys(table.schema)) {
|
||||
if (params[schemaKey] != null) {
|
||||
continue
|
||||
|
@ -144,20 +144,20 @@ async function fillRecordOutput(automation, params) {
|
|||
let propSchema = table.schema[schemaKey]
|
||||
switch (propSchema.constraints.type) {
|
||||
case "string":
|
||||
record[schemaKey] = FAKE_STRING
|
||||
row[schemaKey] = FAKE_STRING
|
||||
break
|
||||
case "boolean":
|
||||
record[schemaKey] = FAKE_BOOL
|
||||
row[schemaKey] = FAKE_BOOL
|
||||
break
|
||||
case "number":
|
||||
record[schemaKey] = FAKE_NUMBER
|
||||
row[schemaKey] = FAKE_NUMBER
|
||||
break
|
||||
case "datetime":
|
||||
record[schemaKey] = FAKE_DATETIME
|
||||
row[schemaKey] = FAKE_DATETIME
|
||||
break
|
||||
}
|
||||
}
|
||||
params.record = record
|
||||
params.row = row
|
||||
} catch (err) {
|
||||
throw "Failed to find table for trigger"
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ module.exports.externalTrigger = async function(automation, params) {
|
|||
automation.definition.trigger != null &&
|
||||
automation.definition.trigger.inputs.tableId != null
|
||||
) {
|
||||
params = await fillRecordOutput(automation, params)
|
||||
params = await fillRowOutput(automation, params)
|
||||
}
|
||||
|
||||
automationQueue.add({ automation, event: params })
|
||||
|
|
|
@ -10,40 +10,40 @@ const { generateLinkID } = require("../utils")
|
|||
* @param {string} tableId2 The ID of the second table (the linked).
|
||||
* @param {string} fieldName1 The name of the field in the linker table.
|
||||
* @param {string} fieldName2 The name of the field in the linked table.
|
||||
* @param {string} recordId1 The ID of the record which is acting as the linker.
|
||||
* @param {string} recordId2 The ID of the record which is acting as the linked.
|
||||
* @param {string} rowId1 The ID of the row which is acting as the linker.
|
||||
* @param {string} rowId2 The ID of the row which is acting as the linked.
|
||||
* @constructor
|
||||
*/
|
||||
function LinkDocument(
|
||||
tableId1,
|
||||
fieldName1,
|
||||
recordId1,
|
||||
rowId1,
|
||||
tableId2,
|
||||
fieldName2,
|
||||
recordId2
|
||||
rowId2
|
||||
) {
|
||||
// build the ID out of unique references to this link document
|
||||
this._id = generateLinkID(tableId1, tableId2, recordId1, recordId2)
|
||||
this._id = generateLinkID(tableId1, tableId2, rowId1, rowId2)
|
||||
// required for referencing in view
|
||||
this.type = "link"
|
||||
this.doc1 = {
|
||||
tableId: tableId1,
|
||||
fieldName: fieldName1,
|
||||
recordId: recordId1,
|
||||
rowId: rowId1,
|
||||
}
|
||||
this.doc2 = {
|
||||
tableId: tableId2,
|
||||
fieldName: fieldName2,
|
||||
recordId: recordId2,
|
||||
rowId: rowId2,
|
||||
}
|
||||
}
|
||||
|
||||
class LinkController {
|
||||
constructor({ instanceId, tableId, record, table, oldTable }) {
|
||||
constructor({ instanceId, tableId, row, table, oldTable }) {
|
||||
this._instanceId = instanceId
|
||||
this._db = new CouchDB(instanceId)
|
||||
this._tableId = tableId
|
||||
this._record = record
|
||||
this._row = row
|
||||
this._table = table
|
||||
this._oldTable = oldTable
|
||||
}
|
||||
|
@ -84,11 +84,11 @@ class LinkController {
|
|||
/**
|
||||
* Utility function for main getLinkDocuments function - refer to it for functionality.
|
||||
*/
|
||||
getRecordLinkDocs(recordId) {
|
||||
getRowLinkDocs(rowId) {
|
||||
return getLinkDocuments({
|
||||
instanceId: this._instanceId,
|
||||
tableId: this._tableId,
|
||||
recordId,
|
||||
rowId,
|
||||
includeDocs: IncludeDocs.INCLUDE,
|
||||
})
|
||||
}
|
||||
|
@ -105,43 +105,43 @@ class LinkController {
|
|||
}
|
||||
|
||||
// all operations here will assume that the table
|
||||
// this operation is related to has linked records
|
||||
// this operation is related to has linked rows
|
||||
/**
|
||||
* When a record is saved this will carry out the necessary operations to make sure
|
||||
* When a row is saved this will carry out the necessary operations to make sure
|
||||
* the link has been created/updated.
|
||||
* @returns {Promise<object>} returns the record that has been cleaned and prepared to be written to the DB - links
|
||||
* @returns {Promise<object>} returns the row that has been cleaned and prepared to be written to the DB - links
|
||||
* have also been created.
|
||||
*/
|
||||
async recordSaved() {
|
||||
async rowSaved() {
|
||||
const table = await this.table()
|
||||
const record = this._record
|
||||
const row = this._row
|
||||
const operations = []
|
||||
// get link docs to compare against
|
||||
const linkDocs = await this.getRecordLinkDocs(record._id)
|
||||
const linkDocs = await this.getRowLinkDocs(row._id)
|
||||
for (let fieldName of Object.keys(table.schema)) {
|
||||
// get the links this record wants to make
|
||||
const recordField = record[fieldName]
|
||||
// get the links this row wants to make
|
||||
const rowField = row[fieldName]
|
||||
const field = table.schema[fieldName]
|
||||
if (field.type === "link" && recordField != null) {
|
||||
// check which links actual pertain to the update in this record
|
||||
if (field.type === "link" && rowField != null) {
|
||||
// check which links actual pertain to the update in this row
|
||||
const thisFieldLinkDocs = linkDocs.filter(
|
||||
linkDoc =>
|
||||
linkDoc.doc1.fieldName === fieldName ||
|
||||
linkDoc.doc2.fieldName === fieldName
|
||||
)
|
||||
const linkDocIds = thisFieldLinkDocs.map(linkDoc => {
|
||||
return linkDoc.doc1.recordId === record._id
|
||||
? linkDoc.doc2.recordId
|
||||
: linkDoc.doc1.recordId
|
||||
return linkDoc.doc1.rowId === row._id
|
||||
? linkDoc.doc2.rowId
|
||||
: linkDoc.doc1.rowId
|
||||
})
|
||||
// iterate through the link IDs in the record field, see if any don't exist already
|
||||
for (let linkId of recordField) {
|
||||
// iterate through the link IDs in the row field, see if any don't exist already
|
||||
for (let linkId of rowField) {
|
||||
if (linkId && linkId !== "" && linkDocIds.indexOf(linkId) === -1) {
|
||||
operations.push(
|
||||
new LinkDocument(
|
||||
table._id,
|
||||
fieldName,
|
||||
record._id,
|
||||
row._id,
|
||||
field.tableId,
|
||||
field.fieldName,
|
||||
linkId
|
||||
|
@ -154,7 +154,7 @@ class LinkController {
|
|||
.filter(doc => {
|
||||
let correctDoc =
|
||||
doc.doc1.fieldName === fieldName ? doc.doc2 : doc.doc1
|
||||
return recordField.indexOf(correctDoc.recordId) === -1
|
||||
return rowField.indexOf(correctDoc.rowId) === -1
|
||||
})
|
||||
.map(doc => {
|
||||
return { ...doc, _deleted: true }
|
||||
|
@ -162,23 +162,23 @@ class LinkController {
|
|||
// now add the docs to be deleted to the bulk operation
|
||||
operations.push(...toDeleteDocs)
|
||||
// replace this field with a simple entry to denote there are links
|
||||
delete record[fieldName]
|
||||
delete row[fieldName]
|
||||
}
|
||||
}
|
||||
await this._db.bulkDocs(operations)
|
||||
return record
|
||||
return row
|
||||
}
|
||||
|
||||
/**
|
||||
* When a record is deleted this will carry out the necessary operations to make sure
|
||||
* When a row is deleted this will carry out the necessary operations to make sure
|
||||
* any links that existed have been removed.
|
||||
* @returns {Promise<object>} The operation has been completed and the link documents should now
|
||||
* be accurate. This also returns the record that was deleted.
|
||||
* be accurate. This also returns the row that was deleted.
|
||||
*/
|
||||
async recordDeleted() {
|
||||
const record = this._record
|
||||
async rowDeleted() {
|
||||
const row = this._row
|
||||
// need to get the full link docs to be be able to delete it
|
||||
const linkDocs = await this.getRecordLinkDocs(record._id)
|
||||
const linkDocs = await this.getRowLinkDocs(row._id)
|
||||
if (linkDocs.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
@ -189,11 +189,11 @@ class LinkController {
|
|||
}
|
||||
})
|
||||
await this._db.bulkDocs(toDelete)
|
||||
return record
|
||||
return row
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a field from a table as well as any linked records that pertained to it.
|
||||
* Remove a field from a table as well as any linked rows that pertained to it.
|
||||
* @param {string} fieldName The field to be removed from the table.
|
||||
* @returns {Promise<void>} The table has now been updated.
|
||||
*/
|
|
@ -2,14 +2,14 @@ const LinkController = require("./LinkController")
|
|||
const { IncludeDocs, getLinkDocuments, createLinkView } = require("./linkUtils")
|
||||
|
||||
/**
|
||||
* This functionality makes sure that when records with links are created, updated or deleted they are processed
|
||||
* This functionality makes sure that when rows with links are created, updated or deleted they are processed
|
||||
* correctly - making sure that no stale links are left around and that all links have been made successfully.
|
||||
*/
|
||||
|
||||
const EventType = {
|
||||
RECORD_SAVE: "record:save",
|
||||
RECORD_UPDATE: "record:update",
|
||||
RECORD_DELETE: "record:delete",
|
||||
ROW_SAVE: "row:save",
|
||||
ROW_UPDATE: "row:update",
|
||||
ROW_DELETE: "row:delete",
|
||||
TABLE_SAVE: "table:save",
|
||||
TABLE_UPDATED: "table:updated",
|
||||
TABLE_DELETE: "table:delete",
|
||||
|
@ -22,21 +22,21 @@ exports.getLinkDocuments = getLinkDocuments
|
|||
exports.createLinkView = createLinkView
|
||||
|
||||
/**
|
||||
* Update link documents for a record or table - this is to be called by the API controller when a change is occurring.
|
||||
* Update link documents for a row or table - this is to be called by the API controller when a change is occurring.
|
||||
* @param {string} eventType states what type of change which is occurring, means this can be expanded upon in the
|
||||
* future quite easily (all updates go through one function).
|
||||
* @param {string} instanceId The ID of the instance in which the change is occurring.
|
||||
* @param {string} tableId The ID of the of the table which is being changed.
|
||||
* @param {object|null} record The record which is changing, e.g. created, updated or deleted.
|
||||
* @param {object|null} row The row which is changing, e.g. created, updated or deleted.
|
||||
* @param {object|null} table If the table has already been retrieved this can be used to reduce database gets.
|
||||
* @param {object|null} oldTable If the table is being updated then the old table can be provided for differencing.
|
||||
* @returns {Promise<object>} When the update is complete this will respond successfully. Returns the record for
|
||||
* record operations and the table for table operations.
|
||||
* @returns {Promise<object>} When the update is complete this will respond successfully. Returns the row for
|
||||
* row operations and the table for table operations.
|
||||
*/
|
||||
exports.updateLinks = async function({
|
||||
eventType,
|
||||
instanceId,
|
||||
record,
|
||||
row,
|
||||
tableId,
|
||||
table,
|
||||
oldTable,
|
||||
|
@ -54,14 +54,14 @@ exports.updateLinks = async function({
|
|||
(oldTable == null ||
|
||||
!(await linkController.doesTableHaveLinkedFields(oldTable)))
|
||||
) {
|
||||
return record
|
||||
return row
|
||||
}
|
||||
switch (eventType) {
|
||||
case EventType.RECORD_SAVE:
|
||||
case EventType.RECORD_UPDATE:
|
||||
return await linkController.recordSaved()
|
||||
case EventType.RECORD_DELETE:
|
||||
return await linkController.recordDeleted()
|
||||
case EventType.ROW_SAVE:
|
||||
case EventType.ROW_UPDATE:
|
||||
return await linkController.rowSaved()
|
||||
case EventType.ROW_DELETE:
|
||||
return await linkController.rowDeleted()
|
||||
case EventType.TABLE_SAVE:
|
||||
return await linkController.tableSaved()
|
||||
case EventType.TABLE_UPDATED:
|
||||
|
@ -69,52 +69,52 @@ exports.updateLinks = async function({
|
|||
case EventType.TABLE_DELETE:
|
||||
return await linkController.tableDeleted()
|
||||
default:
|
||||
throw "Type of event is not known, linked record handler requires update."
|
||||
throw "Type of event is not known, linked row handler requires update."
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a record with information about the links that pertain to it.
|
||||
* @param {string} instanceId The instance in which this record has been created.
|
||||
* @param {object} records The record(s) themselves which is to be updated with info (if applicable). This can be
|
||||
* a single record object or an array of records - both will be handled.
|
||||
* @returns {Promise<object>} The updated record (this may be the same if no links were found). If an array was input
|
||||
* Update a row with information about the links that pertain to it.
|
||||
* @param {string} instanceId The instance in which this row has been created.
|
||||
* @param {object} rows The row(s) themselves which is to be updated with info (if applicable). This can be
|
||||
* a single row object or an array of rows - both will be handled.
|
||||
* @returns {Promise<object>} The updated row (this may be the same if no links were found). If an array was input
|
||||
* then an array will be output, object input -> object output.
|
||||
*/
|
||||
exports.attachLinkInfo = async (instanceId, records) => {
|
||||
// handle a single record as well as multiple
|
||||
exports.attachLinkInfo = async (instanceId, rows) => {
|
||||
// handle a single row as well as multiple
|
||||
let wasArray = true
|
||||
if (!(records instanceof Array)) {
|
||||
records = [records]
|
||||
if (!(rows instanceof Array)) {
|
||||
rows = [rows]
|
||||
wasArray = false
|
||||
}
|
||||
// start by getting all the link values for performance reasons
|
||||
let responses = await Promise.all(
|
||||
records.map(record =>
|
||||
rows.map(row =>
|
||||
getLinkDocuments({
|
||||
instanceId,
|
||||
tableId: record.tableId,
|
||||
recordId: record._id,
|
||||
tableId: row.tableId,
|
||||
rowId: row._id,
|
||||
includeDocs: IncludeDocs.EXCLUDE,
|
||||
})
|
||||
)
|
||||
)
|
||||
// can just use an index to access responses, order maintained
|
||||
let index = 0
|
||||
// now iterate through the records and all field information
|
||||
for (let record of records) {
|
||||
// get all links for record, ignore fieldName for now
|
||||
// now iterate through the rows and all field information
|
||||
for (let row of rows) {
|
||||
// get all links for row, ignore fieldName for now
|
||||
const linkVals = responses[index++]
|
||||
for (let linkVal of linkVals) {
|
||||
// work out which link pertains to this record
|
||||
if (!(record[linkVal.fieldName] instanceof Array)) {
|
||||
record[linkVal.fieldName] = [linkVal.id]
|
||||
// work out which link pertains to this row
|
||||
if (!(row[linkVal.fieldName] instanceof Array)) {
|
||||
row[linkVal.fieldName] = [linkVal.id]
|
||||
} else {
|
||||
record[linkVal.fieldName].push(linkVal.id)
|
||||
row[linkVal.fieldName].push(linkVal.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
// if it was an array when it came in then handle it as an array in response
|
||||
// otherwise return the first element as there was only one input
|
||||
return wasArray ? records : records[0]
|
||||
return wasArray ? rows : rows[0]
|
||||
}
|
|
@ -25,12 +25,12 @@ exports.createLinkView = async instanceId => {
|
|||
if (doc.type === "link") {
|
||||
let doc1 = doc.doc1
|
||||
let doc2 = doc.doc2
|
||||
emit([doc1.tableId, doc1.recordId], {
|
||||
id: doc2.recordId,
|
||||
emit([doc1.tableId, doc1.rowId], {
|
||||
id: doc2.rowId,
|
||||
fieldName: doc1.fieldName,
|
||||
})
|
||||
emit([doc2.tableId, doc2.recordId], {
|
||||
id: doc1.recordId,
|
||||
emit([doc2.tableId, doc2.rowId], {
|
||||
id: doc1.rowId,
|
||||
fieldName: doc2.fieldName,
|
||||
})
|
||||
}
|
||||
|
@ -45,11 +45,11 @@ exports.createLinkView = async instanceId => {
|
|||
|
||||
/**
|
||||
* Gets the linking documents, not the linked documents themselves.
|
||||
* @param {string} instanceId The instance in which we are searching for linked records.
|
||||
* @param {string} tableId The table which we are searching for linked records against.
|
||||
* @param {string} instanceId The instance in which we are searching for linked rows.
|
||||
* @param {string} tableId The table which we are searching for linked rows against.
|
||||
* @param {string|null} fieldName The name of column/field which is being altered, only looking for
|
||||
* linking documents that are related to it. If this is not specified then the table level will be assumed.
|
||||
* @param {string|null} recordId The ID of the record which we want to find linking documents for -
|
||||
* @param {string|null} rowId The ID of the row which we want to find linking documents for -
|
||||
* if this is not specified then it will assume table or field level depending on whether the
|
||||
* field name has been specified.
|
||||
* @param {boolean|null} includeDocs whether to include docs in the response call, this is considerably slower so only
|
||||
|
@ -60,13 +60,13 @@ exports.createLinkView = async instanceId => {
|
|||
exports.getLinkDocuments = async function({
|
||||
instanceId,
|
||||
tableId,
|
||||
recordId,
|
||||
rowId,
|
||||
includeDocs,
|
||||
}) {
|
||||
const db = new CouchDB(instanceId)
|
||||
let params
|
||||
if (recordId != null) {
|
||||
params = { key: [tableId, recordId] }
|
||||
if (rowId != null) {
|
||||
params = { key: [tableId, rowId] }
|
||||
}
|
||||
// only table is known
|
||||
else {
|
|
@ -5,7 +5,7 @@ const SEPARATOR = "_"
|
|||
|
||||
const DocumentTypes = {
|
||||
TABLE: "ta",
|
||||
RECORD: "re",
|
||||
ROW: "re",
|
||||
USER: "us",
|
||||
AUTOMATION: "au",
|
||||
LINK: "li",
|
||||
|
@ -19,7 +19,7 @@ exports.SEPARATOR = SEPARATOR
|
|||
/**
|
||||
* If creating DB allDocs/query params with only a single top level ID this can be used, this
|
||||
* is usually the case as most of our docs are top level e.g. tables, automations, users and so on.
|
||||
* More complex cases such as link docs and records which have multiple levels of IDs that their
|
||||
* More complex cases such as link docs and rows which have multiple levels of IDs that their
|
||||
* ID consists of need their own functions to build the allDocs parameters.
|
||||
* @param {string} docType The type of document which input params are being built for, e.g. user,
|
||||
* link, app, table and so on.
|
||||
|
@ -55,31 +55,31 @@ exports.generateTableID = () => {
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets the DB allDocs/query params for retrieving a record.
|
||||
* @param {string} tableId The table in which the records have been stored.
|
||||
* @param {string|null} recordId The ID of the record which is being specifically queried for. This can be
|
||||
* left null to get all the records in the table.
|
||||
* Gets the DB allDocs/query params for retrieving a row.
|
||||
* @param {string} tableId The table in which the rows have been stored.
|
||||
* @param {string|null} rowId The ID of the row which is being specifically queried for. This can be
|
||||
* left null to get all the rows in the table.
|
||||
* @param {object} otherProps Any other properties to add to the request.
|
||||
* @returns {object} Parameters which can then be used with an allDocs request.
|
||||
*/
|
||||
exports.getRecordParams = (tableId, recordId = null, otherProps = {}) => {
|
||||
exports.getRowParams = (tableId, rowId = null, otherProps = {}) => {
|
||||
if (tableId == null) {
|
||||
throw "Cannot build params for records without a table ID"
|
||||
throw "Cannot build params for rows without a table ID"
|
||||
}
|
||||
const endOfKey =
|
||||
recordId == null
|
||||
rowId == null
|
||||
? `${tableId}${SEPARATOR}`
|
||||
: `${tableId}${SEPARATOR}${recordId}`
|
||||
return getDocParams(DocumentTypes.RECORD, endOfKey, otherProps)
|
||||
: `${tableId}${SEPARATOR}${rowId}`
|
||||
return getDocParams(DocumentTypes.ROW, endOfKey, otherProps)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a new record ID for the specified table.
|
||||
* @param {string} tableId The table which the record is being created for.
|
||||
* @returns {string} The new ID which a record doc can be stored under.
|
||||
* Gets a new row ID for the specified table.
|
||||
* @param {string} tableId The table which the row is being created for.
|
||||
* @returns {string} The new ID which a row doc can be stored under.
|
||||
*/
|
||||
exports.generateRecordID = tableId => {
|
||||
return `${DocumentTypes.RECORD}${SEPARATOR}${tableId}${SEPARATOR}${newid()}`
|
||||
exports.generateRowID = tableId => {
|
||||
return `${DocumentTypes.ROW}${SEPARATOR}${tableId}${SEPARATOR}${newid()}`
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -118,12 +118,12 @@ exports.generateAutomationID = () => {
|
|||
* instead a view is built to make walking to tree easier.
|
||||
* @param {string} tableId1 The ID of the linker table.
|
||||
* @param {string} tableId2 The ID of the linked table.
|
||||
* @param {string} recordId1 The ID of the linker record.
|
||||
* @param {string} recordId2 The ID of the linked record.
|
||||
* @param {string} rowId1 The ID of the linker row.
|
||||
* @param {string} rowId2 The ID of the linked row.
|
||||
* @returns {string} The new link doc ID which the automation doc can be stored under.
|
||||
*/
|
||||
exports.generateLinkID = (tableId1, tableId2, recordId1, recordId2) => {
|
||||
return `${DocumentTypes.AUTOMATION}${SEPARATOR}${tableId1}${SEPARATOR}${tableId2}${SEPARATOR}${recordId1}${SEPARATOR}${recordId2}`
|
||||
exports.generateLinkID = (tableId1, tableId2, rowId1, rowId2) => {
|
||||
return `${DocumentTypes.AUTOMATION}${SEPARATOR}${tableId1}${SEPARATOR}${tableId2}${SEPARATOR}${rowId1}${SEPARATOR}${rowId2}`
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -11,18 +11,18 @@ const EventEmitter = require("events").EventEmitter
|
|||
* This is specifically quite important for mustache used in automations.
|
||||
*/
|
||||
class BudibaseEmitter extends EventEmitter {
|
||||
emitRecord(eventName, instanceId, record, table = null) {
|
||||
emitRow(eventName, instanceId, row, table = null) {
|
||||
let event = {
|
||||
record,
|
||||
row,
|
||||
instanceId,
|
||||
tableId: record.tableId,
|
||||
tableId: row.tableId,
|
||||
}
|
||||
if (table) {
|
||||
event.table = table
|
||||
}
|
||||
event.id = record._id
|
||||
if (record._rev) {
|
||||
event.revision = record._rev
|
||||
event.id = row._id
|
||||
if (row._rev) {
|
||||
event.revision = row._rev
|
||||
}
|
||||
this.emit(eventName, event)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ const METHOD_MAP = {
|
|||
}
|
||||
|
||||
const DOMAIN_MAP = {
|
||||
records: usageQuota.Properties.RECORD,
|
||||
rows: usageQuota.Properties.ROW,
|
||||
upload: usageQuota.Properties.UPLOAD,
|
||||
views: usageQuota.Properties.VIEW,
|
||||
users: usageQuota.Properties.USER,
|
||||
|
|
|
@ -2,7 +2,7 @@ const environment = require("../environment")
|
|||
const { apiKeyTable } = require("../db/dynamoClient")
|
||||
|
||||
const DEFAULT_USAGE = {
|
||||
records: 0,
|
||||
rows: 0,
|
||||
storage: 0,
|
||||
views: 0,
|
||||
automationRuns: 0,
|
||||
|
@ -10,7 +10,7 @@ const DEFAULT_USAGE = {
|
|||
}
|
||||
|
||||
const DEFAULT_PLAN = {
|
||||
records: 1000,
|
||||
rows: 1000,
|
||||
// 1 GB
|
||||
storage: 8589934592,
|
||||
views: 10,
|
||||
|
@ -42,7 +42,7 @@ function getNewQuotaReset() {
|
|||
}
|
||||
|
||||
exports.Properties = {
|
||||
RECORD: "records",
|
||||
ROW: "rows",
|
||||
UPLOAD: "storage",
|
||||
VIEW: "views",
|
||||
USER: "users",
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"_lib": "./dist/index.js",
|
||||
"_templates": {
|
||||
"saveRecordButton": {
|
||||
"description": "Save record button",
|
||||
"saveRowButton": {
|
||||
"description": "Save row button",
|
||||
"component": "button"
|
||||
}
|
||||
},
|
||||
|
@ -269,8 +269,8 @@
|
|||
"destinationUrl": "string"
|
||||
}
|
||||
},
|
||||
"recorddetail": {
|
||||
"description": "Loads a record, using an ID in the url",
|
||||
"rowdetail": {
|
||||
"description": "Loads a row, using an ID in the url",
|
||||
"context": "table",
|
||||
"children": true,
|
||||
"data": true,
|
||||
|
@ -757,4 +757,4 @@
|
|||
"className": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19392,8 +19392,8 @@ var app = (function (crypto$1) {
|
|||
const common = () => commonPlus([]);
|
||||
|
||||
const _events = {
|
||||
recordApi: {
|
||||
save: commonPlus(["onInvalid", "onRecordUpdated", "onRecordCreated"]),
|
||||
rowApi: {
|
||||
save: commonPlus(["onInvalid", "onRowUpdated", "onRowCreated"]),
|
||||
delete: common(),
|
||||
getContext: common(),
|
||||
getNew: common(),
|
||||
|
@ -19409,7 +19409,7 @@ var app = (function (crypto$1) {
|
|||
aggregates: common(),
|
||||
},
|
||||
collectionApi: {
|
||||
getAllowedRecordTypes: common(),
|
||||
getAllowedRowTypes: common(),
|
||||
initialise: common(),
|
||||
delete: common(),
|
||||
},
|
||||
|
@ -21240,7 +21240,7 @@ var app = (function (crypto$1) {
|
|||
),
|
||||
makerule(
|
||||
"indexType",
|
||||
"reference index may only exist on a record node",
|
||||
"reference index may only exist on a row node",
|
||||
index =>
|
||||
isTable(index.parent()) || index.indexType !== indexTypes.reference
|
||||
),
|
||||
|
@ -21298,7 +21298,7 @@ var app = (function (crypto$1) {
|
|||
getFlattenedHierarchy,
|
||||
fp_1(
|
||||
n =>
|
||||
isCollectionRecord(n) &&
|
||||
isCollectionRow(n) &&
|
||||
new RegExp(`${n.collectionPathRegx()}$`).test(collectionKey)
|
||||
),
|
||||
]);
|
||||
|
@ -21309,7 +21309,7 @@ var app = (function (crypto$1) {
|
|||
fp_1(
|
||||
n =>
|
||||
n.nodeKey() === nodeKey ||
|
||||
(isCollectionRecord(n) && n.collectionNodeKey() === nodeKey)
|
||||
(isCollectionRow(n) && n.collectionNodeKey() === nodeKey)
|
||||
),
|
||||
]);
|
||||
|
||||
|
@ -21321,20 +21321,20 @@ var app = (function (crypto$1) {
|
|||
const isNode = (appHierarchy, key) =>
|
||||
isSomething(getExactNodeForKey(appHierarchy)(key));
|
||||
|
||||
const isTable = node => isSomething(node) && node.type === "record";
|
||||
const isSingleRecord = node => isTable(node) && node.isSingle;
|
||||
const isCollectionRecord = node => isTable(node) && !node.isSingle;
|
||||
const isTable = node => isSomething(node) && node.type === "row";
|
||||
const isSingleRow = node => isTable(node) && node.isSingle;
|
||||
const isCollectionRow = node => isTable(node) && !node.isSingle;
|
||||
const isRoot = node => isSomething(node) && node.isRoot();
|
||||
|
||||
const getSafeFieldParser = (tryParse, defaultValueFunctions) => (
|
||||
field,
|
||||
record
|
||||
row
|
||||
) => {
|
||||
if (fp_25(field.name)(record)) {
|
||||
if (fp_25(field.name)(row)) {
|
||||
return getSafeValueParser(
|
||||
tryParse,
|
||||
defaultValueFunctions
|
||||
)(record[field.name])
|
||||
)(row[field.name])
|
||||
}
|
||||
return defaultValueFunctions[field.getUndefinedValue]()
|
||||
};
|
||||
|
@ -21372,10 +21372,10 @@ var app = (function (crypto$1) {
|
|||
|
||||
const validateTypeConstraints = validationRules => async (
|
||||
field,
|
||||
record,
|
||||
row,
|
||||
context
|
||||
) => {
|
||||
const fieldValue = record[field.name];
|
||||
const fieldValue = row[field.name];
|
||||
const validateRule = async r =>
|
||||
!(await r.isValid(fieldValue, field.typeOptions, context))
|
||||
? r.getMessage(fieldValue, field.typeOptions)
|
||||
|
@ -21880,10 +21880,10 @@ var app = (function (crypto$1) {
|
|||
);
|
||||
|
||||
const permissionTypes = {
|
||||
CREATE_RECORD: "create record",
|
||||
UPDATE_RECORD: "update record",
|
||||
READ_RECORD: "read record",
|
||||
DELETE_RECORD: "delete record",
|
||||
CREATE_ROW: "create row",
|
||||
UPDATE_ROW: "update row",
|
||||
READ_ROW: "read row",
|
||||
DELETE_ROW: "delete row",
|
||||
READ_INDEX: "read index",
|
||||
MANAGE_INDEX: "manage index",
|
||||
MANAGE_COLLECTION: "manage collection",
|
||||
|
@ -21953,13 +21953,13 @@ var app = (function (crypto$1) {
|
|||
get: () => ({ type }),
|
||||
});
|
||||
|
||||
const createRecord = nodePermission(permissionTypes.CREATE_RECORD);
|
||||
const createRow = nodePermission(permissionTypes.CREATE_ROW);
|
||||
|
||||
const updateRecord = nodePermission(permissionTypes.UPDATE_RECORD);
|
||||
const updateRow = nodePermission(permissionTypes.UPDATE_ROW);
|
||||
|
||||
const deleteRecord = nodePermission(permissionTypes.DELETE_RECORD);
|
||||
const deleteRow = nodePermission(permissionTypes.DELETE_ROW);
|
||||
|
||||
const readRecord = nodePermission(permissionTypes.READ_RECORD);
|
||||
const readRow = nodePermission(permissionTypes.READ_ROW);
|
||||
|
||||
const writeTemplates = staticPermission(permissionTypes.WRITE_TEMPLATES);
|
||||
|
||||
|
@ -21994,10 +21994,10 @@ var app = (function (crypto$1) {
|
|||
const alwaysAuthorized = () => true;
|
||||
|
||||
const permission = {
|
||||
createRecord,
|
||||
updateRecord,
|
||||
deleteRecord,
|
||||
readRecord,
|
||||
createRow,
|
||||
updateRow,
|
||||
deleteRow,
|
||||
readRow,
|
||||
writeTemplates,
|
||||
createUser,
|
||||
setPassword,
|
||||
|
@ -22013,41 +22013,41 @@ var app = (function (crypto$1) {
|
|||
setUserAccessLevels,
|
||||
};
|
||||
|
||||
const getNew = app => (collectionKey, recordTypeName) => {
|
||||
const recordNode = getRecordNode(app, collectionKey);
|
||||
const getNew = app => (collectionKey, rowTypeName) => {
|
||||
const rowNode = getRowNode(app, collectionKey);
|
||||
collectionKey = safeKey(collectionKey);
|
||||
return apiWrapperSync(
|
||||
app,
|
||||
events.recordApi.getNew,
|
||||
permission.createRecord.isAuthorized(recordNode.nodeKey()),
|
||||
{ collectionKey, recordTypeName },
|
||||
events.rowApi.getNew,
|
||||
permission.createRow.isAuthorized(rowNode.nodeKey()),
|
||||
{ collectionKey, rowTypeName },
|
||||
_getNew,
|
||||
recordNode,
|
||||
rowNode,
|
||||
collectionKey
|
||||
)
|
||||
};
|
||||
|
||||
const _getNew = (recordNode, collectionKey) =>
|
||||
constructRecord(recordNode, getNewFieldValue, collectionKey);
|
||||
const _getNew = (rowNode, collectionKey) =>
|
||||
constructRow(rowNode, getNewFieldValue, collectionKey);
|
||||
|
||||
const getRecordNode = (app, collectionKey) => {
|
||||
const getRowNode = (app, collectionKey) => {
|
||||
collectionKey = safeKey(collectionKey);
|
||||
return getNodeForCollectionPath(app.hierarchy)(collectionKey)
|
||||
};
|
||||
|
||||
const getNewChild = app => (recordKey, collectionName, recordTypeName) =>
|
||||
getNew(app)(joinKey(recordKey, collectionName), recordTypeName);
|
||||
const getNewChild = app => (rowKey, collectionName, rowTypeName) =>
|
||||
getNew(app)(joinKey(rowKey, collectionName), rowTypeName);
|
||||
|
||||
const constructRecord = (recordNode, getFieldValue, collectionKey) => {
|
||||
const record = $(recordNode.fields, [fp_35("name"), fp_26(getFieldValue)]);
|
||||
const constructRow = (rowNode, getFieldValue, collectionKey) => {
|
||||
const row = $(rowNode.fields, [fp_35("name"), fp_26(getFieldValue)]);
|
||||
|
||||
record.id = `${recordNode.nodeId}-${shortid_1()}`;
|
||||
record.key = isSingleRecord(recordNode)
|
||||
? joinKey(collectionKey, recordNode.name)
|
||||
: joinKey(collectionKey, record.id);
|
||||
record.isNew = true;
|
||||
record.type = recordNode.name;
|
||||
return record
|
||||
row.id = `${rowNode.nodeId}-${shortid_1()}`;
|
||||
row.key = isSingleRow(rowNode)
|
||||
? joinKey(collectionKey, rowNode.name)
|
||||
: joinKey(collectionKey, row.id);
|
||||
row.isNew = true;
|
||||
row.type = rowNode.name;
|
||||
return row
|
||||
};
|
||||
|
||||
const pathRegxMaker = node => () =>
|
||||
|
@ -22056,7 +22056,7 @@ var app = (function (crypto$1) {
|
|||
const nodeKeyMaker = node => () =>
|
||||
switchCase(
|
||||
[
|
||||
n => isTable(n) && !isSingleRecord(n),
|
||||
n => isTable(n) && !isSingleRow(n),
|
||||
n =>
|
||||
joinKey(
|
||||
node.parent().nodeKey(),
|
||||
|
@ -22076,7 +22076,7 @@ var app = (function (crypto$1) {
|
|||
node.parent = fp_21(parent);
|
||||
node.isRoot = () =>
|
||||
isNothing(parent) && node.name === "root" && node.type === "root";
|
||||
if (isCollectionRecord(node)) {
|
||||
if (isCollectionRow(node)) {
|
||||
node.collectionNodeKey = () =>
|
||||
joinKey(parent.nodeKey(), node.collectionName);
|
||||
node.collectionPathRegx = () =>
|
||||
|
@ -22116,7 +22116,7 @@ var app = (function (crypto$1) {
|
|||
const app = createCoreApp(backendDefinition, user);
|
||||
|
||||
return {
|
||||
recordApi: {
|
||||
rowApi: {
|
||||
getNew: getNew(app),
|
||||
getNewChild: getNewChild(app),
|
||||
},
|
||||
|
@ -22232,40 +22232,40 @@ var app = (function (crypto$1) {
|
|||
|
||||
const ERROR = "##error_message";
|
||||
|
||||
const loadRecord = api => async ({ recordKey, statePath }) => {
|
||||
if (!recordKey) {
|
||||
api.error("Load Record: record key not set");
|
||||
const loadRow = api => async ({ rowKey, statePath }) => {
|
||||
if (!rowKey) {
|
||||
api.error("Load Row: row key not set");
|
||||
return
|
||||
}
|
||||
|
||||
if (!statePath) {
|
||||
api.error("Load Record: state path not set");
|
||||
api.error("Load Row: state path not set");
|
||||
return
|
||||
}
|
||||
|
||||
const record = await api.get({
|
||||
url: `/api/record/${trimSlash(recordKey)}`,
|
||||
const row = await api.get({
|
||||
url: `/api/row/${trimSlash(rowKey)}`,
|
||||
});
|
||||
|
||||
if (api.isSuccess(record)) api.setState(statePath, record);
|
||||
if (api.isSuccess(row)) api.setState(statePath, row);
|
||||
};
|
||||
|
||||
const listRecords = api => async ({ indexKey, statePath }) => {
|
||||
const listRows = api => async ({ indexKey, statePath }) => {
|
||||
if (!indexKey) {
|
||||
api.error("Load Record: record key not set");
|
||||
api.error("Load Row: row key not set");
|
||||
return
|
||||
}
|
||||
|
||||
if (!statePath) {
|
||||
api.error("Load Record: state path not set");
|
||||
api.error("Load Row: state path not set");
|
||||
return
|
||||
}
|
||||
|
||||
const records = await api.get({
|
||||
url: `/api/listRecords/${trimSlash(indexKey)}`,
|
||||
const rows = await api.get({
|
||||
url: `/api/listRows/${trimSlash(indexKey)}`,
|
||||
});
|
||||
|
||||
if (api.isSuccess(records)) api.setState(statePath, records);
|
||||
if (api.isSuccess(rows)) api.setState(statePath, rows);
|
||||
};
|
||||
|
||||
const USER_STATE_PATH = "_bbuser";
|
||||
|
@ -22291,32 +22291,32 @@ var app = (function (crypto$1) {
|
|||
localStorage.setItem("budibase:user", JSON.stringify(user));
|
||||
};
|
||||
|
||||
const saveRecord = api => async ({ statePath }) => {
|
||||
const saveRow = api => async ({ statePath }) => {
|
||||
if (!statePath) {
|
||||
api.error("Load Record: state path not set");
|
||||
api.error("Load Row: state path not set");
|
||||
return
|
||||
}
|
||||
|
||||
const recordtoSave = api.getState(statePath);
|
||||
const rowtoSave = api.getState(statePath);
|
||||
|
||||
if (!recordtoSave) {
|
||||
api.error(`there is no record in state: ${statePath}`);
|
||||
if (!rowtoSave) {
|
||||
api.error(`there is no row in state: ${statePath}`);
|
||||
return
|
||||
}
|
||||
|
||||
if (!recordtoSave.key) {
|
||||
if (!rowtoSave.key) {
|
||||
api.error(
|
||||
`item in state does not appear to be a record - it has no key (${statePath})`
|
||||
`item in state does not appear to be a row - it has no key (${statePath})`
|
||||
);
|
||||
return
|
||||
}
|
||||
|
||||
const savedRecord = await api.post({
|
||||
url: `/api/record/${trimSlash(recordtoSave.key)}`,
|
||||
body: recordtoSave,
|
||||
const savedRow = await api.post({
|
||||
url: `/api/row/${trimSlash(rowtoSave.key)}`,
|
||||
body: rowtoSave,
|
||||
});
|
||||
|
||||
if (api.isSuccess(savedRecord)) api.setState(statePath, savedRecord);
|
||||
if (api.isSuccess(savedRow)) api.setState(statePath, savedRow);
|
||||
};
|
||||
|
||||
const createApi = ({ rootPath, setState, getState }) => {
|
||||
|
@ -22383,23 +22383,23 @@ var app = (function (crypto$1) {
|
|||
};
|
||||
|
||||
return {
|
||||
loadRecord: loadRecord(apiOpts),
|
||||
listRecords: listRecords(apiOpts),
|
||||
loadRow: loadRow(apiOpts),
|
||||
listRows: listRows(apiOpts),
|
||||
authenticate: authenticate(apiOpts),
|
||||
saveRecord: saveRecord(apiOpts),
|
||||
saveRow: saveRow(apiOpts),
|
||||
}
|
||||
};
|
||||
|
||||
const getNewChildRecordToState = (coreApi, setState) => ({
|
||||
recordKey,
|
||||
const getNewChildRowToState = (coreApi, setState) => ({
|
||||
rowKey,
|
||||
collectionName,
|
||||
childRecordType,
|
||||
childRowType,
|
||||
statePath,
|
||||
}) => {
|
||||
const error = errorHandler(setState);
|
||||
try {
|
||||
if (!recordKey) {
|
||||
error("getNewChild > recordKey not set");
|
||||
if (!rowKey) {
|
||||
error("getNewChild > rowKey not set");
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -22408,8 +22408,8 @@ var app = (function (crypto$1) {
|
|||
return
|
||||
}
|
||||
|
||||
if (!childRecordType) {
|
||||
error("getNewChild > childRecordType not set");
|
||||
if (!childRowType) {
|
||||
error("getNewChild > childRowType not set");
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -22418,10 +22418,10 @@ var app = (function (crypto$1) {
|
|||
return
|
||||
}
|
||||
|
||||
const rec = coreApi.recordApi.getNewChild(
|
||||
recordKey,
|
||||
const rec = coreApi.rowApi.getNewChild(
|
||||
rowKey,
|
||||
collectionName,
|
||||
childRecordType
|
||||
childRowType
|
||||
);
|
||||
setState(statePath, rec);
|
||||
} catch (e) {
|
||||
|
@ -22429,9 +22429,9 @@ var app = (function (crypto$1) {
|
|||
}
|
||||
};
|
||||
|
||||
const getNewRecordToState = (coreApi, setState) => ({
|
||||
const getNewRowToState = (coreApi, setState) => ({
|
||||
collectionKey,
|
||||
childRecordType,
|
||||
childRowType,
|
||||
statePath,
|
||||
}) => {
|
||||
const error = errorHandler(setState);
|
||||
|
@ -22441,8 +22441,8 @@ var app = (function (crypto$1) {
|
|||
return
|
||||
}
|
||||
|
||||
if (!childRecordType) {
|
||||
error("getNewChild > childRecordType not set");
|
||||
if (!childRowType) {
|
||||
error("getNewChild > childRowType not set");
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -22451,7 +22451,7 @@ var app = (function (crypto$1) {
|
|||
return
|
||||
}
|
||||
|
||||
const rec = coreApi.recordApi.getNew(collectionKey, childRecordType);
|
||||
const rec = coreApi.rowApi.getNew(collectionKey, childRowType);
|
||||
setState(statePath, rec);
|
||||
} catch (e) {
|
||||
error(e.message);
|
||||
|
@ -22485,18 +22485,18 @@ var app = (function (crypto$1) {
|
|||
|
||||
return {
|
||||
"Set State": handler(["path", "value"], setStateHandler),
|
||||
"Load Record": handler(["recordKey", "statePath"], api.loadRecord),
|
||||
"List Records": handler(["indexKey", "statePath"], api.listRecords),
|
||||
"Save Record": handler(["statePath"], api.saveRecord),
|
||||
"Load Row": handler(["rowKey", "statePath"], api.loadRow),
|
||||
"List Rows": handler(["indexKey", "statePath"], api.listRows),
|
||||
"Save Row": handler(["statePath"], api.saveRow),
|
||||
|
||||
"Get New Child Record": handler(
|
||||
["recordKey", "collectionName", "childRecordType", "statePath"],
|
||||
getNewChildRecordToState(coreApi, setStateWithStore)
|
||||
"Get New Child Row": handler(
|
||||
["rowKey", "collectionName", "childRowType", "statePath"],
|
||||
getNewChildRowToState(coreApi, setStateWithStore)
|
||||
),
|
||||
|
||||
"Get New Record": handler(
|
||||
["collectionKey", "childRecordType", "statePath"],
|
||||
getNewRecordToState(coreApi, setStateWithStore)
|
||||
"Get New Row": handler(
|
||||
["collectionKey", "childRowType", "statePath"],
|
||||
getNewRowToState(coreApi, setStateWithStore)
|
||||
),
|
||||
|
||||
Authenticate: handler(["username", "password"], api.authenticate),
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -5,7 +5,7 @@ window["##BUDIBASE_APPDEFINITION##"] = {
|
|||
children: [
|
||||
{
|
||||
name: "customer",
|
||||
type: "record",
|
||||
type: "row",
|
||||
fields: [
|
||||
{
|
||||
name: "name",
|
||||
|
@ -23,7 +23,7 @@ window["##BUDIBASE_APPDEFINITION##"] = {
|
|||
children: [
|
||||
{
|
||||
name: "invoiceyooo",
|
||||
type: "record",
|
||||
type: "row",
|
||||
fields: [
|
||||
{
|
||||
name: "amount",
|
||||
|
@ -53,11 +53,11 @@ window["##BUDIBASE_APPDEFINITION##"] = {
|
|||
{
|
||||
name: "customer_invoices",
|
||||
type: "index",
|
||||
map: "return {...record};",
|
||||
map: "return {...row};",
|
||||
filter: "",
|
||||
indexType: "ancestor",
|
||||
getShardName: "",
|
||||
getSortKey: "record.id",
|
||||
getSortKey: "row.id",
|
||||
aggregateGroups: [],
|
||||
allowedTableNodeIds: [2],
|
||||
nodeId: 5,
|
||||
|
@ -73,11 +73,11 @@ window["##BUDIBASE_APPDEFINITION##"] = {
|
|||
{
|
||||
name: "Yeo index",
|
||||
type: "index",
|
||||
map: "return {...record};",
|
||||
map: "return {...row};",
|
||||
filter: "",
|
||||
indexType: "ancestor",
|
||||
getShardName: "",
|
||||
getSortKey: "record.id",
|
||||
getSortKey: "row.id",
|
||||
aggregateGroups: [],
|
||||
allowedTableNodeIds: [1],
|
||||
nodeId: 4,
|
||||
|
@ -85,11 +85,11 @@ window["##BUDIBASE_APPDEFINITION##"] = {
|
|||
{
|
||||
name: "everyones_invoices",
|
||||
type: "index",
|
||||
map: "return {...record};",
|
||||
map: "return {...row};",
|
||||
filter: "",
|
||||
indexType: "ancestor",
|
||||
getShardName: "",
|
||||
getSortKey: "record.id",
|
||||
getSortKey: "row.id",
|
||||
aggregateGroups: [],
|
||||
allowedTableNodeIds: [2],
|
||||
nodeId: 6,
|
||||
|
|
|
@ -54,8 +54,8 @@
|
|||
})
|
||||
|
||||
async function fetchData() {
|
||||
const FETCH_RECORDS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
||||
const FETCH_ROWS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_ROWS_URL)
|
||||
if (response.status === 200) {
|
||||
const json = await response.json()
|
||||
store.update(state => {
|
||||
|
@ -63,7 +63,7 @@
|
|||
return state
|
||||
})
|
||||
} else {
|
||||
throw new Error("Failed to fetch records.", response)
|
||||
throw new Error("Failed to fetch rows.", response)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -46,8 +46,8 @@
|
|||
})
|
||||
|
||||
async function fetchData() {
|
||||
const FETCH_RECORDS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
||||
const FETCH_ROWS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_ROWS_URL)
|
||||
if (response.status === 200) {
|
||||
const json = await response.json()
|
||||
store.update(state => {
|
||||
|
@ -55,7 +55,7 @@
|
|||
return state
|
||||
})
|
||||
} else {
|
||||
throw new Error("Failed to fetch records.", response)
|
||||
throw new Error("Failed to fetch rows.", response)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -99,16 +99,16 @@
|
|||
let ignoreList = ["_id", "_rev", "id"]
|
||||
if (dataKey && data.every(d => d[dataKey])) {
|
||||
return data.map(d => {
|
||||
let clonedRecord = { ...d }
|
||||
if (clonedRecord[formatKey]) {
|
||||
delete clonedRecord[formatKey]
|
||||
let clonedRow = { ...d }
|
||||
if (clonedRow[formatKey]) {
|
||||
delete clonedRow[formatKey]
|
||||
}
|
||||
let value = clonedRecord[dataKey]
|
||||
let value = clonedRow[dataKey]
|
||||
if (!ignoreList.includes(dataKey)) {
|
||||
delete clonedRecord[dataKey]
|
||||
delete clonedRow[dataKey]
|
||||
}
|
||||
clonedRecord[formatKey] = value
|
||||
return clonedRecord
|
||||
clonedRow[formatKey] = value
|
||||
return clonedRow
|
||||
})
|
||||
} else {
|
||||
return data
|
||||
|
|
|
@ -41,8 +41,8 @@
|
|||
})
|
||||
|
||||
async function fetchData() {
|
||||
const FETCH_RECORDS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
||||
const FETCH_ROWS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_ROWS_URL)
|
||||
if (response.status === 200) {
|
||||
const json = await response.json()
|
||||
store.update(state => {
|
||||
|
@ -50,7 +50,7 @@
|
|||
return state
|
||||
})
|
||||
} else {
|
||||
throw new Error("Failed to fetch records.", response)
|
||||
throw new Error("Failed to fetch rows.", response)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -70,8 +70,8 @@
|
|||
})
|
||||
|
||||
async function fetchData() {
|
||||
const FETCH_RECORDS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
||||
const FETCH_ROWS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_ROWS_URL)
|
||||
if (response.status === 200) {
|
||||
const json = await response.json()
|
||||
store.update(state => {
|
||||
|
@ -79,7 +79,7 @@
|
|||
return state
|
||||
})
|
||||
} else {
|
||||
throw new Error("Failed to fetch records.", response)
|
||||
throw new Error("Failed to fetch rows.", response)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,8 +45,8 @@
|
|||
})
|
||||
|
||||
async function fetchData() {
|
||||
const FETCH_RECORDS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
||||
const FETCH_ROWS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_ROWS_URL)
|
||||
if (response.status === 200) {
|
||||
const json = await response.json()
|
||||
store.update(state => {
|
||||
|
@ -54,7 +54,7 @@
|
|||
return state
|
||||
})
|
||||
} else {
|
||||
throw new Error("Failed to fetch records.", response)
|
||||
throw new Error("Failed to fetch rows.", response)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -70,8 +70,8 @@
|
|||
})
|
||||
|
||||
async function fetchData() {
|
||||
const FETCH_RECORDS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
||||
const FETCH_ROWS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_ROWS_URL)
|
||||
if (response.status === 200) {
|
||||
const json = await response.json()
|
||||
store.update(state => {
|
||||
|
@ -79,7 +79,7 @@
|
|||
return state
|
||||
})
|
||||
} else {
|
||||
throw new Error("Failed to fetch records.", response)
|
||||
throw new Error("Failed to fetch rows.", response)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -65,8 +65,8 @@
|
|||
})
|
||||
|
||||
async function fetchData() {
|
||||
const FETCH_RECORDS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
||||
const FETCH_ROWS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_ROWS_URL)
|
||||
if (response.status === 200) {
|
||||
const json = await response.json()
|
||||
store.update(state => {
|
||||
|
@ -74,7 +74,7 @@
|
|||
return state
|
||||
})
|
||||
} else {
|
||||
throw new Error("Failed to fetch records.", response)
|
||||
throw new Error("Failed to fetch rows.", response)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -60,8 +60,8 @@
|
|||
})
|
||||
|
||||
async function fetchData() {
|
||||
const FETCH_RECORDS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
||||
const FETCH_ROWS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_ROWS_URL)
|
||||
if (response.status === 200) {
|
||||
const json = await response.json()
|
||||
store.update(state => {
|
||||
|
@ -69,7 +69,7 @@
|
|||
return state
|
||||
})
|
||||
} else {
|
||||
throw new Error("Failed to fetch records.", response)
|
||||
throw new Error("Failed to fetch rows.", response)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ audited: new Date("2020-01-01T16:00:00-08:00"),
|
|||
city: "Belfast",
|
||||
name: 1,
|
||||
tableId: "2334751ac0764c1a931bff5b6b6767eb",
|
||||
type: "record",
|
||||
type: "row",
|
||||
_id: "ceb87054790f480e80512368545755bb",
|
||||
_rev: "2-56e401ebaf59e6310b85fb0c6c2fece5",
|
||||
},
|
||||
|
@ -42,7 +42,7 @@ audited: new Date("2020-01-03T16:00:00-08:00"),
|
|||
city: "Belfast",
|
||||
name: 1,
|
||||
tableId: "2334751ac0764c1a931bff5b6b6767eb",
|
||||
type: "record",
|
||||
type: "row",
|
||||
_id: "0a36103b55124f348a23d10b2f3ed0e3",
|
||||
_rev: "2-50d62530b2edfc63d5fd0b3719dbb286",
|
||||
},
|
||||
|
@ -52,7 +52,7 @@ audited: new Date("2020-01-04T16:00:00-08:00"),
|
|||
city: "Belfast",
|
||||
name: 1,
|
||||
tableId: "2334751ac0764c1a931bff5b6b6767eb",
|
||||
type: "record",
|
||||
type: "row",
|
||||
_id: "68ade2bb94754caa8fc62c7084e3cef7",
|
||||
_rev: "2-a03fe02f3595920adfbcd9c70564fe9d",
|
||||
},
|
||||
|
@ -62,7 +62,7 @@ audited: new Date("2020-01-01T16:00:00-08:00"),
|
|||
city: "Dublin",
|
||||
name: 2,
|
||||
tableId: "2334751ac0764c1a931bff5b6b6767eb",
|
||||
type: "record",
|
||||
type: "row",
|
||||
_id: "2ab6dabf833f4d99b3438fa4353ba429",
|
||||
_rev: "2-45b190489e76842981902cc9f04369ec",
|
||||
},
|
||||
|
@ -72,7 +72,7 @@ audited: new Date("2020-01-02T16:00:00-08:00"),
|
|||
city: "Dublin",
|
||||
name: 2,
|
||||
tableId: "2334751ac0764c1a931bff5b6b6767eb",
|
||||
type: "record",
|
||||
type: "row",
|
||||
_id: "1b2ca36db1724427a98ba95547f946e0",
|
||||
_rev: "2-c43def17ada959948b9af5484ad5b6b7",
|
||||
},
|
||||
|
@ -82,7 +82,7 @@ audited: new Date("2020-01-03T16:00:00-08:00"),
|
|||
city: "Dublin",
|
||||
name: 2,
|
||||
tableId: "2334751ac0764c1a931bff5b6b6767eb",
|
||||
type: "record",
|
||||
type: "row",
|
||||
_id: "d9235d884a224ca68ac30cefdbb8ae53",
|
||||
_rev: "2-695e426a261a25474cbf6b1f069dccb4",
|
||||
},
|
||||
|
@ -92,7 +92,7 @@ audited: new Date("2020-01-04T16:00:00-08:00"),
|
|||
city: "Dublin",
|
||||
name: 2,
|
||||
tableId: "2334751ac0764c1a931bff5b6b6767eb",
|
||||
type: "record",
|
||||
type: "row",
|
||||
_id: "9f8bc39a9cfb4f779da8c998d7622927",
|
||||
_rev: "2-8ae1aff82e1ffc6ffa75f6b9d074e003",
|
||||
},
|
||||
|
@ -102,7 +102,7 @@ audited: new Date("2020-01-02T16:00:00-08:00"),
|
|||
city: "London",
|
||||
name: 3,
|
||||
tableId: "2334751ac0764c1a931bff5b6b6767eb",
|
||||
type: "record",
|
||||
type: "row",
|
||||
_id: "75274e906073493bbf75cda8656e8db0",
|
||||
_rev: "2-6cfc6bb2fccb83c92b50aa5507f2a092"
|
||||
},
|
||||
|
@ -112,7 +112,7 @@ audited: new Date("2020-01-06T16:00:00-08:00"),
|
|||
city: "London",
|
||||
name: 3,
|
||||
tableId: "2334751ac0764c1a931bff5b6b6767eb",
|
||||
type: "record",
|
||||
type: "row",
|
||||
_id: "da3d4b151bc641f4ace487a2314d2550",
|
||||
_rev: "2-ac18490eaa016be0e71bd4c4ea332981",
|
||||
}
|
||||
|
|
|
@ -24,16 +24,16 @@ export function reformatDataKey(data = [], dataKey = null, formatKey = null) {
|
|||
let ignoreList = ["_id", "_rev", "id"]
|
||||
if (dataKey && data.every(d => d[dataKey])) {
|
||||
return data.map(d => {
|
||||
let clonedRecord = { ...d }
|
||||
if (clonedRecord[formatKey]) {
|
||||
delete clonedRecord[formatKey]
|
||||
let clonedRow = { ...d }
|
||||
if (clonedRow[formatKey]) {
|
||||
delete clonedRow[formatKey]
|
||||
}
|
||||
let value = clonedRecord[dataKey]
|
||||
let value = clonedRow[dataKey]
|
||||
if (!ignoreList.includes(dataKey)) {
|
||||
delete clonedRecord[dataKey]
|
||||
delete clonedRow[dataKey]
|
||||
}
|
||||
clonedRecord[formatKey] = value
|
||||
return clonedRecord
|
||||
clonedRow[formatKey] = value
|
||||
return clonedRow
|
||||
})
|
||||
} else {
|
||||
return data
|
||||
|
|
|
@ -26,8 +26,8 @@
|
|||
$: console.log("CHART CONFIGS", chartConfigs)
|
||||
|
||||
async function fetchData() {
|
||||
const FETCH_RECORDS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
||||
const FETCH_ROWS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_ROWS_URL)
|
||||
if (response.status === 200) {
|
||||
const json = await response.json()
|
||||
store.update(state => {
|
||||
|
@ -35,7 +35,7 @@
|
|||
return state
|
||||
})
|
||||
} else {
|
||||
throw new Error("Failed to fetch records.", response)
|
||||
throw new Error("Failed to fetch rows.", response)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
async function fetchData() {
|
||||
if (!table || !table.length) return
|
||||
|
||||
const FETCH_RECORDS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
||||
const FETCH_ROWS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_ROWS_URL)
|
||||
if (response.status === 200) {
|
||||
const json = await response.json()
|
||||
|
||||
|
@ -21,7 +21,7 @@
|
|||
return state
|
||||
})
|
||||
} else {
|
||||
throw new Error("Failed to fetch records.", response)
|
||||
throw new Error("Failed to fetch rows.", response)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
|
||||
const shouldDisplayField = name => {
|
||||
if (name.startsWith("_")) return false
|
||||
// always 'record'
|
||||
// always 'row'
|
||||
if (name === "type") return false
|
||||
// tables are always tied to a single tableId, this is irrelevant
|
||||
if (name === "tableId") return false
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
Toggle,
|
||||
} from "@budibase/bbui"
|
||||
import Dropzone from "./attachments/Dropzone.svelte"
|
||||
import LinkedRecordSelector from "./LinkedRecordSelector.svelte"
|
||||
import LinkedRowSelector from "./LinkedRowSelector.svelte"
|
||||
import debounce from "lodash.debounce"
|
||||
import ErrorsBox from "./ErrorsBox.svelte"
|
||||
import { capitalise } from "./helpers"
|
||||
|
@ -34,12 +34,12 @@
|
|||
link: [],
|
||||
}
|
||||
|
||||
let record
|
||||
let row
|
||||
let store = _bb.store
|
||||
let schema = {}
|
||||
let tableDef = {}
|
||||
let saved = false
|
||||
let recordId
|
||||
let rowId
|
||||
let isNew = true
|
||||
let errors = {}
|
||||
|
||||
|
@ -53,7 +53,7 @@
|
|||
const response = await _bb.api.get(FETCH_TABLE_URL)
|
||||
tableDef = await response.json()
|
||||
schema = tableDef.schema
|
||||
record = {
|
||||
row = {
|
||||
tableId: table,
|
||||
}
|
||||
}
|
||||
|
@ -61,13 +61,13 @@
|
|||
const save = debounce(async () => {
|
||||
for (let field of fields) {
|
||||
// Assign defaults to empty fields to prevent validation issues
|
||||
if (!(field in record)) {
|
||||
record[field] = DEFAULTS_FOR_TYPE[schema[field].type]
|
||||
if (!(field in row)) {
|
||||
row[field] = DEFAULTS_FOR_TYPE[schema[field].type]
|
||||
}
|
||||
}
|
||||
|
||||
const SAVE_RECORD_URL = `/api/${table}/records`
|
||||
const response = await _bb.api.post(SAVE_RECORD_URL, record)
|
||||
const SAVE_ROW_URL = `/api/${table}/rows`
|
||||
const response = await _bb.api.post(SAVE_ROW_URL, row)
|
||||
|
||||
const json = await response.json()
|
||||
|
||||
|
@ -79,9 +79,9 @@
|
|||
|
||||
errors = {}
|
||||
|
||||
// wipe form, if new record, otherwise update
|
||||
// wipe form, if new row, otherwise update
|
||||
// table to get new _rev
|
||||
record = isNew ? { tableId: table } : json
|
||||
row = isNew ? { tableId: table } : json
|
||||
|
||||
// set saved, and unset after 1 second
|
||||
// i.e. make the success notifier appear, then disappear again after time
|
||||
|
@ -100,18 +100,18 @@
|
|||
|
||||
onMount(async () => {
|
||||
const routeParams = _bb.routeParams()
|
||||
recordId =
|
||||
rowId =
|
||||
Object.keys(routeParams).length > 0 && (routeParams.id || routeParams[0])
|
||||
isNew = !recordId || recordId === "new"
|
||||
isNew = !rowId || rowId === "new"
|
||||
|
||||
if (isNew) {
|
||||
record = { tableId: table }
|
||||
row = { tableId: table }
|
||||
return
|
||||
}
|
||||
|
||||
const GET_RECORD_URL = `/api/${table}/records/${recordId}`
|
||||
const response = await _bb.api.get(GET_RECORD_URL)
|
||||
record = await response.json()
|
||||
const GET_ROW_URL = `/api/${table}/rows/${rowId}`
|
||||
const response = await _bb.api.get(GET_ROW_URL)
|
||||
row = await response.json()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -129,29 +129,29 @@
|
|||
</Label>
|
||||
{/if}
|
||||
{#if schema[field].type === 'options'}
|
||||
<Select secondary bind:value={record[field]}>
|
||||
<Select secondary bind:value={row[field]}>
|
||||
<option value="">Choose an option</option>
|
||||
{#each schema[field].constraints.inclusion as opt}
|
||||
<option>{opt}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
{:else if schema[field].type === 'datetime'}
|
||||
<DatePicker bind:value={record[field]} />
|
||||
<DatePicker bind:value={row[field]} />
|
||||
{:else if schema[field].type === 'boolean'}
|
||||
<Toggle
|
||||
text={wide ? null : capitalise(schema[field].name)}
|
||||
bind:checked={record[field]} />
|
||||
bind:checked={row[field]} />
|
||||
{:else if schema[field].type === 'number'}
|
||||
<Input type="number" bind:value={record[field]} />
|
||||
<Input type="number" bind:value={row[field]} />
|
||||
{:else if schema[field].type === 'string'}
|
||||
<Input bind:value={record[field]} />
|
||||
<Input bind:value={row[field]} />
|
||||
{:else if schema[field].type === 'attachment'}
|
||||
<Dropzone bind:files={record[field]} />
|
||||
<Dropzone bind:files={row[field]} />
|
||||
{:else if schema[field].type === 'link'}
|
||||
<LinkedRecordSelector
|
||||
<LinkedRowSelector
|
||||
secondary
|
||||
showLabel={false}
|
||||
bind:linkedRecords={record[field]}
|
||||
bind:linkedRows={row[field]}
|
||||
schema={schema[field]} />
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import { capitalise } from "./helpers"
|
||||
|
||||
export let schema = {}
|
||||
export let linkedRecords = []
|
||||
export let linkedRows = []
|
||||
export let showLabel = true
|
||||
export let secondary
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
|||
|
||||
$: label = capitalise(schema.name)
|
||||
$: linkedTableId = schema.tableId
|
||||
$: recordsPromise = fetchRecords(linkedTableId)
|
||||
$: rowsPromise = fetchRows(linkedTableId)
|
||||
$: fetchTable(linkedTableId)
|
||||
|
||||
async function fetchTable() {
|
||||
|
@ -25,17 +25,17 @@
|
|||
linkedTable = await response.json()
|
||||
}
|
||||
|
||||
async function fetchRecords(linkedTableId) {
|
||||
async function fetchRows(linkedTableId) {
|
||||
if (linkedTableId == null) {
|
||||
return
|
||||
}
|
||||
const FETCH_RECORDS_URL = `/api/${linkedTableId}/records`
|
||||
const response = await api.get(FETCH_RECORDS_URL)
|
||||
const FETCH_ROWS_URL = `/api/${linkedTableId}/rows`
|
||||
const response = await api.get(FETCH_ROWS_URL)
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
function getPrettyName(record) {
|
||||
return record[(linkedTable && linkedTable.primaryDisplay) || "_id"]
|
||||
function getPrettyName(row) {
|
||||
return row[(linkedTable && linkedTable.primaryDisplay) || "_id"]
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -50,14 +50,14 @@
|
|||
table.
|
||||
</Label>
|
||||
{:else}
|
||||
{#await recordsPromise then records}
|
||||
{#await rowsPromise then rows}
|
||||
<Multiselect
|
||||
{secondary}
|
||||
bind:value={linkedRecords}
|
||||
bind:value={linkedRows}
|
||||
label={showLabel ? label : null}
|
||||
placeholder="Choose some options">
|
||||
{#each records as record}
|
||||
<option value={record._id}>{getPrettyName(record)}</option>
|
||||
{#each rows as row}
|
||||
<option value={row._id}>{getPrettyName(row)}</option>
|
||||
{/each}
|
||||
</Multiselect>
|
||||
{/await}
|
|
@ -14,46 +14,46 @@
|
|||
return await response.json()
|
||||
}
|
||||
|
||||
async function fetchFirstRecord() {
|
||||
const FETCH_RECORDS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
||||
async function fetchFirstRow() {
|
||||
const FETCH_ROWS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_ROWS_URL)
|
||||
if (response.status === 200) {
|
||||
const allRecords = await response.json()
|
||||
if (allRecords.length > 0) return allRecords[0]
|
||||
const allRows = await response.json()
|
||||
if (allRows.length > 0) return allRows[0]
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchData() {
|
||||
const pathParts = window.location.pathname.split("/")
|
||||
|
||||
let record
|
||||
let row
|
||||
// if srcdoc, then we assume this is the builder preview
|
||||
if (pathParts.length === 0 || pathParts[0] === "srcdoc") {
|
||||
record = await fetchFirstRecord()
|
||||
row = await fetchFirstRow()
|
||||
} else {
|
||||
const id = pathParts[pathParts.length - 1]
|
||||
const GET_RECORD_URL = `/api/${table}/records/${id}`
|
||||
const response = await _bb.api.get(GET_RECORD_URL)
|
||||
const GET_ROW_URL = `/api/${table}/rows/${id}`
|
||||
const response = await _bb.api.get(GET_ROW_URL)
|
||||
if (response.status === 200) {
|
||||
record = await response.json()
|
||||
row = await response.json()
|
||||
}
|
||||
}
|
||||
|
||||
if (record) {
|
||||
// Fetch table schema so we can check for linked records
|
||||
const table = await fetchTable(record.tableId)
|
||||
if (row) {
|
||||
// Fetch table schema so we can check for linked rows
|
||||
const table = await fetchTable(row.tableId)
|
||||
for (let key of Object.keys(table.schema)) {
|
||||
if (table.schema[key].type === "link") {
|
||||
record[key] = Array.isArray(record[key]) ? record[key].length : 0
|
||||
row[key] = Array.isArray(row[key]) ? row[key].length : 0
|
||||
}
|
||||
}
|
||||
|
||||
_bb.attachChildren(target, {
|
||||
hydrate: false,
|
||||
context: record,
|
||||
context: row,
|
||||
})
|
||||
} else {
|
||||
throw new Error("Failed to fetch record.", response)
|
||||
throw new Error("Failed to fetch row.", response)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,22 @@
|
|||
export default ({ records }) =>
|
||||
records.map(r => ({
|
||||
export default ({ rows }) =>
|
||||
rows.map(r => ({
|
||||
name: `Save ${r.name} Button`,
|
||||
props: buttonProps(r),
|
||||
}))
|
||||
|
||||
const buttonProps = record => ({
|
||||
const buttonProps = row => ({
|
||||
_component: "@budibase/standard-components/button",
|
||||
_children: [
|
||||
{
|
||||
_component: "@budibase/standard-components/text",
|
||||
text: `Save ${record.name}`,
|
||||
text: `Save ${row.name}`,
|
||||
},
|
||||
],
|
||||
onClick: [
|
||||
{
|
||||
"##eventHandlerType": "Save Record",
|
||||
"##eventHandlerType": "Save Row",
|
||||
parameters: {
|
||||
statePath: `${record.name}`,
|
||||
statePath: `${row.name}`,
|
||||
},
|
||||
},
|
||||
],
|
|
@ -4,22 +4,22 @@ export default async function fetchData(datasource) {
|
|||
const { isTable, name } = datasource
|
||||
|
||||
if (name) {
|
||||
const records = isTable ? await fetchTableData() : await fetchViewData()
|
||||
const rows = isTable ? await fetchTableData() : await fetchViewData()
|
||||
|
||||
// Fetch table schema so we can check for linked records
|
||||
if (records && records.length) {
|
||||
const table = await fetchTable(records[0].tableId)
|
||||
// Fetch table schema so we can check for linked rows
|
||||
if (rows && rows.length) {
|
||||
const table = await fetchTable(rows[0].tableId)
|
||||
const keys = Object.keys(table.schema)
|
||||
records.forEach(record => {
|
||||
rows.forEach(row => {
|
||||
for (let key of keys) {
|
||||
if (table.schema[key].type === "link") {
|
||||
record[key] = Array.isArray(record[key]) ? record[key].length : 0
|
||||
row[key] = Array.isArray(row[key]) ? row[key].length : 0
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return records
|
||||
return rows
|
||||
}
|
||||
|
||||
async function fetchTable(id) {
|
||||
|
|
|
@ -11,7 +11,7 @@ export { default as radiobutton } from "./Radiobutton.svelte"
|
|||
export { default as option } from "./Option.svelte"
|
||||
export { default as button } from "./Button.svelte"
|
||||
export { default as login } from "./Login.svelte"
|
||||
export { default as saveRecordButton } from "./Templates/saveRecordButton"
|
||||
export { default as saveRowButton } from "./Templates/saveRowButton"
|
||||
export { default as link } from "./Link.svelte"
|
||||
export { default as image } from "./Image.svelte"
|
||||
export { default as Navigation } from "./Navigation.svelte"
|
||||
|
@ -26,7 +26,7 @@ export { default as embed } from "./Embed.svelte"
|
|||
export { default as stackedlist } from "./StackedList.svelte"
|
||||
export { default as card } from "./Card.svelte"
|
||||
export { default as cardhorizontal } from "./CardHorizontal.svelte"
|
||||
export { default as recorddetail } from "./RecordDetail.svelte"
|
||||
export { default as rowdetail } from "./RowDetail.svelte"
|
||||
export { default as datepicker } from "./DatePicker.svelte"
|
||||
export * from "./Chart"
|
||||
export { default as icon } from "./Icon.svelte"
|
||||
|
|
Loading…
Reference in New Issue