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