Renaming Model -> Table across the entire system, this is an unstable update and has not been tested fully yet.
This commit is contained in:
parent
2811c78d82
commit
fdaa69ee7f
|
@ -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, models and records are stored using PouchDB when developing locally, and in [CouchDB](https://pouchdb.com/) when running in production.
|
The backend schema, tables and records 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
|
||||||
|
|
||||||
|
|
|
@ -109,7 +109,7 @@ context("Create a View", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("renames a view", () => {
|
it("renames a view", () => {
|
||||||
cy.contains("[data-cy=model-nav-item]", "Test View")
|
cy.contains("[data-cy=table-nav-item]", "Test View")
|
||||||
.find(".ri-more-line")
|
.find(".ri-more-line")
|
||||||
.click()
|
.click()
|
||||||
cy.contains("Edit").click()
|
cy.contains("Edit").click()
|
||||||
|
@ -121,8 +121,8 @@ context("Create a View", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("deletes a view", () => {
|
it("deletes a view", () => {
|
||||||
cy.contains("[data-cy=model-nav-item]", "Test View Updated").click()
|
cy.contains("[data-cy=table-nav-item]", "Test View Updated").click()
|
||||||
cy.contains("[data-cy=model-nav-item]", "Test View Updated")
|
cy.contains("[data-cy=table-nav-item]", "Test View Updated")
|
||||||
.find(".ri-more-line")
|
.find(".ri-more-line")
|
||||||
.click()
|
.click()
|
||||||
cy.contains("Delete").click()
|
cy.contains("Delete").click()
|
||||||
|
|
|
@ -64,7 +64,7 @@ Cypress.Commands.add("createTestTableWithData", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("createTable", tableName => {
|
Cypress.Commands.add("createTable", tableName => {
|
||||||
// Enter model name
|
// Enter table name
|
||||||
cy.contains("Create New Table").click()
|
cy.contains("Create New Table").click()
|
||||||
cy.get(".modal").within(() => {
|
cy.get(".modal").within(() => {
|
||||||
cy.get("input")
|
cy.get("input")
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { cloneDeep, difference } from "lodash/fp"
|
||||||
* @property {string} componentInstanceId - an _id of a component that has been added to a screen, which you want to fetch bindable props for
|
* @property {string} componentInstanceId - an _id of a component that has been added to a screen, which you want to fetch bindable props for
|
||||||
* @propperty {Object} screen - current screen - where componentInstanceId lives
|
* @propperty {Object} screen - current screen - where componentInstanceId lives
|
||||||
* @property {Object} components - dictionary of component definitions
|
* @property {Object} components - dictionary of component definitions
|
||||||
* @property {Array} models - array of all models
|
* @property {Array} tables - array of all tables
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,13 +23,13 @@ import { cloneDeep, difference } from "lodash/fp"
|
||||||
* @param {fetchBindablePropertiesParameter} param
|
* @param {fetchBindablePropertiesParameter} param
|
||||||
* @returns {Array.<BindableProperty>}
|
* @returns {Array.<BindableProperty>}
|
||||||
*/
|
*/
|
||||||
export default function({ componentInstanceId, screen, components, models }) {
|
export default function({ componentInstanceId, screen, components, tables }) {
|
||||||
const walkResult = walk({
|
const walkResult = walk({
|
||||||
// cloning so we are free to mutate props (e.g. by adding _contexts)
|
// cloning so we are free to mutate props (e.g. by adding _contexts)
|
||||||
instance: cloneDeep(screen.props),
|
instance: cloneDeep(screen.props),
|
||||||
targetId: componentInstanceId,
|
targetId: componentInstanceId,
|
||||||
components,
|
components,
|
||||||
models,
|
tables,
|
||||||
})
|
})
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
@ -38,7 +38,7 @@ export default function({ componentInstanceId, screen, components, models }) {
|
||||||
.map(componentInstanceToBindable(walkResult)),
|
.map(componentInstanceToBindable(walkResult)),
|
||||||
|
|
||||||
...(walkResult.target?._contexts
|
...(walkResult.target?._contexts
|
||||||
.map(contextToBindables(models, walkResult))
|
.map(contextToBindables(tables, walkResult))
|
||||||
.flat() ?? []),
|
.flat() ?? []),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -71,15 +71,15 @@ const componentInstanceToBindable = walkResult => i => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const contextToBindables = (models, walkResult) => context => {
|
const contextToBindables = (tables, walkResult) => context => {
|
||||||
const contextParentPath = getParentPath(walkResult, context)
|
const contextParentPath = getParentPath(walkResult, context)
|
||||||
const isModel = context.model?.isModel || typeof context.model === "string"
|
const isTable = context.table?.isTable || typeof context.table === "string"
|
||||||
const modelId =
|
const tableId =
|
||||||
typeof context.model === "string" ? context.model : context.model.modelId
|
typeof context.table === "string" ? context.table : context.table.tableId
|
||||||
const model = models.find(model => model._id === modelId)
|
const table = tables.find(table => table._id === tableId)
|
||||||
|
|
||||||
// Avoid crashing whenever no data source has been selected
|
// Avoid crashing whenever no data source has been selected
|
||||||
if (model == null) {
|
if (table == null) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,12 +89,12 @@ const contextToBindables = (models, walkResult) => context => {
|
||||||
// how the binding expression persists, and is used in the app at runtime
|
// how the binding expression persists, and is used in the app at runtime
|
||||||
runtimeBinding: `${contextParentPath}data.${key}`,
|
runtimeBinding: `${contextParentPath}data.${key}`,
|
||||||
// how the binding exressions looks to the user of the builder
|
// how the binding exressions looks to the user of the builder
|
||||||
readableBinding: `${context.instance._instanceName}.${model.name}.${key}`,
|
readableBinding: `${context.instance._instanceName}.${table.name}.${key}`,
|
||||||
})
|
})
|
||||||
|
|
||||||
// see ModelViewSelect.svelte for the format of context.model
|
// see TableViewSelect.svelte for the format of context.table
|
||||||
// ... this allows us to bind to Model schemas, or View schemas
|
// ... this allows us to bind to Table schemas, or View schemas
|
||||||
const schema = isModel ? model.schema : model.views[context.model.name].schema
|
const schema = isTable ? table.schema : table.views[context.table.name].schema
|
||||||
|
|
||||||
return (
|
return (
|
||||||
Object.keys(schema)
|
Object.keys(schema)
|
||||||
|
@ -118,7 +118,7 @@ const getParentPath = (walkResult, context) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const walk = ({ instance, targetId, components, models, result }) => {
|
const walk = ({ instance, targetId, components, tables, result }) => {
|
||||||
if (!result) {
|
if (!result) {
|
||||||
result = {
|
result = {
|
||||||
target: null,
|
target: null,
|
||||||
|
@ -157,8 +157,8 @@ const walk = ({ instance, targetId, components, models, result }) => {
|
||||||
if (contextualInstance) {
|
if (contextualInstance) {
|
||||||
// add to currentContexts (ancestory of context)
|
// add to currentContexts (ancestory of context)
|
||||||
// before walking children
|
// before walking children
|
||||||
const model = instance[component.context]
|
const table = instance[component.context]
|
||||||
result.currentContexts.push({ instance, model })
|
result.currentContexts.push({ instance, table })
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentContexts = [...result.currentContexts]
|
const currentContexts = [...result.currentContexts]
|
||||||
|
@ -167,7 +167,7 @@ const walk = ({ instance, targetId, components, models, result }) => {
|
||||||
// these have been deep cloned above, so shouln't modify the
|
// these have been deep cloned above, so shouln't modify the
|
||||||
// original component instances
|
// original component instances
|
||||||
child._contexts = currentContexts
|
child._contexts = currentContexts
|
||||||
walk({ instance: child, targetId, components, models, result })
|
walk({ instance: child, targetId, components, tables, result })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contextualInstance) {
|
if (contextualInstance) {
|
||||||
|
|
|
@ -29,13 +29,13 @@ export default {
|
||||||
name: "Record Saved",
|
name: "Record Saved",
|
||||||
event: "record:save",
|
event: "record:save",
|
||||||
icon: "ri-save-line",
|
icon: "ri-save-line",
|
||||||
tagline: "Record is added to <b>{{model.name}}</b>",
|
tagline: "Record is added to <b>{{table.name}}</b>",
|
||||||
description: "Fired when a record is saved to your database.",
|
description: "Fired when a record is saved to your database.",
|
||||||
params: { model: "model" },
|
params: { table: "table" },
|
||||||
type: "TRIGGER",
|
type: "TRIGGER",
|
||||||
args: {
|
args: {
|
||||||
model: {
|
table: {
|
||||||
type: "model",
|
type: "table",
|
||||||
views: {},
|
views: {},
|
||||||
name: "users",
|
name: "users",
|
||||||
schema: {
|
schema: {
|
||||||
|
|
|
@ -3,12 +3,12 @@ import { cloneDeep } from "lodash/fp"
|
||||||
import api from "../api"
|
import api from "../api"
|
||||||
|
|
||||||
const INITIAL_BACKEND_UI_STATE = {
|
const INITIAL_BACKEND_UI_STATE = {
|
||||||
models: [],
|
tables: [],
|
||||||
views: [],
|
views: [],
|
||||||
users: [],
|
users: [],
|
||||||
selectedDatabase: {},
|
selectedDatabase: {},
|
||||||
selectedModel: {},
|
selectedTable: {},
|
||||||
draftModel: {},
|
draftTable: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getBackendUiStore = () => {
|
export const getBackendUiStore = () => {
|
||||||
|
@ -18,11 +18,11 @@ export const getBackendUiStore = () => {
|
||||||
reset: () => store.set({ ...INITIAL_BACKEND_UI_STATE }),
|
reset: () => store.set({ ...INITIAL_BACKEND_UI_STATE }),
|
||||||
database: {
|
database: {
|
||||||
select: async db => {
|
select: async db => {
|
||||||
const modelsResponse = await api.get(`/api/models`)
|
const tablesResponse = await api.get(`/api/tables`)
|
||||||
const models = await modelsResponse.json()
|
const tables = await tablesResponse.json()
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.selectedDatabase = db
|
state.selectedDatabase = db
|
||||||
state.models = models
|
state.tables = tables
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -44,50 +44,50 @@ export const getBackendUiStore = () => {
|
||||||
return state
|
return state
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
models: {
|
tables: {
|
||||||
fetch: async () => {
|
fetch: async () => {
|
||||||
const modelsResponse = await api.get(`/api/models`)
|
const tablesResponse = await api.get(`/api/tables`)
|
||||||
const models = await modelsResponse.json()
|
const tables = await tablesResponse.json()
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.models = models
|
state.tables = tables
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
select: model =>
|
select: table =>
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.selectedModel = model
|
state.selectedTable = table
|
||||||
state.draftModel = cloneDeep(model)
|
state.draftTable = cloneDeep(table)
|
||||||
state.selectedView = { name: `all_${model._id}` }
|
state.selectedView = { name: `all_${table._id}` }
|
||||||
return state
|
return state
|
||||||
}),
|
}),
|
||||||
save: async model => {
|
save: async table => {
|
||||||
const updatedModel = cloneDeep(model)
|
const updatedTable = cloneDeep(table)
|
||||||
|
|
||||||
// update any renamed schema keys to reflect their names
|
// update any renamed schema keys to reflect their names
|
||||||
for (let key in updatedModel.schema) {
|
for (let key in updatedTable.schema) {
|
||||||
const field = updatedModel.schema[key]
|
const field = updatedTable.schema[key]
|
||||||
// field has been renamed
|
// field has been renamed
|
||||||
if (field.name && field.name !== key) {
|
if (field.name && field.name !== key) {
|
||||||
updatedModel.schema[field.name] = field
|
updatedTable.schema[field.name] = field
|
||||||
updatedModel._rename = { old: key, updated: field.name }
|
updatedTable._rename = { old: key, updated: field.name }
|
||||||
delete updatedModel.schema[key]
|
delete updatedTable.schema[key]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SAVE_MODEL_URL = `/api/models`
|
const SAVE_TABLE_URL = `/api/tables`
|
||||||
const response = await api.post(SAVE_MODEL_URL, updatedModel)
|
const response = await api.post(SAVE_TABLE_URL, updatedTable)
|
||||||
const savedModel = await response.json()
|
const savedTable = await response.json()
|
||||||
await store.actions.models.fetch()
|
await store.actions.tables.fetch()
|
||||||
store.actions.models.select(savedModel)
|
store.actions.tables.select(savedTable)
|
||||||
return savedModel
|
return savedTable
|
||||||
},
|
},
|
||||||
delete: async model => {
|
delete: async table => {
|
||||||
await api.delete(`/api/models/${model._id}/${model._rev}`)
|
await api.delete(`/api/tables/${table._id}/${table._rev}`)
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.models = state.models.filter(
|
state.tables = state.tables.filter(
|
||||||
existing => existing._id !== model._id
|
existing => existing._id !== table._id
|
||||||
)
|
)
|
||||||
state.selectedModel = {}
|
state.selectedTable = {}
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -95,23 +95,23 @@ export const getBackendUiStore = () => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
// delete the original if renaming
|
// delete the original if renaming
|
||||||
if (originalName) {
|
if (originalName) {
|
||||||
delete state.draftModel.schema[originalName]
|
delete state.draftTable.schema[originalName]
|
||||||
state.draftModel._rename = {
|
state.draftTable._rename = {
|
||||||
old: originalName,
|
old: originalName,
|
||||||
updated: field.name,
|
updated: field.name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.draftModel.schema[field.name] = cloneDeep(field)
|
state.draftTable.schema[field.name] = cloneDeep(field)
|
||||||
|
|
||||||
store.actions.models.save(state.draftModel)
|
store.actions.tables.save(state.draftTable)
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
deleteField: field => {
|
deleteField: field => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
delete state.draftModel.schema[field.name]
|
delete state.draftTable.schema[field.name]
|
||||||
store.actions.models.save(state.draftModel)
|
store.actions.tables.save(state.draftTable)
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -120,12 +120,12 @@ export const getBackendUiStore = () => {
|
||||||
select: view =>
|
select: view =>
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.selectedView = view
|
state.selectedView = view
|
||||||
state.selectedModel = {}
|
state.selectedTable = {}
|
||||||
return state
|
return state
|
||||||
}),
|
}),
|
||||||
delete: async view => {
|
delete: async view => {
|
||||||
await api.delete(`/api/views/${view}`)
|
await api.delete(`/api/views/${view}`)
|
||||||
await store.actions.models.fetch()
|
await store.actions.tables.fetch()
|
||||||
},
|
},
|
||||||
save: async view => {
|
save: async view => {
|
||||||
const response = await api.post(`/api/views`, view)
|
const response = await api.post(`/api/views`, view)
|
||||||
|
@ -137,14 +137,14 @@ export const getBackendUiStore = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const viewModel = state.models.find(
|
const viewTable = state.tables.find(
|
||||||
model => model._id === view.modelId
|
table => table._id === view.tableId
|
||||||
)
|
)
|
||||||
|
|
||||||
if (view.originalName) delete viewModel.views[view.originalName]
|
if (view.originalName) delete viewTable.views[view.originalName]
|
||||||
viewModel.views[view.name] = viewMeta
|
viewTable.views[view.name] = viewMeta
|
||||||
|
|
||||||
state.models = state.models
|
state.tables = state.tables
|
||||||
state.selectedView = viewMeta
|
state.selectedView = viewMeta
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
|
|
@ -13,10 +13,10 @@
|
||||||
|
|
||||||
function enrichInputs(inputs) {
|
function enrichInputs(inputs) {
|
||||||
let enrichedInputs = { ...inputs, enriched: {} }
|
let enrichedInputs = { ...inputs, enriched: {} }
|
||||||
const modelId = inputs.modelId || inputs.record?.modelId
|
const tableId = inputs.tableId || inputs.record?.tableId
|
||||||
if (modelId) {
|
if (tableId) {
|
||||||
enrichedInputs.enriched.model = $backendUiStore.models.find(
|
enrichedInputs.enriched.table = $backendUiStore.tables.find(
|
||||||
model => model._id === modelId
|
table => table._id === tableId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return enrichedInputs
|
return enrichedInputs
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import ModelSelector from "./ParamInputs/ModelSelector.svelte"
|
import TableSelector from "./ParamInputs/TableSelector.svelte"
|
||||||
import RecordSelector from "./ParamInputs/RecordSelector.svelte"
|
import RecordSelector from "./ParamInputs/RecordSelector.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"
|
||||||
|
@ -60,8 +60,8 @@
|
||||||
</Select>
|
</Select>
|
||||||
{:else if value.customType === 'password'}
|
{:else if value.customType === 'password'}
|
||||||
<Input type="password" thin bind:value={block.inputs[key]} />
|
<Input type="password" thin bind:value={block.inputs[key]} />
|
||||||
{:else if value.customType === 'model'}
|
{:else if value.customType === 'table'}
|
||||||
<ModelSelector bind:value={block.inputs[key]} />
|
<TableSelector bind:value={block.inputs[key]} />
|
||||||
{:else if value.customType === 'record'}
|
{:else if value.customType === 'record'}
|
||||||
<RecordSelector bind:value={block.inputs[key]} {bindings} />
|
<RecordSelector bind:value={block.inputs[key]} {bindings} />
|
||||||
{:else if value.type === 'string' || value.type === 'number'}
|
{:else if value.type === 'string' || value.type === 'number'}
|
||||||
|
|
|
@ -6,12 +6,12 @@
|
||||||
export let value
|
export let value
|
||||||
export let bindings
|
export let bindings
|
||||||
|
|
||||||
$: model = $backendUiStore.models.find(model => model._id === value?.modelId)
|
$: table = $backendUiStore.tables.find(table => table._id === value?.tableId)
|
||||||
$: schemaFields = Object.entries(model?.schema ?? {})
|
$: schemaFields = Object.entries(table?.schema ?? {})
|
||||||
|
|
||||||
// Ensure any nullish modelId values get set to empty string so
|
// Ensure any nullish tableId values get set to empty string so
|
||||||
// that the select works
|
// that the select works
|
||||||
$: if (value?.modelId == null) value = { modelId: "" }
|
$: if (value?.tableId == null) value = { tableId: "" }
|
||||||
|
|
||||||
function schemaHasOptions(schema) {
|
function schemaHasOptions(schema) {
|
||||||
return !!schema.constraints?.inclusion?.length
|
return !!schema.constraints?.inclusion?.length
|
||||||
|
@ -19,10 +19,10 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="block-field">
|
<div class="block-field">
|
||||||
<Select bind:value={value.modelId} thin secondary>
|
<Select bind:value={value.tableId} thin secondary>
|
||||||
<option value="">Choose an option</option>
|
<option value="">Choose an option</option>
|
||||||
{#each $backendUiStore.models as model}
|
{#each $backendUiStore.tables as table}
|
||||||
<option value={model._id}>{model.name}</option>
|
<option value={table._id}>{table.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
<div class="block-field">
|
<div class="block-field">
|
||||||
<Select bind:value secondary thin>
|
<Select bind:value secondary thin>
|
||||||
<option value="">Choose an option</option>
|
<option value="">Choose an option</option>
|
||||||
{#each $backendUiStore.models as model}
|
{#each $backendUiStore.tables as table}
|
||||||
<option value={model._id}>{model.name}</option>
|
<option value={table._id}>{table.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
|
@ -10,14 +10,14 @@
|
||||||
let data = []
|
let data = []
|
||||||
let loading = false
|
let loading = false
|
||||||
|
|
||||||
$: title = $backendUiStore.selectedModel.name
|
$: title = $backendUiStore.selectedTable.name
|
||||||
$: schema = $backendUiStore.selectedModel.schema
|
$: schema = $backendUiStore.selectedTable.schema
|
||||||
$: modelView = {
|
$: tableView = {
|
||||||
schema,
|
schema,
|
||||||
name: $backendUiStore.selectedView.name,
|
name: $backendUiStore.selectedView.name,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch records for specified model
|
// Fetch records for specified table
|
||||||
$: {
|
$: {
|
||||||
if ($backendUiStore.selectedView?.name?.startsWith("all_")) {
|
if ($backendUiStore.selectedView?.name?.startsWith("all_")) {
|
||||||
loading = true
|
loading = true
|
||||||
|
@ -34,6 +34,6 @@
|
||||||
{#if Object.keys(schema).length > 0}
|
{#if Object.keys(schema).length > 0}
|
||||||
<CreateRowButton />
|
<CreateRowButton />
|
||||||
<CreateViewButton />
|
<CreateViewButton />
|
||||||
<ExportButton view={modelView} />
|
<ExportButton view={tableView} />
|
||||||
{/if}
|
{/if}
|
||||||
</Table>
|
</Table>
|
|
@ -4,7 +4,7 @@
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
|
|
||||||
export let modelId
|
export let tableId
|
||||||
export let recordId
|
export let recordId
|
||||||
export let fieldName
|
export let fieldName
|
||||||
|
|
||||||
|
@ -12,15 +12,15 @@
|
||||||
let title
|
let title
|
||||||
|
|
||||||
$: data = record?.[fieldName] ?? []
|
$: data = record?.[fieldName] ?? []
|
||||||
$: linkedModelId = data?.length ? data[0].modelId : null
|
$: linkedTableId = data?.length ? data[0].tableId : null
|
||||||
$: linkedModel = $backendUiStore.models.find(
|
$: linkedTable = $backendUiStore.tables.find(
|
||||||
model => model._id === linkedModelId
|
table => table._id === linkedTableId
|
||||||
)
|
)
|
||||||
$: schema = linkedModel?.schema
|
$: schema = linkedTable?.schema
|
||||||
$: model = $backendUiStore.models.find(model => model._id === modelId)
|
$: table = $backendUiStore.tables.find(table => table._id === tableId)
|
||||||
$: fetchData(modelId, recordId)
|
$: fetchData(tableId, recordId)
|
||||||
$: {
|
$: {
|
||||||
let recordLabel = record?.[model?.primaryDisplay]
|
let recordLabel = record?.[table?.primaryDisplay]
|
||||||
if (recordLabel) {
|
if (recordLabel) {
|
||||||
title = `${recordLabel} - ${fieldName}`
|
title = `${recordLabel} - ${fieldName}`
|
||||||
} else {
|
} else {
|
||||||
|
@ -28,8 +28,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchData(modelId, recordId) {
|
async function fetchData(tableId, recordId) {
|
||||||
const QUERY_VIEW_URL = `/api/${modelId}/${recordId}/enrich`
|
const QUERY_VIEW_URL = `/api/${tableId}/${recordId}/enrich`
|
||||||
const response = await api.get(QUERY_VIEW_URL)
|
const response = await api.get(QUERY_VIEW_URL)
|
||||||
record = await response.json()
|
record = await response.json()
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,14 +39,14 @@
|
||||||
currentPage * ITEMS_PER_PAGE + ITEMS_PER_PAGE
|
currentPage * ITEMS_PER_PAGE + ITEMS_PER_PAGE
|
||||||
)
|
)
|
||||||
: []
|
: []
|
||||||
$: modelId = data?.length ? data[0].modelId : null
|
$: tableId = data?.length ? data[0].tableId : null
|
||||||
|
|
||||||
function selectRelationship(record, fieldName) {
|
function selectRelationship(record, fieldName) {
|
||||||
if (!record?.[fieldName]?.length) {
|
if (!record?.[fieldName]?.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
$goto(
|
$goto(
|
||||||
`/${$params.application}/backend/model/${modelId}/relationship/${record._id}/${fieldName}`
|
`/${$params.application}/backend/table/${tableId}/relationship/${record._id}/${fieldName}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -6,15 +6,15 @@ export async function createUser(user) {
|
||||||
return await response.json()
|
return await response.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveRecord(record, modelId) {
|
export async function saveRecord(record, tableId) {
|
||||||
const SAVE_RECORDS_URL = `/api/${modelId}/records`
|
const SAVE_RECORDS_URL = `/api/${tableId}/records`
|
||||||
const response = await api.post(SAVE_RECORDS_URL, record)
|
const response = await api.post(SAVE_RECORDS_URL, record)
|
||||||
|
|
||||||
return await response.json()
|
return await response.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteRecord(record) {
|
export async function deleteRecord(record) {
|
||||||
const DELETE_RECORDS_URL = `/api/${record.modelId}/records/${record._id}/${record._rev}`
|
const DELETE_RECORDS_URL = `/api/${record.tableId}/records/${record._id}/${record._rev}`
|
||||||
const response = await api.delete(DELETE_RECORDS_URL)
|
const response = await api.delete(DELETE_RECORDS_URL)
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,15 +11,15 @@
|
||||||
let errors = []
|
let errors = []
|
||||||
|
|
||||||
$: creating = record?._id == null
|
$: creating = record?._id == null
|
||||||
$: model = record.modelId
|
$: table = record.tableId
|
||||||
? $backendUiStore.models.find(model => model._id === record?.modelId)
|
? $backendUiStore.tables.find(table => table._id === record?.tableId)
|
||||||
: $backendUiStore.selectedModel
|
: $backendUiStore.selectedTable
|
||||||
$: modelSchema = Object.entries(model?.schema ?? {})
|
$: tableSchema = Object.entries(table?.schema ?? {})
|
||||||
|
|
||||||
async function saveRecord() {
|
async function saveRecord() {
|
||||||
const recordResponse = await api.saveRecord(
|
const recordResponse = await api.saveRecord(
|
||||||
{ ...record, modelId: model._id },
|
{ ...record, tableId: table._id },
|
||||||
model._id
|
table._id
|
||||||
)
|
)
|
||||||
if (recordResponse.errors) {
|
if (recordResponse.errors) {
|
||||||
errors = Object.keys(recordResponse.errors)
|
errors = Object.keys(recordResponse.errors)
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
confirmText={creating ? 'Create Row' : 'Save Row'}
|
confirmText={creating ? 'Create Row' : 'Save Row'}
|
||||||
onConfirm={saveRecord}>
|
onConfirm={saveRecord}>
|
||||||
<ErrorsBox {errors} />
|
<ErrorsBox {errors} />
|
||||||
{#each modelSchema as [key, meta]}
|
{#each tableSchema as [key, meta]}
|
||||||
<div>
|
<div>
|
||||||
<RecordFieldControl {meta} bind:value={record[key]} />
|
<RecordFieldControl {meta} bind:value={record[key]} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,13 +14,13 @@
|
||||||
export let view = {}
|
export let view = {}
|
||||||
export let onClosed
|
export let onClosed
|
||||||
|
|
||||||
$: viewModel = $backendUiStore.models.find(
|
$: viewTable = $backendUiStore.tables.find(
|
||||||
({ _id }) => _id === $backendUiStore.selectedView.modelId
|
({ _id }) => _id === $backendUiStore.selectedView.tableId
|
||||||
)
|
)
|
||||||
$: fields =
|
$: fields =
|
||||||
viewModel &&
|
viewTable &&
|
||||||
Object.keys(viewModel.schema).filter(
|
Object.keys(viewTable.schema).filter(
|
||||||
field => viewModel.schema[field].type === "number"
|
field => viewTable.schema[field].type === "number"
|
||||||
)
|
)
|
||||||
|
|
||||||
function saveView() {
|
function saveView() {
|
||||||
|
|
|
@ -32,10 +32,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteColumn() {
|
function deleteColumn() {
|
||||||
if (field.name === $backendUiStore.selectedModel.primaryDisplay) {
|
if (field.name === $backendUiStore.selectedTable.primaryDisplay) {
|
||||||
notifier.danger("You cannot delete the primary display column")
|
notifier.danger("You cannot delete the primary display column")
|
||||||
} else {
|
} else {
|
||||||
backendUiStore.actions.models.deleteField(field)
|
backendUiStore.actions.tables.deleteField(field)
|
||||||
notifier.success("Column deleted")
|
notifier.success("Column deleted")
|
||||||
}
|
}
|
||||||
hideEditor()
|
hideEditor()
|
||||||
|
|
|
@ -31,14 +31,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
let originalName = field.name
|
let originalName = field.name
|
||||||
$: modelOptions = $backendUiStore.models.filter(
|
$: tableOptions = $backendUiStore.tables.filter(
|
||||||
model => model._id !== $backendUiStore.draftModel._id
|
table => table._id !== $backendUiStore.draftTable._id
|
||||||
)
|
)
|
||||||
$: required = !!field?.constraints?.presence
|
$: required = !!field?.constraints?.presence
|
||||||
|
|
||||||
async function saveColumn() {
|
async function saveColumn() {
|
||||||
backendUiStore.update(state => {
|
backendUiStore.update(state => {
|
||||||
backendUiStore.actions.models.saveField({
|
backendUiStore.actions.tables.saveField({
|
||||||
originalName,
|
originalName,
|
||||||
field,
|
field,
|
||||||
})
|
})
|
||||||
|
@ -111,10 +111,10 @@
|
||||||
label="Max Value"
|
label="Max Value"
|
||||||
bind:value={field.constraints.numericality.lessThanOrEqualTo} />
|
bind:value={field.constraints.numericality.lessThanOrEqualTo} />
|
||||||
{:else if field.type === 'link'}
|
{:else if field.type === 'link'}
|
||||||
<Select label="Table" thin secondary bind:value={field.modelId}>
|
<Select label="Table" thin secondary bind:value={field.tableId}>
|
||||||
<option value="">Choose an option</option>
|
<option value="">Choose an option</option>
|
||||||
{#each modelOptions as model}
|
{#each tableOptions as table}
|
||||||
<option value={model._id}>{model.name}</option>
|
<option value={table._id}>{table.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
||||||
<Input
|
<Input
|
||||||
|
|
|
@ -10,11 +10,11 @@
|
||||||
let name
|
let name
|
||||||
let field
|
let field
|
||||||
|
|
||||||
$: fields = Object.keys($backendUiStore.selectedModel.schema).filter(key => {
|
$: fields = Object.keys($backendUiStore.selectedTable.schema).filter(key => {
|
||||||
return $backendUiStore.selectedModel.schema[key].type === "number"
|
return $backendUiStore.selectedTable.schema[key].type === "number"
|
||||||
})
|
})
|
||||||
$: views = $backendUiStore.models.flatMap(model =>
|
$: views = $backendUiStore.tables.flatMap(table =>
|
||||||
Object.keys(model.views || {})
|
Object.keys(table.views || {})
|
||||||
)
|
)
|
||||||
|
|
||||||
function saveView() {
|
function saveView() {
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
}
|
}
|
||||||
backendUiStore.actions.views.save({
|
backendUiStore.actions.views.save({
|
||||||
name,
|
name,
|
||||||
modelId: $backendUiStore.selectedModel._id,
|
tableId: $backendUiStore.selectedTable._id,
|
||||||
field,
|
field,
|
||||||
})
|
})
|
||||||
notifier.success(`View ${name} created`)
|
notifier.success(`View ${name} created`)
|
||||||
|
|
|
@ -45,10 +45,10 @@
|
||||||
export let view = {}
|
export let view = {}
|
||||||
export let onClosed
|
export let onClosed
|
||||||
|
|
||||||
$: viewModel = $backendUiStore.models.find(
|
$: viewTable = $backendUiStore.tables.find(
|
||||||
({ _id }) => _id === $backendUiStore.selectedView.modelId
|
({ _id }) => _id === $backendUiStore.selectedView.tableId
|
||||||
)
|
)
|
||||||
$: fields = viewModel && Object.keys(viewModel.schema)
|
$: fields = viewTable && Object.keys(viewTable.schema)
|
||||||
|
|
||||||
function saveView() {
|
function saveView() {
|
||||||
backendUiStore.actions.views.save(view)
|
backendUiStore.actions.views.save(view)
|
||||||
|
@ -71,25 +71,25 @@
|
||||||
|
|
||||||
function isMultipleChoice(field) {
|
function isMultipleChoice(field) {
|
||||||
return (
|
return (
|
||||||
(viewModel.schema[field].constraints &&
|
(viewTable.schema[field].constraints &&
|
||||||
viewModel.schema[field].constraints.inclusion &&
|
viewTable.schema[field].constraints.inclusion &&
|
||||||
viewModel.schema[field].constraints.inclusion.length) ||
|
viewTable.schema[field].constraints.inclusion.length) ||
|
||||||
viewModel.schema[field].type === "boolean"
|
viewTable.schema[field].type === "boolean"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function fieldOptions(field) {
|
function fieldOptions(field) {
|
||||||
return viewModel.schema[field].type === "string"
|
return viewTable.schema[field].type === "string"
|
||||||
? viewModel.schema[field].constraints.inclusion
|
? viewTable.schema[field].constraints.inclusion
|
||||||
: [true, false]
|
: [true, false]
|
||||||
}
|
}
|
||||||
|
|
||||||
function isDate(field) {
|
function isDate(field) {
|
||||||
return viewModel.schema[field].type === "datetime"
|
return viewTable.schema[field].type === "datetime"
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNumber(field) {
|
function isNumber(field) {
|
||||||
return viewModel.schema[field].type === "number"
|
return viewTable.schema[field].type === "number"
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldChanged = filter => ev => {
|
const fieldChanged = filter => ev => {
|
||||||
|
@ -97,8 +97,8 @@
|
||||||
if (
|
if (
|
||||||
filter.key &&
|
filter.key &&
|
||||||
ev.target.value &&
|
ev.target.value &&
|
||||||
viewModel.schema[filter.key].type !==
|
viewTable.schema[filter.key].type !==
|
||||||
viewModel.schema[ev.target.value].type
|
viewTable.schema[ev.target.value].type
|
||||||
) {
|
) {
|
||||||
filter.value = ""
|
filter.value = ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,10 @@
|
||||||
export let view = {}
|
export let view = {}
|
||||||
export let onClosed
|
export let onClosed
|
||||||
|
|
||||||
$: viewModel = $backendUiStore.models.find(
|
$: viewTable = $backendUiStore.tables.find(
|
||||||
({ _id }) => _id === $backendUiStore.selectedView.modelId
|
({ _id }) => _id === $backendUiStore.selectedView.tableId
|
||||||
)
|
)
|
||||||
$: fields = viewModel && Object.keys(viewModel.schema)
|
$: fields = viewTable && Object.keys(viewTable.schema)
|
||||||
|
|
||||||
function saveView() {
|
function saveView() {
|
||||||
backendUiStore.actions.views.save(view)
|
backendUiStore.actions.views.save(view)
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
data-cy="model-nav-item"
|
data-cy="table-nav-item"
|
||||||
class:indented
|
class:indented
|
||||||
class:selected
|
class:selected
|
||||||
on:click
|
on:click
|
|
@ -21,28 +21,28 @@
|
||||||
!schema || Object.keys(schema).every(column => schema[column].success)
|
!schema || Object.keys(schema).every(column => schema[column].success)
|
||||||
$: dataImport = {
|
$: dataImport = {
|
||||||
valid,
|
valid,
|
||||||
schema: buildModelSchema(schema),
|
schema: buildTableSchema(schema),
|
||||||
path: files[0] && files[0].path,
|
path: files[0] && files[0].path,
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildModelSchema(schema) {
|
function buildTableSchema(schema) {
|
||||||
const modelSchema = {}
|
const tableSchema = {}
|
||||||
for (let key in schema) {
|
for (let key in schema) {
|
||||||
const type = schema[key].type
|
const type = schema[key].type
|
||||||
|
|
||||||
if (type === "omit") continue
|
if (type === "omit") continue
|
||||||
|
|
||||||
modelSchema[key] = {
|
tableSchema[key] = {
|
||||||
name: key,
|
name: key,
|
||||||
type,
|
type,
|
||||||
constraints: FIELDS[type.toUpperCase()].constraints,
|
constraints: FIELDS[type.toUpperCase()].constraints,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return modelSchema
|
return tableSchema
|
||||||
}
|
}
|
||||||
|
|
||||||
async function validateCSV() {
|
async function validateCSV() {
|
||||||
const response = await api.post("/api/models/csv/validate", {
|
const response = await api.post("/api/tables/csv/validate", {
|
||||||
file: files[0],
|
file: files[0],
|
||||||
schema: schema || {},
|
schema: schema || {},
|
||||||
})
|
})
|
|
@ -11,9 +11,9 @@
|
||||||
$: selectedView =
|
$: selectedView =
|
||||||
$backendUiStore.selectedView && $backendUiStore.selectedView.name
|
$backendUiStore.selectedView && $backendUiStore.selectedView.name
|
||||||
|
|
||||||
function selectModel(model) {
|
function selectTable(table) {
|
||||||
backendUiStore.actions.models.select(model)
|
backendUiStore.actions.tables.select(table)
|
||||||
$goto(`./model/${model._id}`)
|
$goto(`./table/${table._id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectView(view) {
|
function selectView(view) {
|
||||||
|
@ -30,15 +30,15 @@
|
||||||
<Spacer medium />
|
<Spacer medium />
|
||||||
<CreateTableModal />
|
<CreateTableModal />
|
||||||
<div class="hierarchy-items-container">
|
<div class="hierarchy-items-container">
|
||||||
{#each $backendUiStore.models as model}
|
{#each $backendUiStore.tables as table}
|
||||||
<ListItem
|
<ListItem
|
||||||
selected={selectedView === `all_${model._id}`}
|
selected={selectedView === `all_${table._id}`}
|
||||||
title={model.name}
|
title={table.name}
|
||||||
icon="ri-table-fill"
|
icon="ri-table-fill"
|
||||||
on:click={() => selectModel(model)}>
|
on:click={() => selectTable(table)}>
|
||||||
<EditTablePopover table={model} />
|
<EditTablePopover table={table} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
{#each Object.keys(model.views || {}) as viewName}
|
{#each Object.keys(table.views || {}) as viewName}
|
||||||
<ListItem
|
<ListItem
|
||||||
indented
|
indented
|
||||||
selected={selectedView === viewName}
|
selected={selectedView === viewName}
|
||||||
|
@ -46,10 +46,10 @@
|
||||||
icon="ri-eye-line"
|
icon="ri-eye-line"
|
||||||
on:click={() => (selectedView === viewName ? {} : selectView({
|
on:click={() => (selectedView === viewName ? {} : selectView({
|
||||||
name: viewName,
|
name: viewName,
|
||||||
...model.views[viewName],
|
...table.views[viewName],
|
||||||
}))}>
|
}))}>
|
||||||
<EditViewPopover
|
<EditViewPopover
|
||||||
view={{ name: viewName, ...model.views[viewName] }} />
|
view={{ name: viewName, ...table.views[viewName] }} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
{/each}
|
{/each}
|
||||||
{/each}
|
{/each}
|
|
@ -17,13 +17,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveTable() {
|
async function saveTable() {
|
||||||
const model = await backendUiStore.actions.models.save({
|
const table = await backendUiStore.actions.tables.save({
|
||||||
name,
|
name,
|
||||||
schema: dataImport.schema || {},
|
schema: dataImport.schema || {},
|
||||||
dataImport,
|
dataImport,
|
||||||
})
|
})
|
||||||
notifier.success(`Table ${name} created successfully.`)
|
notifier.success(`Table ${name} created successfully.`)
|
||||||
$goto(`./model/${model._id}`)
|
$goto(`./table/${table._id}`)
|
||||||
analytics.captureEvent("Table Created", { name })
|
analytics.captureEvent("Table Created", { name })
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -29,13 +29,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteTable() {
|
async function deleteTable() {
|
||||||
await backendUiStore.actions.models.delete(table)
|
await backendUiStore.actions.tables.delete(table)
|
||||||
notifier.success("Table deleted")
|
notifier.success("Table deleted")
|
||||||
hideEditor()
|
hideEditor()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
await backendUiStore.actions.models.save(table)
|
await backendUiStore.actions.tables.save(table)
|
||||||
notifier.success("Table renamed successfully")
|
notifier.success("Table renamed successfully")
|
||||||
hideEditor()
|
hideEditor()
|
||||||
}
|
}
|
|
@ -39,10 +39,10 @@
|
||||||
|
|
||||||
async function deleteView() {
|
async function deleteView() {
|
||||||
const name = view.name
|
const name = view.name
|
||||||
const id = view.modelId
|
const id = view.tableId
|
||||||
await backendUiStore.actions.views.delete(name)
|
await backendUiStore.actions.views.delete(name)
|
||||||
notifier.success("View deleted")
|
notifier.success("View deleted")
|
||||||
$goto(`./model/${id}`)
|
$goto(`./table/${id}`)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -11,14 +11,14 @@
|
||||||
let records = []
|
let records = []
|
||||||
|
|
||||||
$: label = capitalise(schema.name)
|
$: label = capitalise(schema.name)
|
||||||
$: linkedModelId = schema.modelId
|
$: linkedTableId = schema.tableId
|
||||||
$: linkedModel = $backendUiStore.models.find(
|
$: linkedTable = $backendUiStore.tables.find(
|
||||||
model => model._id === linkedModelId
|
table => table._id === linkedTableId
|
||||||
)
|
)
|
||||||
$: fetchRecords(linkedModelId)
|
$: fetchRecords(linkedTableId)
|
||||||
|
|
||||||
async function fetchRecords(linkedModelId) {
|
async function fetchRecords(linkedTableId) {
|
||||||
const FETCH_RECORDS_URL = `/api/${linkedModelId}/records`
|
const FETCH_RECORDS_URL = `/api/${linkedTableId}/records`
|
||||||
try {
|
try {
|
||||||
const response = await api.get(FETCH_RECORDS_URL)
|
const response = await api.get(FETCH_RECORDS_URL)
|
||||||
records = await response.json()
|
records = await response.json()
|
||||||
|
@ -29,15 +29,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPrettyName(record) {
|
function getPrettyName(record) {
|
||||||
return record[linkedModel.primaryDisplay || "_id"]
|
return record[linkedTable.primaryDisplay || "_id"]
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if linkedModel.primaryDisplay == null}
|
{#if linkedTable.primaryDisplay == null}
|
||||||
<Label extraSmall grey>{label}</Label>
|
<Label extraSmall grey>{label}</Label>
|
||||||
<Label small black>
|
<Label small black>
|
||||||
Please choose a primary display column for the
|
Please choose a primary display column for the
|
||||||
<b>{linkedModel.name}</b>
|
<b>{linkedTable.name}</b>
|
||||||
table.
|
table.
|
||||||
</Label>
|
</Label>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
|
@ -10,23 +10,23 @@
|
||||||
componentInstanceId: $store.currentComponentInfo._id,
|
componentInstanceId: $store.currentComponentInfo._id,
|
||||||
components: $store.components,
|
components: $store.components,
|
||||||
screen: $store.currentPreviewItem,
|
screen: $store.currentPreviewItem,
|
||||||
models: $backendUiStore.models,
|
tables: $backendUiStore.tables,
|
||||||
})
|
})
|
||||||
|
|
||||||
// just wraps binding in {{ ... }}
|
// just wraps binding in {{ ... }}
|
||||||
const toBindingExpression = bindingPath => `{{ ${bindingPath} }}`
|
const toBindingExpression = bindingPath => `{{ ${bindingPath} }}`
|
||||||
|
|
||||||
const modelFields = modelId => {
|
const tableFields = tableId => {
|
||||||
const model = $backendUiStore.models.find(m => m._id === modelId)
|
const table = $backendUiStore.tables.find(m => m._id === tableId)
|
||||||
|
|
||||||
return Object.keys(model.schema).map(k => ({
|
return Object.keys(table.schema).map(k => ({
|
||||||
name: k,
|
name: k,
|
||||||
type: model.schema[k].type,
|
type: table.schema[k].type,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
$: schemaFields =
|
$: schemaFields =
|
||||||
parameters && parameters.modelId ? modelFields(parameters.modelId) : []
|
parameters && parameters.tableId ? tableFields(parameters.tableId) : []
|
||||||
|
|
||||||
const onFieldsChanged = e => {
|
const onFieldsChanged = e => {
|
||||||
parameters.fields = e.detail
|
parameters.fields = e.detail
|
||||||
|
@ -35,14 +35,14 @@
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
<Label size="m" color="dark">Table</Label>
|
<Label size="m" color="dark">Table</Label>
|
||||||
<Select secondary bind:value={parameters.modelId}>
|
<Select secondary bind:value={parameters.tableId}>
|
||||||
<option value="" />
|
<option value="" />
|
||||||
{#each $backendUiStore.models as model}
|
{#each $backendUiStore.tables as table}
|
||||||
<option value={model._id}>{model.name}</option>
|
<option value={table._id}>{table.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
{#if parameters.modelId}
|
{#if parameters.tableId}
|
||||||
<SaveFields
|
<SaveFields
|
||||||
parameterFields={parameters.fields}
|
parameterFields={parameters.fields}
|
||||||
{schemaFields}
|
{schemaFields}
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
componentInstanceId: $store.currentComponentInfo._id,
|
componentInstanceId: $store.currentComponentInfo._id,
|
||||||
components: $store.components,
|
components: $store.components,
|
||||||
screen: $store.currentPreviewItem,
|
screen: $store.currentPreviewItem,
|
||||||
models: $backendUiStore.models,
|
tables: $backendUiStore.tables,
|
||||||
})
|
})
|
||||||
|
|
||||||
const addField = () => {
|
const addField = () => {
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
componentInstanceId: $store.currentComponentInfo._id,
|
componentInstanceId: $store.currentComponentInfo._id,
|
||||||
components: $store.components,
|
components: $store.components,
|
||||||
screen: $store.currentPreviewItem,
|
screen: $store.currentPreviewItem,
|
||||||
models: $backendUiStore.models,
|
tables: $backendUiStore.tables,
|
||||||
})
|
})
|
||||||
|
|
||||||
let idFields
|
let idFields
|
||||||
|
@ -56,16 +56,16 @@
|
||||||
|
|
||||||
const component = $store.components[instance._component]
|
const component = $store.components[instance._component]
|
||||||
|
|
||||||
// component.context is the name of the prop that holds the modelId
|
// component.context is the name of the prop that holds the tableId
|
||||||
const modelInfo = instance[component.context]
|
const tableInfo = instance[component.context]
|
||||||
|
|
||||||
if (!modelInfo) return []
|
if (!tableInfo) return []
|
||||||
|
|
||||||
const model = $backendUiStore.models.find(m => m._id === modelInfo.modelId)
|
const table = $backendUiStore.tables.find(m => m._id === tableInfo.tableId)
|
||||||
parameters.modelId = modelInfo.modelId
|
parameters.tableId = tableInfo.tableId
|
||||||
return Object.keys(model.schema).map(k => ({
|
return Object.keys(table.schema).map(k => ({
|
||||||
name: k,
|
name: k,
|
||||||
type: model.schema[k].type,
|
type: table.schema[k].type,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
componentInstanceId: $store.currentComponentInfo._id,
|
componentInstanceId: $store.currentComponentInfo._id,
|
||||||
components: $store.components,
|
components: $store.components,
|
||||||
screen: $store.currentPreviewItem,
|
screen: $store.currentPreviewItem,
|
||||||
models: $backendUiStore.models,
|
tables: $backendUiStore.tables,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
<Select thin secondary wide on:change {value}>
|
<Select thin secondary wide on:change {value}>
|
||||||
<option value="" />
|
<option value="" />
|
||||||
{#each $backendUiStore.models as model}
|
{#each $backendUiStore.tables as table}
|
||||||
<option value={model._id}>{model.name}</option>
|
<option value={table._id}>{table.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
|
@ -7,18 +7,18 @@
|
||||||
export let value = ""
|
export let value = ""
|
||||||
export let onChange = (val = {})
|
export let onChange = (val = {})
|
||||||
|
|
||||||
const models = $backendUiStore.models
|
const tables = $backendUiStore.tables
|
||||||
|
|
||||||
let options = []
|
let options = []
|
||||||
|
|
||||||
$: model = componentInstance.datasource
|
$: table = componentInstance.datasource
|
||||||
? models.find(m => m._id === componentInstance.datasource.modelId)
|
? tables.find(m => m._id === componentInstance.datasource.tableId)
|
||||||
: null
|
: null
|
||||||
|
|
||||||
$: if (model) {
|
$: if (table) {
|
||||||
options = componentInstance.datasource.isModel
|
options = componentInstance.datasource.isTable
|
||||||
? Object.keys(model.schema)
|
? Object.keys(table.schema)
|
||||||
: Object.keys(model.views[componentInstance.datasource.name].schema)
|
: Object.keys(table.views[componentInstance.datasource.name].schema)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -13,14 +13,14 @@
|
||||||
dropdownRight.hide()
|
dropdownRight.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
const models = $backendUiStore.models.map(m => ({
|
const tables = $backendUiStore.tables.map(m => ({
|
||||||
label: m.name,
|
label: m.name,
|
||||||
name: `all_${m._id}`,
|
name: `all_${m._id}`,
|
||||||
modelId: m._id,
|
tableId: m._id,
|
||||||
isModel: true,
|
isTable: true,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const views = $backendUiStore.models.reduce((acc, cur) => {
|
const views = $backendUiStore.tables.reduce((acc, cur) => {
|
||||||
let viewsArr = Object.entries(cur.views).map(([key, value]) => ({
|
let viewsArr = Object.entries(cur.views).map(([key, value]) => ({
|
||||||
label: key,
|
label: key,
|
||||||
name: key,
|
name: key,
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
|
|
||||||
<div class="dropdownbutton" bind:this={anchorRight}>
|
<div class="dropdownbutton" bind:this={anchorRight}>
|
||||||
<Button secondary wide on:click={dropdownRight.show}>
|
<Button secondary wide on:click={dropdownRight.show}>
|
||||||
<span>{value.label ? value.label : 'Model / View'}</span>
|
<span>{value.label ? value.label : 'Table / View'}</span>
|
||||||
<Icon name="arrowdown" />
|
<Icon name="arrowdown" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -42,11 +42,11 @@
|
||||||
<Heading extraSmall>Tables</Heading>
|
<Heading extraSmall>Tables</Heading>
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
<ul>
|
||||||
{#each models as model}
|
{#each tables as table}
|
||||||
<li
|
<li
|
||||||
class:selected={value === model}
|
class:selected={value === table}
|
||||||
on:click={() => handleSelected(model)}>
|
on:click={() => handleSelected(table)}>
|
||||||
{model.label}
|
{table.label}
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
|
@ -20,7 +20,7 @@ export const TYPE_MAP = {
|
||||||
"##bbstate": "",
|
"##bbstate": "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
models: {
|
tables: {
|
||||||
default: {},
|
default: {},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import Input from "./PropertyPanelControls/Input.svelte"
|
import Input from "./PropertyPanelControls/Input.svelte"
|
||||||
import OptionSelect from "./OptionSelect.svelte"
|
import OptionSelect from "./OptionSelect.svelte"
|
||||||
import Checkbox from "../common/Checkbox.svelte"
|
import Checkbox from "../common/Checkbox.svelte"
|
||||||
import ModelSelect from "components/userInterface/ModelSelect.svelte"
|
import TableSelect from "components/userInterface/TableSelect.svelte"
|
||||||
import ModelViewSelect from "components/userInterface/ModelViewSelect.svelte"
|
import TableViewSelect from "components/userInterface/TableViewSelect.svelte"
|
||||||
import ModelViewFieldSelect from "components/userInterface/ModelViewFieldSelect.svelte"
|
import TableViewFieldSelect from "components/userInterface/TableViewFieldSelect.svelte"
|
||||||
import Event from "components/userInterface/EventsEditor/EventPropertyControl.svelte"
|
import Event from "components/userInterface/EventsEditor/EventPropertyControl.svelte"
|
||||||
import ScreenSelect from "components/userInterface/ScreenSelect.svelte"
|
import ScreenSelect from "components/userInterface/ScreenSelect.svelte"
|
||||||
import { IconSelect } from "components/userInterface/IconSelect"
|
import { IconSelect } from "components/userInterface/IconSelect"
|
||||||
|
@ -307,7 +307,7 @@ export default {
|
||||||
{
|
{
|
||||||
label: "Table",
|
label: "Table",
|
||||||
key: "datasource",
|
key: "datasource",
|
||||||
control: ModelViewSelect,
|
control: TableViewSelect,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -540,7 +540,7 @@ export default {
|
||||||
{
|
{
|
||||||
label: "Table",
|
label: "Table",
|
||||||
key: "datasource",
|
key: "datasource",
|
||||||
control: ModelViewSelect,
|
control: TableViewSelect,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Stripe Color",
|
label: "Stripe Color",
|
||||||
|
@ -566,7 +566,7 @@ export default {
|
||||||
control: Colorpicker,
|
control: Colorpicker,
|
||||||
defaultValue: "#FFFFFF",
|
defaultValue: "#FFFFFF",
|
||||||
},
|
},
|
||||||
{ label: "Table", key: "model", control: ModelSelect },
|
{ label: "Table", key: "table", control: TableSelect },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
children: [],
|
children: [],
|
||||||
|
@ -586,8 +586,8 @@ export default {
|
||||||
settings: [
|
settings: [
|
||||||
{
|
{
|
||||||
label: "Table",
|
label: "Table",
|
||||||
key: "model",
|
key: "table",
|
||||||
control: ModelSelect,
|
control: TableSelect,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Title",
|
label: "Title",
|
||||||
|
@ -611,8 +611,8 @@ export default {
|
||||||
settings: [
|
settings: [
|
||||||
{
|
{
|
||||||
label: "Table",
|
label: "Table",
|
||||||
key: "model",
|
key: "table",
|
||||||
control: ModelSelect,
|
control: TableSelect,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Title",
|
label: "Title",
|
||||||
|
@ -644,19 +644,19 @@ export default {
|
||||||
{
|
{
|
||||||
label: "Table",
|
label: "Table",
|
||||||
key: "datasource",
|
key: "datasource",
|
||||||
control: ModelViewSelect,
|
control: TableViewSelect,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Name Field",
|
label: "Name Field",
|
||||||
key: "nameKey",
|
key: "nameKey",
|
||||||
dependsOn: "datasource",
|
dependsOn: "datasource",
|
||||||
control: ModelViewFieldSelect,
|
control: TableViewFieldSelect,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Value Field",
|
label: "Value Field",
|
||||||
key: "valueKey",
|
key: "valueKey",
|
||||||
dependsOn: "datasource",
|
dependsOn: "datasource",
|
||||||
control: ModelViewFieldSelect,
|
control: TableViewFieldSelect,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Animate Chart",
|
label: "Animate Chart",
|
||||||
|
@ -738,19 +738,19 @@ export default {
|
||||||
{
|
{
|
||||||
label: "Table",
|
label: "Table",
|
||||||
key: "datasource",
|
key: "datasource",
|
||||||
control: ModelViewSelect,
|
control: TableViewSelect,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Name Label",
|
label: "Name Label",
|
||||||
key: "nameLabel",
|
key: "nameLabel",
|
||||||
dependsOn: "datasource",
|
dependsOn: "datasource",
|
||||||
control: ModelViewFieldSelect,
|
control: TableViewFieldSelect,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Value Label",
|
label: "Value Label",
|
||||||
key: "valueLabel",
|
key: "valueLabel",
|
||||||
dependsOn: "datasource",
|
dependsOn: "datasource",
|
||||||
control: ModelViewFieldSelect,
|
control: TableViewFieldSelect,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Y Axis Label",
|
label: "Y Axis Label",
|
||||||
|
@ -852,25 +852,25 @@ export default {
|
||||||
{
|
{
|
||||||
label: "Table",
|
label: "Table",
|
||||||
key: "datasource",
|
key: "datasource",
|
||||||
control: ModelViewSelect,
|
control: TableViewSelect,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Name Label",
|
label: "Name Label",
|
||||||
key: "nameLabel",
|
key: "nameLabel",
|
||||||
dependsOn: "datasource",
|
dependsOn: "datasource",
|
||||||
control: ModelViewFieldSelect,
|
control: TableViewFieldSelect,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Group Label",
|
label: "Group Label",
|
||||||
key: "groupLabel",
|
key: "groupLabel",
|
||||||
dependsOn: "datasource",
|
dependsOn: "datasource",
|
||||||
control: ModelViewFieldSelect,
|
control: TableViewFieldSelect,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Value Label",
|
label: "Value Label",
|
||||||
key: "valueLabel",
|
key: "valueLabel",
|
||||||
dependsOn: "datasource",
|
dependsOn: "datasource",
|
||||||
control: ModelViewFieldSelect,
|
control: TableViewFieldSelect,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Color",
|
label: "Color",
|
||||||
|
@ -955,25 +955,25 @@ export default {
|
||||||
{
|
{
|
||||||
label: "Table",
|
label: "Table",
|
||||||
key: "datasource",
|
key: "datasource",
|
||||||
control: ModelViewSelect,
|
control: TableViewSelect,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Value Label",
|
label: "Value Label",
|
||||||
key: "valueLabel",
|
key: "valueLabel",
|
||||||
dependsOn: "datasource",
|
dependsOn: "datasource",
|
||||||
control: ModelViewFieldSelect,
|
control: TableViewFieldSelect,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Topic Label",
|
label: "Topic Label",
|
||||||
key: "topicLabel",
|
key: "topicLabel",
|
||||||
dependsOn: "datasource",
|
dependsOn: "datasource",
|
||||||
control: ModelViewFieldSelect,
|
control: TableViewFieldSelect,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Date Label",
|
label: "Date Label",
|
||||||
key: "dateLabel",
|
key: "dateLabel",
|
||||||
dependsOn: "datasource",
|
dependsOn: "datasource",
|
||||||
control: ModelViewFieldSelect,
|
control: TableViewFieldSelect,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Colors",
|
label: "Colors",
|
||||||
|
@ -1120,7 +1120,7 @@ export default {
|
||||||
// icon: "ri-file-list-line",
|
// icon: "ri-file-list-line",
|
||||||
// properties: {
|
// properties: {
|
||||||
// design: { ...all },
|
// design: { ...all },
|
||||||
// settings: [{ label: "Table", key: "model", control: ModelSelect }],
|
// settings: [{ label: "Table", key: "table", control: TableSelect }],
|
||||||
// },
|
// },
|
||||||
// children: [],
|
// children: [],
|
||||||
// },
|
// },
|
||||||
|
@ -1132,7 +1132,7 @@ export default {
|
||||||
icon: "ri-profile-line",
|
icon: "ri-profile-line",
|
||||||
properties: {
|
properties: {
|
||||||
design: { ...all },
|
design: { ...all },
|
||||||
settings: [{ label: "Table", key: "model", control: ModelSelect }],
|
settings: [{ label: "Table", key: "table", control: TableSelect }],
|
||||||
},
|
},
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { store, backendUiStore } from "builderStore"
|
||||||
import * as api from "components/backend/DataTable/api"
|
import * as api from "components/backend/DataTable/api"
|
||||||
import ModelNavigator from "components/backend/ModelNavigator/ModelNavigator.svelte"
|
import TableNavigator from "components/backend/TableNavigator/TableNavigator.svelte"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- routify:options index=1 -->
|
<!-- routify:options index=1 -->
|
||||||
<div class="root">
|
<div class="root">
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
<ModelNavigator />
|
<TableNavigator />
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<slot />
|
<slot />
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
$goto("../model")
|
$goto("../table")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- routify:options index=false -->
|
<!-- routify:options index=false -->
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
<script>
|
|
||||||
import { params } from "@sveltech/routify"
|
|
||||||
import { backendUiStore } from "builderStore"
|
|
||||||
|
|
||||||
if ($params.selectedModel) {
|
|
||||||
const model = $backendUiStore.models.find(
|
|
||||||
m => m._id === $params.selectedModel
|
|
||||||
)
|
|
||||||
if (model) {
|
|
||||||
backendUiStore.actions.models.select(model)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<slot />
|
|
|
@ -1,32 +0,0 @@
|
||||||
<script>
|
|
||||||
import { backendUiStore } from "builderStore"
|
|
||||||
import { goto, leftover } from "@sveltech/routify"
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
|
|
||||||
async function selectModel(model) {
|
|
||||||
backendUiStore.actions.models.select(model)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
// navigate to first model in list, if not already selected
|
|
||||||
// and this is the final url (i.e. no selectedModel)
|
|
||||||
if (
|
|
||||||
!$leftover &&
|
|
||||||
$backendUiStore.models.length > 0 &&
|
|
||||||
(!$backendUiStore.selectedModel || !$backendUiStore.selectedModel._id)
|
|
||||||
) {
|
|
||||||
$goto(`./${$backendUiStore.models[0]._id}`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="root">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.root {
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,35 +0,0 @@
|
||||||
<script>
|
|
||||||
import { store, backendUiStore } from "builderStore"
|
|
||||||
import { goto, leftover } from "@sveltech/routify"
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
|
|
||||||
async function selectModel(model) {
|
|
||||||
backendUiStore.actions.models.select(model)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
// navigate to first model in list, if not already selected
|
|
||||||
// and this is the final url (i.e. no selectedModel)
|
|
||||||
if (
|
|
||||||
!$leftover &&
|
|
||||||
$backendUiStore.models.length > 0 &&
|
|
||||||
(!$backendUiStore.selectedModel || !$backendUiStore.selectedModel._id)
|
|
||||||
) {
|
|
||||||
// this file routes as .../models/index, so, go up one.
|
|
||||||
$goto(`../${$backendUiStore.models[0]._id}`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if $backendUiStore.models.length === 0}
|
|
||||||
<i>Create your first table to start building</i>
|
|
||||||
{:else}
|
|
||||||
<i>Select a table to edit</i>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
i {
|
|
||||||
font-size: var(--font-size-xl);
|
|
||||||
color: var(--grey-4);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
<script>
|
||||||
|
import { params } from "@sveltech/routify"
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
|
||||||
|
if ($params.selectedTable) {
|
||||||
|
const table = $backendUiStore.tables.find(
|
||||||
|
m => m._id === $params.selectedTable
|
||||||
|
)
|
||||||
|
if (table) {
|
||||||
|
backendUiStore.actions.tables.select(table)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<slot />
|
|
@ -1,12 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import ModelDataTable from "components/backend/DataTable/ModelDataTable.svelte"
|
import TableDataTable from "components/backend/DataTable/DataTable.svelte"
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
|
|
||||||
$: selectedModel = $backendUiStore.selectedModel
|
$: selectedTable = $backendUiStore.selectedTable
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $backendUiStore.selectedDatabase._id && selectedModel.name}
|
{#if $backendUiStore.selectedDatabase._id && selectedTable.name}
|
||||||
<ModelDataTable />
|
<TableDataTable />
|
||||||
{:else}
|
{:else}
|
||||||
<i>Create your first table to start building</i>
|
<i>Create your first table to start building</i>
|
||||||
{/if}
|
{/if}
|
|
@ -4,6 +4,6 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<RelationshipDataTable
|
<RelationshipDataTable
|
||||||
modelId={$params.selectedModel}
|
tableId={$params.selectedTable}
|
||||||
recordId={$params.selectedRecord}
|
recordId={$params.selectedRecord}
|
||||||
fieldName={decodeURI($params.selectedField)} />
|
fieldName={decodeURI($params.selectedField)} />
|
|
@ -0,0 +1,32 @@
|
||||||
|
<script>
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
import { goto, leftover } from "@sveltech/routify"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
|
async function selectTable(table) {
|
||||||
|
backendUiStore.actions.tables.select(table)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
// navigate to first table in list, if not already selected
|
||||||
|
// and this is the final url (i.e. no selectedTable)
|
||||||
|
if (
|
||||||
|
!$leftover &&
|
||||||
|
$backendUiStore.tables.length > 0 &&
|
||||||
|
(!$backendUiStore.selectedTable || !$backendUiStore.selectedTable._id)
|
||||||
|
) {
|
||||||
|
$goto(`./${$backendUiStore.tables[0]._id}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="root">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.root {
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,35 @@
|
||||||
|
<script>
|
||||||
|
import { store, backendUiStore } from "builderStore"
|
||||||
|
import { goto, leftover } from "@sveltech/routify"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
|
async function selectTable(table) {
|
||||||
|
backendUiStore.actions.tables.select(table)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
// navigate to first table in list, if not already selected
|
||||||
|
// and this is the final url (i.e. no selectedTable)
|
||||||
|
if (
|
||||||
|
!$leftover &&
|
||||||
|
$backendUiStore.tables.length > 0 &&
|
||||||
|
(!$backendUiStore.selectedTable || !$backendUiStore.selectedTable._id)
|
||||||
|
) {
|
||||||
|
// this file routes as .../tables/index, so, go up one.
|
||||||
|
$goto(`../${$backendUiStore.tables[0]._id}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $backendUiStore.tables.length === 0}
|
||||||
|
<i>Create your first table to start building</i>
|
||||||
|
{:else}
|
||||||
|
<i>Select a table to edit</i>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
i {
|
||||||
|
font-size: var(--font-size-xl);
|
||||||
|
color: var(--grey-4);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -5,9 +5,9 @@
|
||||||
if ($params.selectedView) {
|
if ($params.selectedView) {
|
||||||
let view
|
let view
|
||||||
const viewName = decodeURI($params.selectedView)
|
const viewName = decodeURI($params.selectedView)
|
||||||
for (let model of $backendUiStore.models) {
|
for (let table of $backendUiStore.tables) {
|
||||||
if (model.views && model.views[viewName]) {
|
if (table.views && table.views[viewName]) {
|
||||||
view = model.views[viewName]
|
view = table.views[viewName]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (view) {
|
if (view) {
|
||||||
|
|
|
@ -22,7 +22,7 @@ describe("fetch bindable properties", () => {
|
||||||
expect(componentBinding).not.toBeDefined()
|
expect(componentBinding).not.toBeDefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should return model schema, when inside a context", () => {
|
it("should return table schema, when inside a context", () => {
|
||||||
const result = fetchBindableProperties({
|
const result = fetchBindableProperties({
|
||||||
componentInstanceId: "list-item-input-id",
|
componentInstanceId: "list-item-input-id",
|
||||||
...testData()
|
...testData()
|
||||||
|
@ -44,13 +44,13 @@ describe("fetch bindable properties", () => {
|
||||||
expect(idbinding.readableBinding).toBe("list-name.Test Model._id")
|
expect(idbinding.readableBinding).toBe("list-name.Test Model._id")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should return model schema, for grantparent context", () => {
|
it("should return table schema, for grantparent context", () => {
|
||||||
const result = fetchBindableProperties({
|
const result = fetchBindableProperties({
|
||||||
componentInstanceId: "child-list-item-input-id",
|
componentInstanceId: "child-list-item-input-id",
|
||||||
...testData()
|
...testData()
|
||||||
})
|
})
|
||||||
const contextBindings = result.filter(r => r.type==="context")
|
const contextBindings = result.filter(r => r.type==="context")
|
||||||
// 2 fields + _id + _rev ... x 2 models
|
// 2 fields + _id + _rev ... x 2 tables
|
||||||
expect(contextBindings.length).toBe(8)
|
expect(contextBindings.length).toBe(8)
|
||||||
|
|
||||||
const namebinding_parent = contextBindings.find(b => b.runtimeBinding === "parent.data.name")
|
const namebinding_parent = contextBindings.find(b => b.runtimeBinding === "parent.data.name")
|
||||||
|
@ -126,7 +126,7 @@ const testData = () => {
|
||||||
_id: "list-id",
|
_id: "list-id",
|
||||||
_component: "@budibase/standard-components/list",
|
_component: "@budibase/standard-components/list",
|
||||||
_instanceName: "list-name",
|
_instanceName: "list-name",
|
||||||
model: { isModel: true, modelId: "test-model-id", label: "Test Model", name: "all_test-model-id" },
|
table: { isTable: true, tableId: "test-table-id", label: "Test Model", name: "all_test-table-id" },
|
||||||
_children: [
|
_children: [
|
||||||
{
|
{
|
||||||
_id: "list-item-heading-id",
|
_id: "list-item-heading-id",
|
||||||
|
@ -144,7 +144,7 @@ const testData = () => {
|
||||||
_id: "child-list-id",
|
_id: "child-list-id",
|
||||||
_component: "@budibase/standard-components/list",
|
_component: "@budibase/standard-components/list",
|
||||||
_instanceName: "child-list-name",
|
_instanceName: "child-list-name",
|
||||||
model: { isModel: true, modelId: "test-model-id", label: "Test Model", name: "all_test-model-id"},
|
table: { isTable: true, tableId: "test-table-id", label: "Test Model", name: "all_test-table-id"},
|
||||||
_children: [
|
_children: [
|
||||||
{
|
{
|
||||||
_id: "child-list-item-heading-id",
|
_id: "child-list-item-heading-id",
|
||||||
|
@ -166,8 +166,8 @@ const testData = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const models = [{
|
const tables = [{
|
||||||
_id: "test-model-id",
|
_id: "test-table-id",
|
||||||
name: "Test Model",
|
name: "Test Model",
|
||||||
schema: {
|
schema: {
|
||||||
name: {
|
name: {
|
||||||
|
@ -184,9 +184,9 @@ const testData = () => {
|
||||||
props: {},
|
props: {},
|
||||||
},
|
},
|
||||||
"@budibase/standard-components/list" : {
|
"@budibase/standard-components/list" : {
|
||||||
context: "model",
|
context: "table",
|
||||||
props: {
|
props: {
|
||||||
model: "string"
|
table: "string"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"@budibase/standard-components/input" : {
|
"@budibase/standard-components/input" : {
|
||||||
|
@ -202,6 +202,6 @@ const testData = () => {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return { screen, models, components }
|
return { screen, tables, components }
|
||||||
|
|
||||||
}
|
}
|
|
@ -54,7 +54,7 @@ const apiOpts = {
|
||||||
|
|
||||||
const createRecord = async params =>
|
const createRecord = async params =>
|
||||||
await post({
|
await post({
|
||||||
url: `/api/${params.modelId}/records`,
|
url: `/api/${params.tableId}/records`,
|
||||||
body: makeRecordRequestBody(params),
|
body: makeRecordRequestBody(params),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ const updateRecord = async params => {
|
||||||
const record = makeRecordRequestBody(params)
|
const record = makeRecordRequestBody(params)
|
||||||
record._id = params._id
|
record._id = params._id
|
||||||
await patch({
|
await patch({
|
||||||
url: `/api/${params.modelId}/records/${params._id}`,
|
url: `/api/${params.tableId}/records/${params._id}`,
|
||||||
body: record,
|
body: record,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,7 @@
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Jest - Models",
|
"name": "Jest - Models",
|
||||||
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
"program": "${workspaceFolder}/node_modules/.bin/jest",
|
||||||
"args": ["model.spec", "--runInBand"],
|
"args": ["table.spec", "--runInBand"],
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"internalConsoleOptions": "neverOpen",
|
"internalConsoleOptions": "neverOpen",
|
||||||
"disableOptimisticBPs": true,
|
"disableOptimisticBPs": true,
|
||||||
|
|
|
@ -9,7 +9,7 @@ const {
|
||||||
} = require("../../db/utils")
|
} = require("../../db/utils")
|
||||||
const { cloneDeep } = require("lodash")
|
const { cloneDeep } = require("lodash")
|
||||||
|
|
||||||
const MODEL_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.MODEL}${SEPARATOR}`
|
const TABLE_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.TABLE}${SEPARATOR}`
|
||||||
|
|
||||||
validateJs.extend(validateJs.validators.datetime, {
|
validateJs.extend(validateJs.validators.datetime, {
|
||||||
parse: function(value) {
|
parse: function(value) {
|
||||||
|
@ -25,18 +25,18 @@ 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 record = await db.get(ctx.params.id)
|
||||||
const model = await db.get(record.modelId)
|
const table = await db.get(record.tableId)
|
||||||
const patchfields = ctx.request.body
|
const patchfields = ctx.request.body
|
||||||
record = coerceRecordValues(record, model)
|
record = coerceRecordValues(record, table)
|
||||||
|
|
||||||
for (let key of Object.keys(patchfields)) {
|
for (let key of Object.keys(patchfields)) {
|
||||||
if (!model.schema[key]) continue
|
if (!table.schema[key]) continue
|
||||||
record[key] = patchfields[key]
|
record[key] = patchfields[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
const validateResult = await validate({
|
const validateResult = await validate({
|
||||||
record,
|
record,
|
||||||
model,
|
table,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!validateResult.valid) {
|
if (!validateResult.valid) {
|
||||||
|
@ -53,40 +53,40 @@ exports.patch = async function(ctx) {
|
||||||
instanceId,
|
instanceId,
|
||||||
eventType: linkRecords.EventType.RECORD_UPDATE,
|
eventType: linkRecords.EventType.RECORD_UPDATE,
|
||||||
record,
|
record,
|
||||||
modelId: record.modelId,
|
tableId: record.tableId,
|
||||||
model,
|
table,
|
||||||
})
|
})
|
||||||
const response = await db.put(record)
|
const response = await db.put(record)
|
||||||
record._rev = response.rev
|
record._rev = response.rev
|
||||||
record.type = "record"
|
record.type = "record"
|
||||||
|
|
||||||
ctx.eventEmitter &&
|
ctx.eventEmitter &&
|
||||||
ctx.eventEmitter.emitRecord(`record:update`, instanceId, record, model)
|
ctx.eventEmitter.emitRecord(`record:update`, instanceId, record, table)
|
||||||
ctx.body = record
|
ctx.body = record
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.message = `${model.name} updated successfully.`
|
ctx.message = `${table.name} updated successfully.`
|
||||||
}
|
}
|
||||||
|
|
||||||
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 record = ctx.request.body
|
||||||
record.modelId = ctx.params.modelId
|
record.tableId = ctx.params.tableId
|
||||||
|
|
||||||
if (!record._rev && !record._id) {
|
if (!record._rev && !record._id) {
|
||||||
record._id = generateRecordID(record.modelId)
|
record._id = generateRecordID(record.tableId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the record obj had an _id then it will have been retrieved
|
// if the record obj had an _id then it will have been retrieved
|
||||||
const existingRecord = ctx.preExisting
|
const existingRecord = ctx.preExisting
|
||||||
|
|
||||||
const model = await db.get(record.modelId)
|
const table = await db.get(record.tableId)
|
||||||
|
|
||||||
record = coerceRecordValues(record, model)
|
record = coerceRecordValues(record, table)
|
||||||
|
|
||||||
const validateResult = await validate({
|
const validateResult = await validate({
|
||||||
record,
|
record,
|
||||||
model,
|
table,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!validateResult.valid) {
|
if (!validateResult.valid) {
|
||||||
|
@ -103,8 +103,8 @@ exports.save = async function(ctx) {
|
||||||
instanceId,
|
instanceId,
|
||||||
eventType: linkRecords.EventType.RECORD_SAVE,
|
eventType: linkRecords.EventType.RECORD_SAVE,
|
||||||
record,
|
record,
|
||||||
modelId: record.modelId,
|
tableId: record.tableId,
|
||||||
model,
|
table,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (existingRecord) {
|
if (existingRecord) {
|
||||||
|
@ -113,7 +113,7 @@ exports.save = async function(ctx) {
|
||||||
record.type = "record"
|
record.type = "record"
|
||||||
ctx.body = record
|
ctx.body = record
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.message = `${model.name} updated successfully.`
|
ctx.message = `${table.name} updated successfully.`
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,10 +122,10 @@ exports.save = async function(ctx) {
|
||||||
record._rev = response.rev
|
record._rev = response.rev
|
||||||
|
|
||||||
ctx.eventEmitter &&
|
ctx.eventEmitter &&
|
||||||
ctx.eventEmitter.emitRecord(`record:save`, instanceId, record, model)
|
ctx.eventEmitter.emitRecord(`record:save`, instanceId, record, table)
|
||||||
ctx.body = record
|
ctx.body = record
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.message = `${model.name} created successfully`
|
ctx.message = `${table.name} created successfully`
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetchView = async function(ctx) {
|
exports.fetchView = async function(ctx) {
|
||||||
|
@ -134,10 +134,10 @@ exports.fetchView = async function(ctx) {
|
||||||
const { stats, group, field } = ctx.query
|
const { stats, group, field } = ctx.query
|
||||||
const viewName = ctx.params.viewName
|
const viewName = ctx.params.viewName
|
||||||
|
|
||||||
// if this is a model view being looked for just transfer to that
|
// if this is a table view being looked for just transfer to that
|
||||||
if (viewName.indexOf(MODEL_VIEW_BEGINS_WITH) === 0) {
|
if (viewName.indexOf(TABLE_VIEW_BEGINS_WITH) === 0) {
|
||||||
ctx.params.modelId = viewName.substring(4)
|
ctx.params.tableId = viewName.substring(4)
|
||||||
await exports.fetchModelRecords(ctx)
|
await exports.fetchTableRecords(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,11 +160,11 @@ exports.fetchView = async function(ctx) {
|
||||||
ctx.body = await linkRecords.attachLinkInfo(instanceId, response.rows)
|
ctx.body = await linkRecords.attachLinkInfo(instanceId, response.rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetchModelRecords = async function(ctx) {
|
exports.fetchTableRecords = 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.modelId, null, {
|
getRecordParams(ctx.params.tableId, null, {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -192,8 +192,8 @@ 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 record = await db.get(ctx.params.recordId)
|
||||||
if (record.modelId !== ctx.params.modelId) {
|
if (record.tableId !== ctx.params.tableId) {
|
||||||
ctx.throw(400, "Supplied modelId does not match the records modelId")
|
ctx.throw(400, "Supplied tableId does not match the records tableId")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.body = await linkRecords.attachLinkInfo(instanceId, record)
|
ctx.body = await linkRecords.attachLinkInfo(instanceId, record)
|
||||||
|
@ -203,15 +203,15 @@ 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 record = await db.get(ctx.params.recordId)
|
||||||
if (record.modelId !== ctx.params.modelId) {
|
if (record.tableId !== ctx.params.tableId) {
|
||||||
ctx.throw(400, "Supplied modelId doesn't match the record's modelId")
|
ctx.throw(400, "Supplied tableId doesn't match the record's tableId")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await linkRecords.updateLinks({
|
await linkRecords.updateLinks({
|
||||||
instanceId,
|
instanceId,
|
||||||
eventType: linkRecords.EventType.RECORD_DELETE,
|
eventType: linkRecords.EventType.RECORD_DELETE,
|
||||||
record,
|
record,
|
||||||
modelId: record.modelId,
|
tableId: record.tableId,
|
||||||
})
|
})
|
||||||
ctx.body = await db.remove(ctx.params.recordId, ctx.params.revId)
|
ctx.body = await db.remove(ctx.params.recordId, ctx.params.revId)
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
|
@ -225,23 +225,23 @@ exports.destroy = async function(ctx) {
|
||||||
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,
|
||||||
modelId: ctx.params.modelId,
|
tableId: ctx.params.tableId,
|
||||||
record: ctx.request.body,
|
record: ctx.request.body,
|
||||||
})
|
})
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.body = errors
|
ctx.body = errors
|
||||||
}
|
}
|
||||||
|
|
||||||
async function validate({ instanceId, modelId, record, model }) {
|
async function validate({ instanceId, tableId, record, table }) {
|
||||||
if (!model) {
|
if (!table) {
|
||||||
const db = new CouchDB(instanceId)
|
const db = new CouchDB(instanceId)
|
||||||
model = await db.get(modelId)
|
table = await db.get(tableId)
|
||||||
}
|
}
|
||||||
const errors = {}
|
const errors = {}
|
||||||
for (let fieldName of Object.keys(model.schema)) {
|
for (let fieldName of Object.keys(table.schema)) {
|
||||||
const res = validateJs.single(
|
const res = validateJs.single(
|
||||||
record[fieldName],
|
record[fieldName],
|
||||||
model.schema[fieldName].constraints
|
table.schema[fieldName].constraints
|
||||||
)
|
)
|
||||||
if (res) errors[fieldName] = res
|
if (res) errors[fieldName] = res
|
||||||
}
|
}
|
||||||
|
@ -251,9 +251,9 @@ async function validate({ instanceId, modelId, record, model }) {
|
||||||
exports.fetchEnrichedRecord = async function(ctx) {
|
exports.fetchEnrichedRecord = async function(ctx) {
|
||||||
const instanceId = ctx.user.instanceId
|
const instanceId = ctx.user.instanceId
|
||||||
const db = new CouchDB(instanceId)
|
const db = new CouchDB(instanceId)
|
||||||
const modelId = ctx.params.modelId
|
const tableId = ctx.params.tableId
|
||||||
const recordId = ctx.params.recordId
|
const recordId = ctx.params.recordId
|
||||||
if (instanceId == null || modelId == null || recordId == null) {
|
if (instanceId == null || tableId == null || recordId == null) {
|
||||||
ctx.status = 400
|
ctx.status = 400
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
status: 400,
|
status: 400,
|
||||||
|
@ -262,12 +262,12 @@ exports.fetchEnrichedRecord = async function(ctx) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// need model to work out where links go in record
|
// need table to work out where links go in record
|
||||||
const [model, record] = await Promise.all([db.get(modelId), db.get(recordId)])
|
const [table, record] = await Promise.all([db.get(tableId), db.get(recordId)])
|
||||||
// get the link docs
|
// get the link docs
|
||||||
const linkVals = await linkRecords.getLinkDocuments({
|
const linkVals = await linkRecords.getLinkDocuments({
|
||||||
instanceId,
|
instanceId,
|
||||||
modelId,
|
tableId,
|
||||||
recordId,
|
recordId,
|
||||||
})
|
})
|
||||||
// look up the actual records based on the ids
|
// look up the actual records based on the ids
|
||||||
|
@ -281,11 +281,11 @@ exports.fetchEnrichedRecord = async function(ctx) {
|
||||||
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 records in the correct place throughout the main record
|
||||||
for (let fieldName of Object.keys(model.schema)) {
|
for (let fieldName of Object.keys(table.schema)) {
|
||||||
let field = model.schema[fieldName]
|
let field = table.schema[fieldName]
|
||||||
if (field.type === "link") {
|
if (field.type === "link") {
|
||||||
record[fieldName] = linkedRecords.filter(
|
record[fieldName] = linkedRecords.filter(
|
||||||
linkRecord => linkRecord.modelId === field.modelId
|
linkRecord => linkRecord.tableId === field.tableId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -293,10 +293,10 @@ exports.fetchEnrichedRecord = async function(ctx) {
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
}
|
}
|
||||||
|
|
||||||
function coerceRecordValues(rec, model) {
|
function coerceRecordValues(rec, table) {
|
||||||
const record = cloneDeep(rec)
|
const record = cloneDeep(rec)
|
||||||
for (let [key, value] of Object.entries(record)) {
|
for (let [key, value] of Object.entries(record)) {
|
||||||
const field = model.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
|
||||||
|
|
|
@ -3,15 +3,15 @@ const linkRecords = require("../../db/linkedRecords")
|
||||||
const csvParser = require("../../utilities/csvParser")
|
const csvParser = require("../../utilities/csvParser")
|
||||||
const {
|
const {
|
||||||
getRecordParams,
|
getRecordParams,
|
||||||
getModelParams,
|
getTableParams,
|
||||||
generateModelID,
|
generateTableID,
|
||||||
generateRecordID,
|
generateRecordID,
|
||||||
} = require("../../db/utils")
|
} = require("../../db/utils")
|
||||||
|
|
||||||
exports.fetch = async function(ctx) {
|
exports.fetch = async function(ctx) {
|
||||||
const db = new CouchDB(ctx.user.instanceId)
|
const db = new CouchDB(ctx.user.instanceId)
|
||||||
const body = await db.allDocs(
|
const body = await db.allDocs(
|
||||||
getModelParams(null, {
|
getTableParams(null, {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -27,25 +27,25 @@ 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)
|
||||||
const { dataImport, ...rest } = ctx.request.body
|
const { dataImport, ...rest } = ctx.request.body
|
||||||
const modelToSave = {
|
const tableToSave = {
|
||||||
type: "model",
|
type: "table",
|
||||||
_id: generateModelID(),
|
_id: generateTableID(),
|
||||||
views: {},
|
views: {},
|
||||||
...rest,
|
...rest,
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the model obj had an _id then it will have been retrieved
|
// if the table obj had an _id then it will have been retrieved
|
||||||
const oldModel = ctx.preExisting
|
const oldTable = ctx.preExisting
|
||||||
|
|
||||||
// rename record fields when table column is renamed
|
// rename record fields when table column is renamed
|
||||||
const { _rename } = modelToSave
|
const { _rename } = tableToSave
|
||||||
if (_rename && modelToSave.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 && modelToSave.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 records = await db.allDocs(
|
||||||
getRecordParams(modelToSave._id, null, {
|
getRecordParams(tableToSave._id, null, {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -57,62 +57,62 @@ exports.save = async function(ctx) {
|
||||||
})
|
})
|
||||||
|
|
||||||
await db.bulkDocs(docs)
|
await db.bulkDocs(docs)
|
||||||
delete modelToSave._rename
|
delete tableToSave._rename
|
||||||
}
|
}
|
||||||
|
|
||||||
// update schema of non-statistics views when new columns are added
|
// update schema of non-statistics views when new columns are added
|
||||||
for (let view in modelToSave.views) {
|
for (let view in tableToSave.views) {
|
||||||
const modelView = modelToSave.views[view]
|
const tableView = tableToSave.views[view]
|
||||||
if (!modelView) continue
|
if (!tableView) continue
|
||||||
|
|
||||||
if (modelView.schema.group || modelView.schema.field) continue
|
if (tableView.schema.group || tableView.schema.field) continue
|
||||||
modelView.schema = modelToSave.schema
|
tableView.schema = tableToSave.schema
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await db.post(modelToSave)
|
const result = await db.post(tableToSave)
|
||||||
modelToSave._rev = result.rev
|
tableToSave._rev = result.rev
|
||||||
|
|
||||||
// update linked records
|
// update linked records
|
||||||
await linkRecords.updateLinks({
|
await linkRecords.updateLinks({
|
||||||
instanceId,
|
instanceId,
|
||||||
eventType: oldModel
|
eventType: oldTable
|
||||||
? linkRecords.EventType.MODEL_UPDATED
|
? linkRecords.EventType.TABLE_UPDATED
|
||||||
: linkRecords.EventType.MODEL_SAVE,
|
: linkRecords.EventType.TABLE_SAVE,
|
||||||
model: modelToSave,
|
table: tableToSave,
|
||||||
oldModel: oldModel,
|
oldTable: oldTable,
|
||||||
})
|
})
|
||||||
|
|
||||||
ctx.eventEmitter &&
|
ctx.eventEmitter &&
|
||||||
ctx.eventEmitter.emitModel(`model:save`, instanceId, modelToSave)
|
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 records 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(modelToSave._id)
|
row._id = generateRecordID(tableToSave._id)
|
||||||
row.modelId = modelToSave._id
|
row.tableId = tableToSave._id
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.bulkDocs(data)
|
await db.bulkDocs(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.message = `Model ${ctx.request.body.name} saved successfully.`
|
ctx.message = `Table ${ctx.request.body.name} saved successfully.`
|
||||||
ctx.body = modelToSave
|
ctx.body = tableToSave
|
||||||
}
|
}
|
||||||
|
|
||||||
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 modelToDelete = await db.get(ctx.params.modelId)
|
const tableToDelete = await db.get(ctx.params.tableId)
|
||||||
|
|
||||||
await db.remove(modelToDelete)
|
await db.remove(tableToDelete)
|
||||||
|
|
||||||
// Delete all records for that model
|
// Delete all records for that table
|
||||||
const records = await db.allDocs(
|
const records = await db.allDocs(
|
||||||
getRecordParams(ctx.params.modelId, null, {
|
getRecordParams(ctx.params.tableId, null, {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -123,14 +123,14 @@ exports.destroy = async function(ctx) {
|
||||||
// update linked records
|
// update linked records
|
||||||
await linkRecords.updateLinks({
|
await linkRecords.updateLinks({
|
||||||
instanceId,
|
instanceId,
|
||||||
eventType: linkRecords.EventType.MODEL_DELETE,
|
eventType: linkRecords.EventType.TABLE_DELETE,
|
||||||
model: modelToDelete,
|
table: tableToDelete,
|
||||||
})
|
})
|
||||||
|
|
||||||
ctx.eventEmitter &&
|
ctx.eventEmitter &&
|
||||||
ctx.eventEmitter.emitModel(`model:delete`, instanceId, modelToDelete)
|
ctx.eventEmitter.emitTable(`table:delete`, instanceId, tableToDelete)
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.message = `Model ${ctx.params.modelId} deleted.`
|
ctx.message = `Table ${ctx.params.tableId} deleted.`
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.validateCSVSchema = async function(ctx) {
|
exports.validateCSVSchema = async function(ctx) {
|
|
@ -45,21 +45,21 @@ const controller = {
|
||||||
|
|
||||||
await db.put(designDoc)
|
await db.put(designDoc)
|
||||||
|
|
||||||
// add views to model document
|
// add views to table document
|
||||||
const model = await db.get(ctx.request.body.modelId)
|
const table = await db.get(ctx.request.body.tableId)
|
||||||
if (!model.views) model.views = {}
|
if (!table.views) table.views = {}
|
||||||
if (!view.meta.schema) {
|
if (!view.meta.schema) {
|
||||||
view.meta.schema = model.schema
|
view.meta.schema = table.schema
|
||||||
}
|
}
|
||||||
model.views[viewToSave.name] = view.meta
|
table.views[viewToSave.name] = view.meta
|
||||||
|
|
||||||
if (originalName) {
|
if (originalName) {
|
||||||
delete model.views[originalName]
|
delete table.views[originalName]
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.put(model)
|
await db.put(table)
|
||||||
|
|
||||||
ctx.body = model.views[viewToSave.name]
|
ctx.body = table.views[viewToSave.name]
|
||||||
ctx.message = `View ${viewToSave.name} saved successfully.`
|
ctx.message = `View ${viewToSave.name} saved successfully.`
|
||||||
},
|
},
|
||||||
destroy: async ctx => {
|
destroy: async ctx => {
|
||||||
|
@ -74,9 +74,9 @@ const controller = {
|
||||||
|
|
||||||
await db.put(designDoc)
|
await db.put(designDoc)
|
||||||
|
|
||||||
const model = await db.get(view.meta.modelId)
|
const table = await db.get(view.meta.tableId)
|
||||||
delete model.views[viewName]
|
delete table.views[viewName]
|
||||||
await db.put(model)
|
await db.put(table)
|
||||||
|
|
||||||
ctx.body = view
|
ctx.body = view
|
||||||
ctx.message = `View ${ctx.params.viewName} saved successfully.`
|
ctx.message = `View ${ctx.params.viewName} saved successfully.`
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
exports[`viewBuilder Calculate creates a view with the calculation statistics schema 1`] = `
|
exports[`viewBuilder Calculate creates a view with the calculation statistics schema 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"map": "function (doc) {
|
"map": "function (doc) {
|
||||||
if (doc.modelId === \\"14f1c4e94d6a47b682ce89d35d4c78b0\\" ) {
|
if (doc.tableId === \\"14f1c4e94d6a47b682ce89d35d4c78b0\\" ) {
|
||||||
emit(doc[\\"_id\\"], doc[\\"myField\\"]);
|
emit(doc[\\"_id\\"], doc[\\"myField\\"]);
|
||||||
}
|
}
|
||||||
}",
|
}",
|
||||||
|
@ -12,7 +12,7 @@ Object {
|
||||||
"field": "myField",
|
"field": "myField",
|
||||||
"filters": Array [],
|
"filters": Array [],
|
||||||
"groupBy": undefined,
|
"groupBy": undefined,
|
||||||
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
"tableId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
||||||
"schema": Object {
|
"schema": Object {
|
||||||
"avg": Object {
|
"avg": Object {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
@ -44,7 +44,7 @@ Object {
|
||||||
exports[`viewBuilder Filter creates a view with multiple filters and conjunctions 1`] = `
|
exports[`viewBuilder Filter creates a view with multiple filters and conjunctions 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"map": "function (doc) {
|
"map": "function (doc) {
|
||||||
if (doc.modelId === \\"14f1c4e94d6a47b682ce89d35d4c78b0\\" && doc[\\"Name\\"] === \\"Test\\" || doc[\\"Yes\\"] > \\"Value\\") {
|
if (doc.tableId === \\"14f1c4e94d6a47b682ce89d35d4c78b0\\" && doc[\\"Name\\"] === \\"Test\\" || doc[\\"Yes\\"] > \\"Value\\") {
|
||||||
emit(doc._id);
|
emit(doc._id);
|
||||||
}
|
}
|
||||||
}",
|
}",
|
||||||
|
@ -65,7 +65,7 @@ Object {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"groupBy": undefined,
|
"groupBy": undefined,
|
||||||
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
"tableId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
||||||
"schema": null,
|
"schema": null,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ Object {
|
||||||
exports[`viewBuilder Group By creates a view emitting the group by field 1`] = `
|
exports[`viewBuilder Group By creates a view emitting the group by field 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"map": "function (doc) {
|
"map": "function (doc) {
|
||||||
if (doc.modelId === \\"14f1c4e94d6a47b682ce89d35d4c78b0\\" ) {
|
if (doc.tableId === \\"14f1c4e94d6a47b682ce89d35d4c78b0\\" ) {
|
||||||
emit(doc[\\"age\\"], doc[\\"score\\"]);
|
emit(doc[\\"age\\"], doc[\\"score\\"]);
|
||||||
}
|
}
|
||||||
}",
|
}",
|
||||||
|
@ -83,7 +83,7 @@ Object {
|
||||||
"field": "score",
|
"field": "score",
|
||||||
"filters": Array [],
|
"filters": Array [],
|
||||||
"groupBy": "age",
|
"groupBy": "age",
|
||||||
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
"tableId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
||||||
"schema": null,
|
"schema": null,
|
||||||
},
|
},
|
||||||
"reduce": "_stats",
|
"reduce": "_stats",
|
||||||
|
|
|
@ -6,7 +6,7 @@ describe("viewBuilder", () => {
|
||||||
it("creates a view with multiple filters and conjunctions", () => {
|
it("creates a view with multiple filters and conjunctions", () => {
|
||||||
expect(viewTemplate({
|
expect(viewTemplate({
|
||||||
"name": "Test View",
|
"name": "Test View",
|
||||||
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
"tableId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
||||||
"filters": [{
|
"filters": [{
|
||||||
"value": "Test",
|
"value": "Test",
|
||||||
"condition": "EQUALS",
|
"condition": "EQUALS",
|
||||||
|
@ -27,7 +27,7 @@ describe("viewBuilder", () => {
|
||||||
"name": "Calculate View",
|
"name": "Calculate View",
|
||||||
"field": "myField",
|
"field": "myField",
|
||||||
"calculation": "stats",
|
"calculation": "stats",
|
||||||
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
"tableId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
||||||
"filters": []
|
"filters": []
|
||||||
})).toMatchSnapshot()
|
})).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
@ -37,7 +37,7 @@ describe("viewBuilder", () => {
|
||||||
it("creates a view emitting the group by field", () => {
|
it("creates a view emitting the group by field", () => {
|
||||||
expect(viewTemplate({
|
expect(viewTemplate({
|
||||||
"name": "Test Scores Grouped By Age",
|
"name": "Test Scores Grouped By Age",
|
||||||
"modelId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
"tableId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
||||||
"groupBy": "age",
|
"groupBy": "age",
|
||||||
"field": "score",
|
"field": "score",
|
||||||
"filters": [],
|
"filters": [],
|
||||||
|
|
|
@ -90,12 +90,12 @@ function parseEmitExpression(field, groupBy) {
|
||||||
*
|
*
|
||||||
* @param {Object} viewDefinition - the JSON definition for a custom view.
|
* @param {Object} viewDefinition - the JSON definition for a custom view.
|
||||||
* field: field that calculations will be performed on
|
* field: field that calculations will be performed on
|
||||||
* modelId: modelId of the model this view was created from
|
* tableId: tableId of the table this view was created from
|
||||||
* groupBy: field that calculations will be grouped by. Field must be present for this to be useful
|
* groupBy: field that calculations will be grouped by. Field must be present for this to be useful
|
||||||
* filters: Array of filter objects containing predicates that are parsed into a JS expression
|
* filters: Array of filter objects containing predicates that are parsed into a JS expression
|
||||||
* calculation: an optional calculation to be performed over the view data.
|
* calculation: an optional calculation to be performed over the view data.
|
||||||
*/
|
*/
|
||||||
function viewTemplate({ field, modelId, groupBy, filters = [], calculation }) {
|
function viewTemplate({ field, tableId, groupBy, filters = [], calculation }) {
|
||||||
const parsedFilters = parseFilterExpression(filters)
|
const parsedFilters = parseFilterExpression(filters)
|
||||||
const filterExpression = parsedFilters ? `&& ${parsedFilters}` : ""
|
const filterExpression = parsedFilters ? `&& ${parsedFilters}` : ""
|
||||||
|
|
||||||
|
@ -115,14 +115,14 @@ function viewTemplate({ field, modelId, groupBy, filters = [], calculation }) {
|
||||||
return {
|
return {
|
||||||
meta: {
|
meta: {
|
||||||
field,
|
field,
|
||||||
modelId,
|
tableId,
|
||||||
groupBy,
|
groupBy,
|
||||||
filters,
|
filters,
|
||||||
schema,
|
schema,
|
||||||
calculation,
|
calculation,
|
||||||
},
|
},
|
||||||
map: `function (doc) {
|
map: `function (doc) {
|
||||||
if (doc.modelId === "${modelId}" ${filterExpression}) {
|
if (doc.tableId === "${tableId}" ${filterExpression}) {
|
||||||
${emitExpression}
|
${emitExpression}
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
|
|
|
@ -12,7 +12,7 @@ const {
|
||||||
clientRoutes,
|
clientRoutes,
|
||||||
applicationRoutes,
|
applicationRoutes,
|
||||||
recordRoutes,
|
recordRoutes,
|
||||||
modelRoutes,
|
tableRoutes,
|
||||||
viewRoutes,
|
viewRoutes,
|
||||||
staticRoutes,
|
staticRoutes,
|
||||||
componentRoutes,
|
componentRoutes,
|
||||||
|
@ -74,8 +74,8 @@ router.use(authRoutes.allowedMethods())
|
||||||
router.use(viewRoutes.routes())
|
router.use(viewRoutes.routes())
|
||||||
router.use(viewRoutes.allowedMethods())
|
router.use(viewRoutes.allowedMethods())
|
||||||
|
|
||||||
router.use(modelRoutes.routes())
|
router.use(tableRoutes.routes())
|
||||||
router.use(modelRoutes.allowedMethods())
|
router.use(tableRoutes.allowedMethods())
|
||||||
|
|
||||||
router.use(recordRoutes.routes())
|
router.use(recordRoutes.routes())
|
||||||
router.use(recordRoutes.allowedMethods())
|
router.use(recordRoutes.allowedMethods())
|
||||||
|
|
|
@ -4,7 +4,7 @@ const userRoutes = require("./user")
|
||||||
const instanceRoutes = require("./instance")
|
const instanceRoutes = require("./instance")
|
||||||
const clientRoutes = require("./client")
|
const clientRoutes = require("./client")
|
||||||
const applicationRoutes = require("./application")
|
const applicationRoutes = require("./application")
|
||||||
const modelRoutes = require("./model")
|
const tableRoutes = require("./table")
|
||||||
const recordRoutes = require("./record")
|
const recordRoutes = require("./record")
|
||||||
const viewRoutes = require("./view")
|
const viewRoutes = require("./view")
|
||||||
const staticRoutes = require("./static")
|
const staticRoutes = require("./static")
|
||||||
|
@ -25,7 +25,7 @@ module.exports = {
|
||||||
clientRoutes,
|
clientRoutes,
|
||||||
applicationRoutes,
|
applicationRoutes,
|
||||||
recordRoutes,
|
recordRoutes,
|
||||||
modelRoutes,
|
tableRoutes,
|
||||||
viewRoutes,
|
viewRoutes,
|
||||||
staticRoutes,
|
staticRoutes,
|
||||||
componentRoutes,
|
componentRoutes,
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
const Router = require("@koa/router")
|
|
||||||
const modelController = require("../controllers/model")
|
|
||||||
const authorized = require("../../middleware/authorized")
|
|
||||||
const { BUILDER, READ_MODEL } = require("../../utilities/accessLevels")
|
|
||||||
|
|
||||||
const router = Router()
|
|
||||||
|
|
||||||
router
|
|
||||||
.get("/api/models", authorized(BUILDER), modelController.fetch)
|
|
||||||
.get(
|
|
||||||
"/api/models/:id",
|
|
||||||
authorized(READ_MODEL, ctx => ctx.params.id),
|
|
||||||
modelController.find
|
|
||||||
)
|
|
||||||
.post("/api/models", authorized(BUILDER), modelController.save)
|
|
||||||
.post(
|
|
||||||
"/api/models/csv/validate",
|
|
||||||
authorized(BUILDER),
|
|
||||||
modelController.validateCSVSchema
|
|
||||||
)
|
|
||||||
.delete(
|
|
||||||
"/api/models/:modelId/:revId",
|
|
||||||
authorized(BUILDER),
|
|
||||||
modelController.destroy
|
|
||||||
)
|
|
||||||
|
|
||||||
module.exports = router
|
|
|
@ -2,46 +2,46 @@ const Router = require("@koa/router")
|
||||||
const recordController = require("../controllers/record")
|
const recordController = require("../controllers/record")
|
||||||
const authorized = require("../../middleware/authorized")
|
const authorized = require("../../middleware/authorized")
|
||||||
const usage = require("../../middleware/usageQuota")
|
const usage = require("../../middleware/usageQuota")
|
||||||
const { READ_MODEL, WRITE_MODEL } = require("../../utilities/accessLevels")
|
const { READ_TABLE, WRITE_TABLE } = require("../../utilities/accessLevels")
|
||||||
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|
||||||
router
|
router
|
||||||
.get(
|
.get(
|
||||||
"/api/:modelId/:recordId/enrich",
|
"/api/:tableId/:recordId/enrich",
|
||||||
authorized(READ_MODEL, ctx => ctx.params.modelId),
|
authorized(READ_TABLE, ctx => ctx.params.tableId),
|
||||||
recordController.fetchEnrichedRecord
|
recordController.fetchEnrichedRecord
|
||||||
)
|
)
|
||||||
.get(
|
.get(
|
||||||
"/api/:modelId/records",
|
"/api/:tableId/records",
|
||||||
authorized(READ_MODEL, ctx => ctx.params.modelId),
|
authorized(READ_TABLE, ctx => ctx.params.tableId),
|
||||||
recordController.fetchModelRecords
|
recordController.fetchTableRecords
|
||||||
)
|
)
|
||||||
.get(
|
.get(
|
||||||
"/api/:modelId/records/:recordId",
|
"/api/:tableId/records/:recordId",
|
||||||
authorized(READ_MODEL, ctx => ctx.params.modelId),
|
authorized(READ_TABLE, ctx => ctx.params.tableId),
|
||||||
recordController.find
|
recordController.find
|
||||||
)
|
)
|
||||||
.post("/api/records/search", recordController.search)
|
.post("/api/records/search", recordController.search)
|
||||||
.post(
|
.post(
|
||||||
"/api/:modelId/records",
|
"/api/:tableId/records",
|
||||||
authorized(WRITE_MODEL, ctx => ctx.params.modelId),
|
authorized(WRITE_TABLE, ctx => ctx.params.tableId),
|
||||||
usage,
|
usage,
|
||||||
recordController.save
|
recordController.save
|
||||||
)
|
)
|
||||||
.patch(
|
.patch(
|
||||||
"/api/:modelId/records/:id",
|
"/api/:tableId/records/:id",
|
||||||
authorized(WRITE_MODEL, ctx => ctx.params.modelId),
|
authorized(WRITE_TABLE, ctx => ctx.params.tableId),
|
||||||
recordController.patch
|
recordController.patch
|
||||||
)
|
)
|
||||||
.post(
|
.post(
|
||||||
"/api/:modelId/records/validate",
|
"/api/:tableId/records/validate",
|
||||||
authorized(WRITE_MODEL, ctx => ctx.params.modelId),
|
authorized(WRITE_TABLE, ctx => ctx.params.tableId),
|
||||||
recordController.validate
|
recordController.validate
|
||||||
)
|
)
|
||||||
.delete(
|
.delete(
|
||||||
"/api/:modelId/records/:recordId/:revId",
|
"/api/:tableId/records/:recordId/:revId",
|
||||||
authorized(WRITE_MODEL, ctx => ctx.params.modelId),
|
authorized(WRITE_TABLE, ctx => ctx.params.tableId),
|
||||||
usage,
|
usage,
|
||||||
recordController.destroy
|
recordController.destroy
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
const Router = require("@koa/router")
|
||||||
|
const tableController = require("../controllers/table")
|
||||||
|
const authorized = require("../../middleware/authorized")
|
||||||
|
const { BUILDER, READ_TABLE } = require("../../utilities/accessLevels")
|
||||||
|
|
||||||
|
const router = Router()
|
||||||
|
|
||||||
|
router
|
||||||
|
.get("/api/tables", authorized(BUILDER), tableController.fetch)
|
||||||
|
.get(
|
||||||
|
"/api/tables/:id",
|
||||||
|
authorized(READ_TABLE, ctx => ctx.params.id),
|
||||||
|
tableController.find
|
||||||
|
)
|
||||||
|
.post("/api/tables", authorized(BUILDER), tableController.save)
|
||||||
|
.post(
|
||||||
|
"/api/tables/csv/validate",
|
||||||
|
authorized(BUILDER),
|
||||||
|
tableController.validateCSVSchema
|
||||||
|
)
|
||||||
|
.delete(
|
||||||
|
"/api/tables/:tableId/:revId",
|
||||||
|
authorized(BUILDER),
|
||||||
|
tableController.destroy
|
||||||
|
)
|
||||||
|
|
||||||
|
module.exports = router
|
|
@ -2,7 +2,7 @@ const {
|
||||||
createInstance,
|
createInstance,
|
||||||
createClientDatabase,
|
createClientDatabase,
|
||||||
createApplication,
|
createApplication,
|
||||||
createModel,
|
createTable,
|
||||||
createView,
|
createView,
|
||||||
supertest,
|
supertest,
|
||||||
defaultHeaders
|
defaultHeaders
|
||||||
|
@ -12,8 +12,8 @@ const {
|
||||||
generatePowerUserPermissions,
|
generatePowerUserPermissions,
|
||||||
POWERUSER_LEVEL_ID,
|
POWERUSER_LEVEL_ID,
|
||||||
ADMIN_LEVEL_ID,
|
ADMIN_LEVEL_ID,
|
||||||
READ_MODEL,
|
READ_TABLE,
|
||||||
WRITE_MODEL,
|
WRITE_TABLE,
|
||||||
} = require("../../../utilities/accessLevels")
|
} = require("../../../utilities/accessLevels")
|
||||||
|
|
||||||
describe("/accesslevels", () => {
|
describe("/accesslevels", () => {
|
||||||
|
@ -21,7 +21,7 @@ describe("/accesslevels", () => {
|
||||||
let server
|
let server
|
||||||
let request
|
let request
|
||||||
let instanceId
|
let instanceId
|
||||||
let model
|
let table
|
||||||
let view
|
let view
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
@ -36,8 +36,8 @@ describe("/accesslevels", () => {
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
instanceId = (await createInstance(request, appId))._id
|
instanceId = (await createInstance(request, appId))._id
|
||||||
model = await createModel(request, appId, instanceId)
|
table = await createTable(request, appId, instanceId)
|
||||||
view = await createView(request, appId, instanceId, model._id)
|
view = await createView(request, appId, instanceId, table._id)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("create", () => {
|
describe("create", () => {
|
||||||
|
@ -63,7 +63,7 @@ describe("/accesslevels", () => {
|
||||||
it("should list custom levels, plus 2 default levels", async () => {
|
it("should list custom levels, plus 2 default levels", async () => {
|
||||||
const createRes = await request
|
const createRes = await request
|
||||||
.post(`/api/accesslevels`)
|
.post(`/api/accesslevels`)
|
||||||
.send({ name: "user", permissions: [ { itemId: model._id, name: READ_MODEL }] })
|
.send({ name: "user", permissions: [ { itemId: table._id, name: READ_TABLE }] })
|
||||||
.set(defaultHeaders(appId, instanceId))
|
.set(defaultHeaders(appId, instanceId))
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
@ -96,7 +96,7 @@ describe("/accesslevels", () => {
|
||||||
it("should delete custom access level", async () => {
|
it("should delete custom access level", async () => {
|
||||||
const createRes = await request
|
const createRes = await request
|
||||||
.post(`/api/accesslevels`)
|
.post(`/api/accesslevels`)
|
||||||
.send({ name: "user", permissions: [ { itemId: model._id, name: READ_MODEL } ] })
|
.send({ name: "user", permissions: [ { itemId: table._id, name: READ_TABLE } ] })
|
||||||
.set(defaultHeaders(appId, instanceId))
|
.set(defaultHeaders(appId, instanceId))
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
@ -119,7 +119,7 @@ describe("/accesslevels", () => {
|
||||||
it("should add given permissions", async () => {
|
it("should add given permissions", async () => {
|
||||||
const createRes = await request
|
const createRes = await request
|
||||||
.post(`/api/accesslevels`)
|
.post(`/api/accesslevels`)
|
||||||
.send({ name: "user", permissions: [ { itemId: model._id, name: READ_MODEL }] })
|
.send({ name: "user", permissions: [ { itemId: table._id, name: READ_TABLE }] })
|
||||||
.set(defaultHeaders(appId, instanceId))
|
.set(defaultHeaders(appId, instanceId))
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
@ -130,7 +130,7 @@ describe("/accesslevels", () => {
|
||||||
.patch(`/api/accesslevels/${customLevel._id}`)
|
.patch(`/api/accesslevels/${customLevel._id}`)
|
||||||
.send({
|
.send({
|
||||||
_rev: customLevel._rev,
|
_rev: customLevel._rev,
|
||||||
addedPermissions: [ { itemId: model._id, name: WRITE_MODEL } ]
|
addedPermissions: [ { itemId: table._id, name: WRITE_TABLE } ]
|
||||||
})
|
})
|
||||||
.set(defaultHeaders(appId, instanceId))
|
.set(defaultHeaders(appId, instanceId))
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
|
@ -142,8 +142,8 @@ describe("/accesslevels", () => {
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(finalRes.body.permissions.length).toBe(2)
|
expect(finalRes.body.permissions.length).toBe(2)
|
||||||
expect(finalRes.body.permissions.some(p => p.name === WRITE_MODEL)).toBe(true)
|
expect(finalRes.body.permissions.some(p => p.name === WRITE_TABLE)).toBe(true)
|
||||||
expect(finalRes.body.permissions.some(p => p.name === READ_MODEL)).toBe(true)
|
expect(finalRes.body.permissions.some(p => p.name === READ_TABLE)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should remove given permissions", async () => {
|
it("should remove given permissions", async () => {
|
||||||
|
@ -152,8 +152,8 @@ describe("/accesslevels", () => {
|
||||||
.send({
|
.send({
|
||||||
name: "user",
|
name: "user",
|
||||||
permissions: [
|
permissions: [
|
||||||
{ itemId: model._id, name: READ_MODEL },
|
{ itemId: table._id, name: READ_TABLE },
|
||||||
{ itemId: model._id, name: WRITE_MODEL },
|
{ itemId: table._id, name: WRITE_TABLE },
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
.set(defaultHeaders(appId, instanceId))
|
.set(defaultHeaders(appId, instanceId))
|
||||||
|
@ -166,7 +166,7 @@ describe("/accesslevels", () => {
|
||||||
.patch(`/api/accesslevels/${customLevel._id}`)
|
.patch(`/api/accesslevels/${customLevel._id}`)
|
||||||
.send({
|
.send({
|
||||||
_rev: customLevel._rev,
|
_rev: customLevel._rev,
|
||||||
removedPermissions: [ { itemId: model._id, name: WRITE_MODEL }]
|
removedPermissions: [ { itemId: table._id, name: WRITE_TABLE }]
|
||||||
})
|
})
|
||||||
.set(defaultHeaders(appId, instanceId))
|
.set(defaultHeaders(appId, instanceId))
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
|
@ -178,7 +178,7 @@ describe("/accesslevels", () => {
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(finalRes.body.permissions.length).toBe(1)
|
expect(finalRes.body.permissions.length).toBe(1)
|
||||||
expect(finalRes.body.permissions.some(p => p.name === READ_MODEL)).toBe(true)
|
expect(finalRes.body.permissions.some(p => p.name === READ_TABLE)).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,8 +2,8 @@ const {
|
||||||
createClientDatabase,
|
createClientDatabase,
|
||||||
createApplication,
|
createApplication,
|
||||||
createInstance,
|
createInstance,
|
||||||
createModel,
|
createTable,
|
||||||
getAllFromModel,
|
getAllFromTable,
|
||||||
defaultHeaders,
|
defaultHeaders,
|
||||||
supertest,
|
supertest,
|
||||||
insertDocument,
|
insertDocument,
|
||||||
|
@ -167,9 +167,9 @@ describe("/automations", () => {
|
||||||
|
|
||||||
describe("trigger", () => {
|
describe("trigger", () => {
|
||||||
it("trigger the automation successfully", async () => {
|
it("trigger the automation successfully", async () => {
|
||||||
let model = await createModel(request, app._id, instance._id)
|
let table = await createTable(request, app._id, instance._id)
|
||||||
TEST_AUTOMATION.definition.trigger.inputs.modelId = model._id
|
TEST_AUTOMATION.definition.trigger.inputs.tableId = table._id
|
||||||
TEST_AUTOMATION.definition.steps[0].inputs.record.modelId = model._id
|
TEST_AUTOMATION.definition.steps[0].inputs.record.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
|
||||||
|
@ -180,7 +180,7 @@ describe("/automations", () => {
|
||||||
expect(res.body.message).toEqual(`Automation ${automation._id} has been triggered.`)
|
expect(res.body.message).toEqual(`Automation ${automation._id} has been triggered.`)
|
||||||
expect(res.body.automation.name).toEqual(TEST_AUTOMATION.name)
|
expect(res.body.automation.name).toEqual(TEST_AUTOMATION.name)
|
||||||
await delay(500)
|
await delay(500)
|
||||||
let elements = await getAllFromModel(request, app._id, instance._id, model._id)
|
let elements = await getAllFromTable(request, app._id, instance._id, table._id)
|
||||||
// don't test it unless there are values to test
|
// don't test it unless there are values to test
|
||||||
if (elements.length === 1) {
|
if (elements.length === 1) {
|
||||||
expect(elements.length).toEqual(1)
|
expect(elements.length).toEqual(1)
|
||||||
|
|
|
@ -39,13 +39,13 @@ exports.defaultHeaders = (appId, instanceId) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.createModel = async (request, appId, instanceId, model) => {
|
exports.createTable = async (request, appId, instanceId, table) => {
|
||||||
if (model != null && model._id) {
|
if (table != null && table._id) {
|
||||||
delete model._id
|
delete table._id
|
||||||
}
|
}
|
||||||
model = model || {
|
table = table || {
|
||||||
name: "TestModel",
|
name: "TestTable",
|
||||||
type: "model",
|
type: "table",
|
||||||
key: "name",
|
key: "name",
|
||||||
schema: {
|
schema: {
|
||||||
name: {
|
name: {
|
||||||
|
@ -64,23 +64,23 @@ exports.createModel = async (request, appId, instanceId, model) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.post(`/api/models`)
|
.post(`/api/tables`)
|
||||||
.set(exports.defaultHeaders(appId, instanceId))
|
.set(exports.defaultHeaders(appId, instanceId))
|
||||||
.send(model)
|
.send(table)
|
||||||
return res.body
|
return res.body
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getAllFromModel = async (request, appId, instanceId, modelId) => {
|
exports.getAllFromTable = async (request, appId, instanceId, tableId) => {
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/${modelId}/records`)
|
.get(`/api/${tableId}/records`)
|
||||||
.set(exports.defaultHeaders(appId, instanceId))
|
.set(exports.defaultHeaders(appId, instanceId))
|
||||||
return res.body
|
return res.body
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.createView = async (request, appId, instanceId, modelId, view) => {
|
exports.createView = async (request, appId, instanceId, tableId, view) => {
|
||||||
view = view || {
|
view = view || {
|
||||||
map: "function(doc) { emit(doc[doc.key], doc._id); } ",
|
map: "function(doc) { emit(doc[doc.key], doc._id); } ",
|
||||||
modelId: modelId,
|
tableId: tableId,
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
|
|
|
@ -2,7 +2,7 @@ const {
|
||||||
createApplication,
|
createApplication,
|
||||||
createClientDatabase,
|
createClientDatabase,
|
||||||
createInstance,
|
createInstance,
|
||||||
createModel,
|
createTable,
|
||||||
supertest,
|
supertest,
|
||||||
defaultHeaders,
|
defaultHeaders,
|
||||||
} = require("./couchTestUtils");
|
} = require("./couchTestUtils");
|
||||||
|
@ -11,7 +11,7 @@ describe("/records", () => {
|
||||||
let request
|
let request
|
||||||
let server
|
let server
|
||||||
let instance
|
let instance
|
||||||
let model
|
let table
|
||||||
let record
|
let record
|
||||||
let app
|
let app
|
||||||
|
|
||||||
|
@ -27,18 +27,18 @@ describe("/records", () => {
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
instance = await createInstance(request, app._id)
|
instance = await createInstance(request, app._id)
|
||||||
model = await createModel(request, app._id, instance._id)
|
table = await createTable(request, app._id, instance._id)
|
||||||
record = {
|
record = {
|
||||||
name: "Test Contact",
|
name: "Test Contact",
|
||||||
description: "original description",
|
description: "original description",
|
||||||
status: "new",
|
status: "new",
|
||||||
modelId: model._id
|
tableId: table._id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const createRecord = async r =>
|
const createRecord = async r =>
|
||||||
await request
|
await request
|
||||||
.post(`/api/${r ? r.modelId : record.modelId}/records`)
|
.post(`/api/${r ? r.tableId : record.tableId}/records`)
|
||||||
.send(r || record)
|
.send(r || record)
|
||||||
.set(defaultHeaders(app._id, instance._id))
|
.set(defaultHeaders(app._id, instance._id))
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
|
@ -46,7 +46,7 @@ describe("/records", () => {
|
||||||
|
|
||||||
const loadRecord = async id =>
|
const loadRecord = async id =>
|
||||||
await request
|
await request
|
||||||
.get(`/api/${model._id}/records/${id}`)
|
.get(`/api/${table._id}/records/${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)
|
||||||
|
@ -57,7 +57,7 @@ describe("/records", () => {
|
||||||
|
|
||||||
it("returns a success message when the record is created", async () => {
|
it("returns a success message when the record is created", async () => {
|
||||||
const res = await createRecord()
|
const res = await createRecord()
|
||||||
expect(res.res.statusMessage).toEqual(`${model.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()
|
||||||
})
|
})
|
||||||
|
@ -67,18 +67,18 @@ describe("/records", () => {
|
||||||
const existing = rec.body
|
const existing = rec.body
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.post(`/api/${model._id}/records`)
|
.post(`/api/${table._id}/records`)
|
||||||
.send({
|
.send({
|
||||||
_id: existing._id,
|
_id: existing._id,
|
||||||
_rev: existing._rev,
|
_rev: existing._rev,
|
||||||
modelId: model._id,
|
tableId: table._id,
|
||||||
name: "Updated Name",
|
name: "Updated Name",
|
||||||
})
|
})
|
||||||
.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.res.statusMessage).toEqual(`${model.name} updated successfully.`)
|
expect(res.res.statusMessage).toEqual(`${table.name} updated successfully.`)
|
||||||
expect(res.body.name).toEqual("Updated Name")
|
expect(res.body.name).toEqual("Updated Name")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ describe("/records", () => {
|
||||||
const existing = rec.body
|
const existing = rec.body
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/${model._id}/records/${existing._id}`)
|
.get(`/api/${table._id}/records/${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)
|
||||||
|
@ -100,9 +100,9 @@ describe("/records", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should list all records for given modelId", async () => {
|
it("should list all records for given tableId", async () => {
|
||||||
const newRecord = {
|
const newRecord = {
|
||||||
modelId: model._id,
|
tableId: table._id,
|
||||||
name: "Second Contact",
|
name: "Second Contact",
|
||||||
status: "new"
|
status: "new"
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ describe("/records", () => {
|
||||||
await createRecord(newRecord)
|
await createRecord(newRecord)
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/${model._id}/records`)
|
.get(`/api/${table._id}/records`)
|
||||||
.set(defaultHeaders(app._id, instance._id))
|
.set(defaultHeaders(app._id, instance._id))
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
@ -122,7 +122,7 @@ describe("/records", () => {
|
||||||
|
|
||||||
it("lists records when queried by their ID", async () => {
|
it("lists records when queried by their ID", async () => {
|
||||||
const newRecord = {
|
const newRecord = {
|
||||||
modelId: model._id,
|
tableId: table._id,
|
||||||
name: "Second Contact",
|
name: "Second Contact",
|
||||||
status: "new"
|
status: "new"
|
||||||
}
|
}
|
||||||
|
@ -147,7 +147,7 @@ describe("/records", () => {
|
||||||
it("load should return 404 when record does not exist", async () => {
|
it("load should return 404 when record does not exist", async () => {
|
||||||
await createRecord()
|
await createRecord()
|
||||||
await request
|
await request
|
||||||
.get(`/api/${model._id}/records/not-a-valid-id`)
|
.get(`/api/${table._id}/records/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)
|
||||||
|
@ -160,9 +160,9 @@ describe("/records", () => {
|
||||||
const number = {type:"number", constraints: { type: "number", presence: false }}
|
const number = {type:"number", constraints: { type: "number", presence: false }}
|
||||||
const datetime = {type:"datetime", constraints: { type: "string", presence: false, datetime: {earliest:"", latest: ""} }}
|
const datetime = {type:"datetime", constraints: { type: "string", presence: false, datetime: {earliest:"", latest: ""} }}
|
||||||
|
|
||||||
model = await createModel(request, app._id, instance._id, {
|
table = await createTable(request, app._id, instance._id, {
|
||||||
name: "TestModel2",
|
name: "TestTable2",
|
||||||
type: "model",
|
type: "table",
|
||||||
key: "name",
|
key: "name",
|
||||||
schema: {
|
schema: {
|
||||||
name: str,
|
name: str,
|
||||||
|
@ -209,7 +209,7 @@ describe("/records", () => {
|
||||||
boolUndefined: undefined,
|
boolUndefined: undefined,
|
||||||
boolString: "true",
|
boolString: "true",
|
||||||
boolBool: true,
|
boolBool: true,
|
||||||
modelId: model._id,
|
tableId: table._id,
|
||||||
attachmentNull : null,
|
attachmentNull : null,
|
||||||
attachmentUndefined : undefined,
|
attachmentUndefined : undefined,
|
||||||
attachmentEmpty : "",
|
attachmentEmpty : "",
|
||||||
|
@ -249,18 +249,18 @@ describe("/records", () => {
|
||||||
const existing = rec.body
|
const existing = rec.body
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.patch(`/api/${model._id}/records/${existing._id}`)
|
.patch(`/api/${table._id}/records/${existing._id}`)
|
||||||
.send({
|
.send({
|
||||||
_id: existing._id,
|
_id: existing._id,
|
||||||
_rev: existing._rev,
|
_rev: existing._rev,
|
||||||
modelId: model._id,
|
tableId: table._id,
|
||||||
name: "Updated Name",
|
name: "Updated Name",
|
||||||
})
|
})
|
||||||
.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.res.statusMessage).toEqual(`${model.name} updated successfully.`)
|
expect(res.res.statusMessage).toEqual(`${table.name} updated successfully.`)
|
||||||
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)
|
||||||
|
|
||||||
|
@ -275,7 +275,7 @@ describe("/records", () => {
|
||||||
describe("validate", () => {
|
describe("validate", () => {
|
||||||
it("should return no errors on valid record", async () => {
|
it("should return no errors on valid record", async () => {
|
||||||
const result = await request
|
const result = await request
|
||||||
.post(`/api/${model._id}/records/validate`)
|
.post(`/api/${table._id}/records/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/)
|
||||||
|
@ -287,7 +287,7 @@ describe("/records", () => {
|
||||||
|
|
||||||
it("should errors on invalid record", async () => {
|
it("should errors on invalid record", async () => {
|
||||||
const result = await request
|
const result = await request
|
||||||
.post(`/api/${model._id}/records/validate`)
|
.post(`/api/${table._id}/records/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/)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const {
|
const {
|
||||||
createInstance,
|
createInstance,
|
||||||
createModel,
|
createTable,
|
||||||
supertest,
|
supertest,
|
||||||
createClientDatabase,
|
createClientDatabase,
|
||||||
createApplication,
|
createApplication,
|
||||||
|
@ -9,7 +9,7 @@ const {
|
||||||
getDocument
|
getDocument
|
||||||
} = require("./couchTestUtils")
|
} = require("./couchTestUtils")
|
||||||
|
|
||||||
describe("/models", () => {
|
describe("/tables", () => {
|
||||||
let request
|
let request
|
||||||
let server
|
let server
|
||||||
let app
|
let app
|
||||||
|
@ -30,11 +30,11 @@ describe("/models", () => {
|
||||||
instance = await createInstance(request, app._id);
|
instance = await createInstance(request, app._id);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns a success message when the model is successfully created", done => {
|
it("returns a success message when the table is successfully created", done => {
|
||||||
request
|
request
|
||||||
.post(`/api/models`)
|
.post(`/api/tables`)
|
||||||
.send({
|
.send({
|
||||||
name: "TestModel",
|
name: "TestTable",
|
||||||
key: "name",
|
key: "name",
|
||||||
schema: {
|
schema: {
|
||||||
name: { type: "string" }
|
name: { type: "string" }
|
||||||
|
@ -44,17 +44,17 @@ describe("/models", () => {
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.end(async (err, res) => {
|
.end(async (err, res) => {
|
||||||
expect(res.res.statusMessage).toEqual("Model TestModel saved successfully.");
|
expect(res.res.statusMessage).toEqual("Table TestTable saved successfully.");
|
||||||
expect(res.body.name).toEqual("TestModel");
|
expect(res.body.name).toEqual("TestTable");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
it("renames all the record fields for a model when a schema key is renamed", async () => {
|
it("renames all the record fields for a table when a schema key is renamed", async () => {
|
||||||
const testModel = await createModel(request, app._id, instance._id);
|
const testTable = await createTable(request, app._id, instance._id);
|
||||||
|
|
||||||
const testRecord = await request
|
const testRecord = await request
|
||||||
.post(`/api/${testModel._id}/records`)
|
.post(`/api/${testTable._id}/records`)
|
||||||
.send({
|
.send({
|
||||||
name: "test"
|
name: "test"
|
||||||
})
|
})
|
||||||
|
@ -62,12 +62,12 @@ describe("/models", () => {
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
const updatedModel = await request
|
const updatedTable = await request
|
||||||
.post(`/api/models`)
|
.post(`/api/tables`)
|
||||||
.send({
|
.send({
|
||||||
_id: testModel._id,
|
_id: testTable._id,
|
||||||
_rev: testModel._rev,
|
_rev: testTable._rev,
|
||||||
name: "TestModel",
|
name: "TestTable",
|
||||||
key: "name",
|
key: "name",
|
||||||
_rename: {
|
_rename: {
|
||||||
old: "name",
|
old: "name",
|
||||||
|
@ -81,11 +81,11 @@ describe("/models", () => {
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(updatedModel.res.statusMessage).toEqual("Model TestModel saved successfully.");
|
expect(updatedTable.res.statusMessage).toEqual("Table TestTable saved successfully.");
|
||||||
expect(updatedModel.body.name).toEqual("TestModel");
|
expect(updatedTable.body.name).toEqual("TestTable");
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/${testModel._id}/records/${testRecord.body._id}`)
|
.get(`/api/${testTable._id}/records/${testRecord.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)
|
||||||
|
@ -98,11 +98,11 @@ describe("/models", () => {
|
||||||
await builderEndpointShouldBlockNormalUsers({
|
await builderEndpointShouldBlockNormalUsers({
|
||||||
request,
|
request,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: `/api/models`,
|
url: `/api/tables`,
|
||||||
instanceId: instance._id,
|
instanceId: instance._id,
|
||||||
appId: app._id,
|
appId: app._id,
|
||||||
body: {
|
body: {
|
||||||
name: "TestModel",
|
name: "TestTable",
|
||||||
key: "name",
|
key: "name",
|
||||||
schema: {
|
schema: {
|
||||||
name: { type: "string" }
|
name: { type: "string" }
|
||||||
|
@ -113,27 +113,27 @@ describe("/models", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("fetch", () => {
|
describe("fetch", () => {
|
||||||
let testModel
|
let testTable
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
instance = await createInstance(request, app._id)
|
instance = await createInstance(request, app._id)
|
||||||
testModel = await createModel(request, app._id, instance._id, testModel)
|
testTable = await createTable(request, app._id, instance._id, testTable)
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
delete testModel._rev
|
delete testTable._rev
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns all the models for that instance in the response body", done => {
|
it("returns all the tables for that instance in the response body", done => {
|
||||||
request
|
request
|
||||||
.get(`/api/models`)
|
.get(`/api/tables`)
|
||||||
.set(defaultHeaders(app._id, instance._id))
|
.set(defaultHeaders(app._id, instance._id))
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.end(async (_, res) => {
|
.end(async (_, res) => {
|
||||||
const fetchedModel = res.body[0];
|
const fetchedTable = res.body[0];
|
||||||
expect(fetchedModel.name).toEqual(testModel.name);
|
expect(fetchedTable.name).toEqual(testTable.name);
|
||||||
expect(fetchedModel.type).toEqual("model");
|
expect(fetchedTable.type).toEqual("table");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -142,7 +142,7 @@ describe("/models", () => {
|
||||||
await builderEndpointShouldBlockNormalUsers({
|
await builderEndpointShouldBlockNormalUsers({
|
||||||
request,
|
request,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: `/api/models`,
|
url: `/api/tables`,
|
||||||
instanceId: instance._id,
|
instanceId: instance._id,
|
||||||
appId: app._id,
|
appId: app._id,
|
||||||
})
|
})
|
||||||
|
@ -150,33 +150,33 @@ describe("/models", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("destroy", () => {
|
describe("destroy", () => {
|
||||||
let testModel;
|
let testTable;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
instance = await createInstance(request, app._id)
|
instance = await createInstance(request, app._id)
|
||||||
testModel = await createModel(request, app._id, instance._id, testModel)
|
testTable = await createTable(request, app._id, instance._id, testTable)
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
delete testModel._rev
|
delete testTable._rev
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns a success response when a model is deleted.", async done => {
|
it("returns a success response when a table is deleted.", async done => {
|
||||||
request
|
request
|
||||||
.delete(`/api/models/${testModel._id}/${testModel._rev}`)
|
.delete(`/api/tables/${testTable._id}/${testTable._rev}`)
|
||||||
.set(defaultHeaders(app._id, instance._id))
|
.set(defaultHeaders(app._id, instance._id))
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.end(async (_, res) => {
|
.end(async (_, res) => {
|
||||||
expect(res.res.statusMessage).toEqual(`Model ${testModel._id} deleted.`);
|
expect(res.res.statusMessage).toEqual(`Table ${testTable._id} deleted.`);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
it("deletes linked references to the model after deletion", async done => {
|
it("deletes linked references to the table after deletion", async done => {
|
||||||
const linkedModel = await createModel(request, app._id, instance._id, {
|
const linkedTable = await createTable(request, app._id, instance._id, {
|
||||||
name: "LinkedModel",
|
name: "LinkedTable",
|
||||||
type: "model",
|
type: "table",
|
||||||
key: "name",
|
key: "name",
|
||||||
schema: {
|
schema: {
|
||||||
name: {
|
name: {
|
||||||
|
@ -185,9 +185,9 @@ describe("/models", () => {
|
||||||
type: "string",
|
type: "string",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TestModel: {
|
TestTable: {
|
||||||
type: "link",
|
type: "link",
|
||||||
modelId: testModel._id,
|
tableId: testTable._id,
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "array"
|
type: "array"
|
||||||
}
|
}
|
||||||
|
@ -196,14 +196,14 @@ describe("/models", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
request
|
request
|
||||||
.delete(`/api/models/${testModel._id}/${testModel._rev}`)
|
.delete(`/api/tables/${testTable._id}/${testTable._rev}`)
|
||||||
.set(defaultHeaders(app._id, instance._id))
|
.set(defaultHeaders(app._id, instance._id))
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.end(async (_, res) => {
|
.end(async (_, res) => {
|
||||||
expect(res.res.statusMessage).toEqual(`Model ${testModel._id} deleted.`);
|
expect(res.res.statusMessage).toEqual(`Table ${testTable._id} deleted.`);
|
||||||
const dependentModel = await getDocument(instance._id, linkedModel._id)
|
const dependentTable = await getDocument(instance._id, linkedTable._id)
|
||||||
expect(dependentModel.schema.TestModel).not.toBeDefined();
|
expect(dependentTable.schema.TestTable).not.toBeDefined();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -212,7 +212,7 @@ describe("/models", () => {
|
||||||
await builderEndpointShouldBlockNormalUsers({
|
await builderEndpointShouldBlockNormalUsers({
|
||||||
request,
|
request,
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: `/api/models/${testModel._id}/${testModel._rev}`,
|
url: `/api/tables/${testTable._id}/${testTable._rev}`,
|
||||||
instanceId: instance._id,
|
instanceId: instance._id,
|
||||||
appId: app._id,
|
appId: app._id,
|
||||||
})
|
})
|
|
@ -2,7 +2,7 @@ const {
|
||||||
createClientDatabase,
|
createClientDatabase,
|
||||||
createApplication,
|
createApplication,
|
||||||
createInstance,
|
createInstance,
|
||||||
createModel,
|
createTable,
|
||||||
supertest,
|
supertest,
|
||||||
defaultHeaders,
|
defaultHeaders,
|
||||||
getDocument
|
getDocument
|
||||||
|
@ -13,12 +13,12 @@ describe("/views", () => {
|
||||||
let server
|
let server
|
||||||
let app
|
let app
|
||||||
let instance
|
let instance
|
||||||
let model
|
let table
|
||||||
|
|
||||||
const createView = async (config = {
|
const createView = async (config = {
|
||||||
name: "TestView",
|
name: "TestView",
|
||||||
field: "Price",
|
field: "Price",
|
||||||
modelId: model._id
|
tableId: table._id
|
||||||
}) =>
|
}) =>
|
||||||
await request
|
await request
|
||||||
.post(`/api/views`)
|
.post(`/api/views`)
|
||||||
|
@ -28,7 +28,7 @@ describe("/views", () => {
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
const createRecord = async record => request
|
const createRecord = async record => request
|
||||||
.post(`/api/${model._id}/records`)
|
.post(`/api/${table._id}/records`)
|
||||||
.send(record)
|
.send(record)
|
||||||
.set(defaultHeaders(app._id, instance._id))
|
.set(defaultHeaders(app._id, instance._id))
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
|
@ -50,7 +50,7 @@ describe("/views", () => {
|
||||||
|
|
||||||
describe("create", () => {
|
describe("create", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
model = await createModel(request, app._id, instance._id);
|
table = await createTable(request, app._id, instance._id);
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns a success message when the view is successfully created", async () => {
|
it("returns a success message when the view is successfully created", async () => {
|
||||||
|
@ -58,14 +58,14 @@ describe("/views", () => {
|
||||||
expect(res.res.statusMessage).toEqual("View TestView saved successfully.");
|
expect(res.res.statusMessage).toEqual("View TestView saved successfully.");
|
||||||
})
|
})
|
||||||
|
|
||||||
it("updates the model record with the new view metadata", async () => {
|
it("updates the table record 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 updatedModel = await getDocument(instance._id, model._id)
|
const updatedTable = await getDocument(instance._id, table._id)
|
||||||
expect(updatedModel.views).toEqual({
|
expect(updatedTable.views).toEqual({
|
||||||
TestView: {
|
TestView: {
|
||||||
field: "Price",
|
field: "Price",
|
||||||
modelId: model._id,
|
tableId: table._id,
|
||||||
filters: [],
|
filters: [],
|
||||||
schema: {
|
schema: {
|
||||||
name: {
|
name: {
|
||||||
|
@ -88,7 +88,7 @@ describe("/views", () => {
|
||||||
|
|
||||||
describe("fetch", () => {
|
describe("fetch", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
model = await createModel(request, app._id, instance._id);
|
table = await createTable(request, app._id, instance._id);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns only custom views", async () => {
|
it("returns only custom views", async () => {
|
||||||
|
@ -105,21 +105,21 @@ describe("/views", () => {
|
||||||
|
|
||||||
describe("query", () => {
|
describe("query", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
model = await createModel(request, app._id, instance._id);
|
table = await createTable(request, app._id, instance._id);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns data for the created view", async () => {
|
it("returns data for the created view", async () => {
|
||||||
await createView()
|
await createView()
|
||||||
await createRecord({
|
await createRecord({
|
||||||
modelId: model._id,
|
tableId: table._id,
|
||||||
Price: 1000
|
Price: 1000
|
||||||
})
|
})
|
||||||
await createRecord({
|
await createRecord({
|
||||||
modelId: model._id,
|
tableId: table._id,
|
||||||
Price: 2000
|
Price: 2000
|
||||||
})
|
})
|
||||||
await createRecord({
|
await createRecord({
|
||||||
modelId: model._id,
|
tableId: table._id,
|
||||||
Price: 4000
|
Price: 4000
|
||||||
})
|
})
|
||||||
const res = await request
|
const res = await request
|
||||||
|
@ -136,20 +136,20 @@ describe("/views", () => {
|
||||||
name: "TestView",
|
name: "TestView",
|
||||||
field: "Price",
|
field: "Price",
|
||||||
groupBy: "Category",
|
groupBy: "Category",
|
||||||
modelId: model._id
|
tableId: table._id
|
||||||
})
|
})
|
||||||
await createRecord({
|
await createRecord({
|
||||||
modelId: model._id,
|
tableId: table._id,
|
||||||
Price: 1000,
|
Price: 1000,
|
||||||
Category: "One"
|
Category: "One"
|
||||||
})
|
})
|
||||||
await createRecord({
|
await createRecord({
|
||||||
modelId: model._id,
|
tableId: table._id,
|
||||||
Price: 2000,
|
Price: 2000,
|
||||||
Category: "One"
|
Category: "One"
|
||||||
})
|
})
|
||||||
await createRecord({
|
await createRecord({
|
||||||
modelId: model._id,
|
tableId: table._id,
|
||||||
Price: 4000,
|
Price: 4000,
|
||||||
Category: "Two"
|
Category: "Two"
|
||||||
})
|
})
|
||||||
|
|
|
@ -85,32 +85,32 @@ 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 record input like a save or update record we need to clean the inputs against a schema that is not part of
|
||||||
* the automation but is instead part of the Table/Model. This function will get the model 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 record.
|
||||||
*
|
*
|
||||||
* @param {string} instanceId The instance which the Table/Model is contained under.
|
* @param {string} instanceId The instance which the Table/Table is contained under.
|
||||||
* @param {string} modelId The ID of the Table/Model 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} record The input record structure which requires clean-up after having been through mustache statements.
|
||||||
* @returns {Promise<Object>} The cleaned up records object, will should now have all the required primitive types.
|
* @returns {Promise<Object>} The cleaned up records object, will should now have all the required primitive types.
|
||||||
*/
|
*/
|
||||||
module.exports.cleanUpRecord = async (instanceId, modelId, record) => {
|
module.exports.cleanUpRecord = async (instanceId, tableId, record) => {
|
||||||
const db = new CouchDB(instanceId)
|
const db = new CouchDB(instanceId)
|
||||||
const model = await db.get(modelId)
|
const table = await db.get(tableId)
|
||||||
|
|
||||||
return module.exports.cleanInputValues(record, { properties: model.schema })
|
return module.exports.cleanInputValues(record, { properties: table.schema })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A utility function for the cleanUpRecord, which can be used if only the record ID is known (not the model ID) to clean
|
* A utility function for the cleanUpRecord, which can be used if only the record ID is known (not the table ID) to clean
|
||||||
* up a record after mustache statements have been replaced. This is specifically useful for the update record action.
|
* up a record after mustache statements have been replaced. This is specifically useful for the update record action.
|
||||||
*
|
*
|
||||||
* @param {string} instanceId The instance which the Table/Model 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 modelId will be extracted, to get the Table/Model schema.
|
* @param {string} recordId The ID of the record from which the tableId will be extracted, to get the Table/Table schema.
|
||||||
* @param {object} record The input record structure which requires clean-up after having been through mustache statements.
|
* @param {object} record The input record structure which requires clean-up after having been through mustache statements.
|
||||||
* @returns {Promise<Object>} The cleaned up records object, which will now have all the required primitive types.
|
* @returns {Promise<Object>} The cleaned up records object, which will now have all the required primitive types.
|
||||||
*/
|
*/
|
||||||
module.exports.cleanUpRecordById = async (instanceId, recordId, record) => {
|
module.exports.cleanUpRecordById = async (instanceId, recordId, record) => {
|
||||||
const db = new CouchDB(instanceId)
|
const db = new CouchDB(instanceId)
|
||||||
const foundRecord = await db.get(recordId)
|
const foundRecord = await db.get(recordId)
|
||||||
return module.exports.cleanUpRecord(instanceId, foundRecord.modelId, record)
|
return module.exports.cleanUpRecord(instanceId, foundRecord.tableId, record)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ const usage = require("../../utilities/usageQuota")
|
||||||
|
|
||||||
module.exports.definition = {
|
module.exports.definition = {
|
||||||
name: "Create Row",
|
name: "Create Row",
|
||||||
tagline: "Create a {{inputs.enriched.model.name}} row",
|
tagline: "Create a {{inputs.enriched.table.name}} row",
|
||||||
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",
|
||||||
|
@ -17,14 +17,14 @@ module.exports.definition = {
|
||||||
record: {
|
record: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
modelId: {
|
tableId: {
|
||||||
type: "string",
|
type: "string",
|
||||||
customType: "model",
|
customType: "table",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
customType: "record",
|
customType: "record",
|
||||||
title: "Table",
|
title: "Table",
|
||||||
required: ["modelId"],
|
required: ["tableId"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
required: ["record"],
|
required: ["record"],
|
||||||
|
@ -60,18 +60,18 @@ 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.modelId == null) {
|
if (inputs.record == null || inputs.record.tableId == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
inputs.record = await automationUtils.cleanUpRecord(
|
inputs.record = await automationUtils.cleanUpRecord(
|
||||||
instanceId,
|
instanceId,
|
||||||
inputs.record.modelId,
|
inputs.record.tableId,
|
||||||
inputs.record
|
inputs.record
|
||||||
)
|
)
|
||||||
// have to clean up the record, remove the model from it
|
// have to clean up the record, remove the table from it
|
||||||
const ctx = {
|
const ctx = {
|
||||||
params: {
|
params: {
|
||||||
modelId: inputs.record.modelId,
|
tableId: inputs.record.tableId,
|
||||||
},
|
},
|
||||||
request: {
|
request: {
|
||||||
body: inputs.record,
|
body: inputs.record,
|
||||||
|
|
|
@ -6,16 +6,16 @@ module.exports.definition = {
|
||||||
description: "Delete a row from your database",
|
description: "Delete a row from your database",
|
||||||
icon: "ri-delete-bin-line",
|
icon: "ri-delete-bin-line",
|
||||||
name: "Delete Row",
|
name: "Delete Row",
|
||||||
tagline: "Delete a {{inputs.enriched.model.name}} row",
|
tagline: "Delete a {{inputs.enriched.table.name}} row",
|
||||||
type: "ACTION",
|
type: "ACTION",
|
||||||
stepId: "DELETE_RECORD",
|
stepId: "DELETE_RECORD",
|
||||||
inputs: {},
|
inputs: {},
|
||||||
schema: {
|
schema: {
|
||||||
inputs: {
|
inputs: {
|
||||||
properties: {
|
properties: {
|
||||||
modelId: {
|
tableId: {
|
||||||
type: "string",
|
type: "string",
|
||||||
customType: "model",
|
customType: "table",
|
||||||
title: "Table",
|
title: "Table",
|
||||||
},
|
},
|
||||||
id: {
|
id: {
|
||||||
|
@ -27,7 +27,7 @@ module.exports.definition = {
|
||||||
title: "Row Revision",
|
title: "Row Revision",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
required: ["modelId", "id", "revision"],
|
required: ["tableId", "id", "revision"],
|
||||||
},
|
},
|
||||||
outputs: {
|
outputs: {
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -57,7 +57,7 @@ module.exports.run = async function({ inputs, instanceId, apiKey }) {
|
||||||
}
|
}
|
||||||
let ctx = {
|
let ctx = {
|
||||||
params: {
|
params: {
|
||||||
modelId: inputs.modelId,
|
tableId: inputs.tableId,
|
||||||
recordId: inputs.id,
|
recordId: inputs.id,
|
||||||
revId: inputs.revision,
|
revId: inputs.revision,
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,7 +3,7 @@ const automationUtils = require("../automationUtils")
|
||||||
|
|
||||||
module.exports.definition = {
|
module.exports.definition = {
|
||||||
name: "Update Row",
|
name: "Update Row",
|
||||||
tagline: "Update a {{inputs.enriched.model.name}} record",
|
tagline: "Update a {{inputs.enriched.table.name}} record",
|
||||||
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",
|
||||||
|
@ -70,7 +70,7 @@ module.exports.run = async function({ inputs, instanceId }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// have to clean up the record, remove the model from it
|
// have to clean up the record, remove the table from it
|
||||||
const ctx = {
|
const ctx = {
|
||||||
params: {
|
params: {
|
||||||
id: inputs.recordId,
|
id: inputs.recordId,
|
||||||
|
|
|
@ -15,20 +15,20 @@ const BUILTIN_DEFINITIONS = {
|
||||||
name: "Row Saved",
|
name: "Row Saved",
|
||||||
event: "record:save",
|
event: "record:save",
|
||||||
icon: "ri-save-line",
|
icon: "ri-save-line",
|
||||||
tagline: "Row is added to {{inputs.enriched.model.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: "RECORD_SAVED",
|
||||||
inputs: {},
|
inputs: {},
|
||||||
schema: {
|
schema: {
|
||||||
inputs: {
|
inputs: {
|
||||||
properties: {
|
properties: {
|
||||||
modelId: {
|
tableId: {
|
||||||
type: "string",
|
type: "string",
|
||||||
customType: "model",
|
customType: "table",
|
||||||
title: "Table",
|
title: "Table",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
required: ["modelId"],
|
required: ["tableId"],
|
||||||
},
|
},
|
||||||
outputs: {
|
outputs: {
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -55,20 +55,20 @@ const BUILTIN_DEFINITIONS = {
|
||||||
name: "Row Deleted",
|
name: "Row Deleted",
|
||||||
event: "record:delete",
|
event: "record:delete",
|
||||||
icon: "ri-delete-bin-line",
|
icon: "ri-delete-bin-line",
|
||||||
tagline: "Row is deleted from {{inputs.enriched.model.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: "RECORD_DELETED",
|
||||||
inputs: {},
|
inputs: {},
|
||||||
schema: {
|
schema: {
|
||||||
inputs: {
|
inputs: {
|
||||||
properties: {
|
properties: {
|
||||||
modelId: {
|
tableId: {
|
||||||
type: "string",
|
type: "string",
|
||||||
customType: "model",
|
customType: "table",
|
||||||
title: "Table",
|
title: "Table",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
required: ["modelId"],
|
required: ["tableId"],
|
||||||
},
|
},
|
||||||
outputs: {
|
outputs: {
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -108,7 +108,7 @@ async function queueRelevantRecordAutomations(event, eventType) {
|
||||||
if (
|
if (
|
||||||
!automation.live ||
|
!automation.live ||
|
||||||
!automationTrigger.inputs ||
|
!automationTrigger.inputs ||
|
||||||
automationTrigger.inputs.modelId !== event.record.modelId
|
automationTrigger.inputs.tableId !== event.record.tableId
|
||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -117,14 +117,14 @@ async function queueRelevantRecordAutomations(event, eventType) {
|
||||||
}
|
}
|
||||||
|
|
||||||
emitter.on("record:save", async function(event) {
|
emitter.on("record:save", async function(event) {
|
||||||
if (!event || !event.record || !event.record.modelId) {
|
if (!event || !event.record || !event.record.tableId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await queueRelevantRecordAutomations(event, "record:save")
|
await queueRelevantRecordAutomations(event, "record:save")
|
||||||
})
|
})
|
||||||
|
|
||||||
emitter.on("record:delete", async function(event) {
|
emitter.on("record:delete", async function(event) {
|
||||||
if (!event || !event.record || !event.record.modelId) {
|
if (!event || !event.record || !event.record.tableId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await queueRelevantRecordAutomations(event, "record:delete")
|
await queueRelevantRecordAutomations(event, "record:delete")
|
||||||
|
@ -132,16 +132,16 @@ emitter.on("record:delete", async function(event) {
|
||||||
|
|
||||||
async function fillRecordOutput(automation, params) {
|
async function fillRecordOutput(automation, params) {
|
||||||
let triggerSchema = automation.definition.trigger
|
let triggerSchema = automation.definition.trigger
|
||||||
let modelId = triggerSchema.inputs.modelId
|
let tableId = triggerSchema.inputs.tableId
|
||||||
const db = new CouchDB(params.instanceId)
|
const db = new CouchDB(params.instanceId)
|
||||||
try {
|
try {
|
||||||
let model = await db.get(modelId)
|
let table = await db.get(tableId)
|
||||||
let record = {}
|
let record = {}
|
||||||
for (let schemaKey of Object.keys(model.schema)) {
|
for (let schemaKey of Object.keys(table.schema)) {
|
||||||
if (params[schemaKey] != null) {
|
if (params[schemaKey] != null) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let propSchema = model.schema[schemaKey]
|
let propSchema = table.schema[schemaKey]
|
||||||
switch (propSchema.constraints.type) {
|
switch (propSchema.constraints.type) {
|
||||||
case "string":
|
case "string":
|
||||||
record[schemaKey] = FAKE_STRING
|
record[schemaKey] = FAKE_STRING
|
||||||
|
@ -159,7 +159,7 @@ async function fillRecordOutput(automation, params) {
|
||||||
}
|
}
|
||||||
params.record = record
|
params.record = record
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw "Failed to find model for trigger"
|
throw "Failed to find table for trigger"
|
||||||
}
|
}
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
@ -169,7 +169,7 @@ module.exports.externalTrigger = async function(automation, params) {
|
||||||
if (
|
if (
|
||||||
automation.definition != null &&
|
automation.definition != null &&
|
||||||
automation.definition.trigger != null &&
|
automation.definition.trigger != null &&
|
||||||
automation.definition.trigger.inputs.modelId != null
|
automation.definition.trigger.inputs.tableId != null
|
||||||
) {
|
) {
|
||||||
params = await fillRecordOutput(automation, params)
|
params = await fillRecordOutput(automation, params)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ const { generateLinkID } = require("../utils")
|
||||||
* Creates a new link document structure which can be put to the database. It is important to
|
* Creates a new link document structure which can be put to the database. It is important to
|
||||||
* note that while this talks about linker/linked the link is bi-directional and for all intent
|
* note that while this talks about linker/linked the link is bi-directional and for all intent
|
||||||
* and purposes it does not matter from which direction the link was initiated.
|
* and purposes it does not matter from which direction the link was initiated.
|
||||||
* @param {string} modelId1 The ID of the first model (the linker).
|
* @param {string} tableId1 The ID of the first table (the linker).
|
||||||
* @param {string} modelId2 The ID of the second model (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} recordId1 The ID of the record which is acting as the linker.
|
||||||
|
@ -15,65 +15,65 @@ const { generateLinkID } = require("../utils")
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function LinkDocument(
|
function LinkDocument(
|
||||||
modelId1,
|
tableId1,
|
||||||
fieldName1,
|
fieldName1,
|
||||||
recordId1,
|
recordId1,
|
||||||
modelId2,
|
tableId2,
|
||||||
fieldName2,
|
fieldName2,
|
||||||
recordId2
|
recordId2
|
||||||
) {
|
) {
|
||||||
// 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(modelId1, modelId2, recordId1, recordId2)
|
this._id = generateLinkID(tableId1, tableId2, recordId1, recordId2)
|
||||||
// required for referencing in view
|
// required for referencing in view
|
||||||
this.type = "link"
|
this.type = "link"
|
||||||
this.doc1 = {
|
this.doc1 = {
|
||||||
modelId: modelId1,
|
tableId: tableId1,
|
||||||
fieldName: fieldName1,
|
fieldName: fieldName1,
|
||||||
recordId: recordId1,
|
recordId: recordId1,
|
||||||
}
|
}
|
||||||
this.doc2 = {
|
this.doc2 = {
|
||||||
modelId: modelId2,
|
tableId: tableId2,
|
||||||
fieldName: fieldName2,
|
fieldName: fieldName2,
|
||||||
recordId: recordId2,
|
recordId: recordId2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LinkController {
|
class LinkController {
|
||||||
constructor({ instanceId, modelId, record, model, oldModel }) {
|
constructor({ instanceId, tableId, record, table, oldTable }) {
|
||||||
this._instanceId = instanceId
|
this._instanceId = instanceId
|
||||||
this._db = new CouchDB(instanceId)
|
this._db = new CouchDB(instanceId)
|
||||||
this._modelId = modelId
|
this._tableId = tableId
|
||||||
this._record = record
|
this._record = record
|
||||||
this._model = model
|
this._table = table
|
||||||
this._oldModel = oldModel
|
this._oldTable = oldTable
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the model, if it was not already found in the eventData.
|
* Retrieves the table, if it was not already found in the eventData.
|
||||||
* @returns {Promise<object>} This will return a model based on the event data, either
|
* @returns {Promise<object>} This will return a table based on the event data, either
|
||||||
* if it was in the event already, or it uses the specified modelId to get it.
|
* if it was in the event already, or it uses the specified tableId to get it.
|
||||||
*/
|
*/
|
||||||
async model() {
|
async table() {
|
||||||
if (this._model == null) {
|
if (this._table == null) {
|
||||||
this._model =
|
this._table =
|
||||||
this._model == null ? await this._db.get(this._modelId) : this._model
|
this._table == null ? await this._db.get(this._tableId) : this._table
|
||||||
}
|
}
|
||||||
return this._model
|
return this._table
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the model this was constructed with has any linking columns currently.
|
* Checks if the table this was constructed with has any linking columns currently.
|
||||||
* If the model has not been retrieved this will retrieve it based on the eventData.
|
* If the table has not been retrieved this will retrieve it based on the eventData.
|
||||||
* @params {object|null} model If a model that is not known to the link controller is to be tested.
|
* @params {object|null} table If a table that is not known to the link controller is to be tested.
|
||||||
* @returns {Promise<boolean>} True if there are any linked fields, otherwise it will return
|
* @returns {Promise<boolean>} True if there are any linked fields, otherwise it will return
|
||||||
* false.
|
* false.
|
||||||
*/
|
*/
|
||||||
async doesModelHaveLinkedFields(model = null) {
|
async doesTableHaveLinkedFields(table = null) {
|
||||||
if (model == null) {
|
if (table == null) {
|
||||||
model = await this.model()
|
table = await this.table()
|
||||||
}
|
}
|
||||||
for (let fieldName of Object.keys(model.schema)) {
|
for (let fieldName of Object.keys(table.schema)) {
|
||||||
const { type } = model.schema[fieldName]
|
const { type } = table.schema[fieldName]
|
||||||
if (type === "link") {
|
if (type === "link") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ class LinkController {
|
||||||
getRecordLinkDocs(recordId) {
|
getRecordLinkDocs(recordId) {
|
||||||
return getLinkDocuments({
|
return getLinkDocuments({
|
||||||
instanceId: this._instanceId,
|
instanceId: this._instanceId,
|
||||||
modelId: this._modelId,
|
tableId: this._tableId,
|
||||||
recordId,
|
recordId,
|
||||||
includeDocs: IncludeDocs.INCLUDE,
|
includeDocs: IncludeDocs.INCLUDE,
|
||||||
})
|
})
|
||||||
|
@ -96,15 +96,15 @@ class LinkController {
|
||||||
/**
|
/**
|
||||||
* Utility function for main getLinkDocuments function - refer to it for functionality.
|
* Utility function for main getLinkDocuments function - refer to it for functionality.
|
||||||
*/
|
*/
|
||||||
getModelLinkDocs() {
|
getTableLinkDocs() {
|
||||||
return getLinkDocuments({
|
return getLinkDocuments({
|
||||||
instanceId: this._instanceId,
|
instanceId: this._instanceId,
|
||||||
modelId: this._modelId,
|
tableId: this._tableId,
|
||||||
includeDocs: IncludeDocs.INCLUDE,
|
includeDocs: IncludeDocs.INCLUDE,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// all operations here will assume that the model
|
// all operations here will assume that the table
|
||||||
// this operation is related to has linked records
|
// this operation is related to has linked records
|
||||||
/**
|
/**
|
||||||
* When a record is saved this will carry out the necessary operations to make sure
|
* When a record is saved this will carry out the necessary operations to make sure
|
||||||
|
@ -113,15 +113,15 @@ class LinkController {
|
||||||
* have also been created.
|
* have also been created.
|
||||||
*/
|
*/
|
||||||
async recordSaved() {
|
async recordSaved() {
|
||||||
const model = await this.model()
|
const table = await this.table()
|
||||||
const record = this._record
|
const record = this._record
|
||||||
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.getRecordLinkDocs(record._id)
|
||||||
for (let fieldName of Object.keys(model.schema)) {
|
for (let fieldName of Object.keys(table.schema)) {
|
||||||
// get the links this record wants to make
|
// get the links this record wants to make
|
||||||
const recordField = record[fieldName]
|
const recordField = record[fieldName]
|
||||||
const field = model.schema[fieldName]
|
const field = table.schema[fieldName]
|
||||||
if (field.type === "link" && recordField != null) {
|
if (field.type === "link" && recordField != null) {
|
||||||
// check which links actual pertain to the update in this record
|
// check which links actual pertain to the update in this record
|
||||||
const thisFieldLinkDocs = linkDocs.filter(
|
const thisFieldLinkDocs = linkDocs.filter(
|
||||||
|
@ -139,10 +139,10 @@ class LinkController {
|
||||||
if (linkId && linkId !== "" && linkDocIds.indexOf(linkId) === -1) {
|
if (linkId && linkId !== "" && linkDocIds.indexOf(linkId) === -1) {
|
||||||
operations.push(
|
operations.push(
|
||||||
new LinkDocument(
|
new LinkDocument(
|
||||||
model._id,
|
table._id,
|
||||||
fieldName,
|
fieldName,
|
||||||
record._id,
|
record._id,
|
||||||
field.modelId,
|
field.tableId,
|
||||||
field.fieldName,
|
field.fieldName,
|
||||||
linkId
|
linkId
|
||||||
)
|
)
|
||||||
|
@ -193,17 +193,17 @@ class LinkController {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a field from a model as well as any linked records that pertained to it.
|
* Remove a field from a table as well as any linked records that pertained to it.
|
||||||
* @param {string} fieldName The field to be removed from the model.
|
* @param {string} fieldName The field to be removed from the table.
|
||||||
* @returns {Promise<void>} The model has now been updated.
|
* @returns {Promise<void>} The table has now been updated.
|
||||||
*/
|
*/
|
||||||
async removeFieldFromModel(fieldName) {
|
async removeFieldFromTable(fieldName) {
|
||||||
let oldModel = this._oldModel
|
let oldTable = this._oldTable
|
||||||
let field = oldModel.schema[fieldName]
|
let field = oldTable.schema[fieldName]
|
||||||
const linkDocs = await this.getModelLinkDocs()
|
const linkDocs = await this.getTableLinkDocs()
|
||||||
let toDelete = linkDocs.filter(linkDoc => {
|
let toDelete = linkDocs.filter(linkDoc => {
|
||||||
let correctFieldName =
|
let correctFieldName =
|
||||||
linkDoc.doc1.modelId === oldModel._id
|
linkDoc.doc1.tableId === oldTable._id
|
||||||
? linkDoc.doc1.fieldName
|
? linkDoc.doc1.fieldName
|
||||||
: linkDoc.doc2.fieldName
|
: linkDoc.doc2.fieldName
|
||||||
return correctFieldName === fieldName
|
return correctFieldName === fieldName
|
||||||
|
@ -216,83 +216,83 @@ class LinkController {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
// remove schema from other model
|
// remove schema from other table
|
||||||
let linkedModel = await this._db.get(field.modelId)
|
let linkedTable = await this._db.get(field.tableId)
|
||||||
delete linkedModel.schema[field.fieldName]
|
delete linkedTable.schema[field.fieldName]
|
||||||
this._db.put(linkedModel)
|
this._db.put(linkedTable)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When a model is saved this will carry out the necessary operations to make sure
|
* When a table is saved this will carry out the necessary operations to make sure
|
||||||
* any linked models are notified and updated correctly.
|
* any linked tables are notified and updated correctly.
|
||||||
* @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. Also returns the model that was operated on.
|
* be accurate. Also returns the table that was operated on.
|
||||||
*/
|
*/
|
||||||
async modelSaved() {
|
async tableSaved() {
|
||||||
const model = await this.model()
|
const table = await this.table()
|
||||||
const schema = model.schema
|
const schema = table.schema
|
||||||
for (let fieldName of Object.keys(schema)) {
|
for (let fieldName of Object.keys(schema)) {
|
||||||
const field = schema[fieldName]
|
const field = schema[fieldName]
|
||||||
if (field.type === "link") {
|
if (field.type === "link") {
|
||||||
// create the link field in the other model
|
// create the link field in the other table
|
||||||
const linkedModel = await this._db.get(field.modelId)
|
const linkedTable = await this._db.get(field.tableId)
|
||||||
linkedModel.schema[field.fieldName] = {
|
linkedTable.schema[field.fieldName] = {
|
||||||
name: field.fieldName,
|
name: field.fieldName,
|
||||||
type: "link",
|
type: "link",
|
||||||
// these are the props of the table that initiated the link
|
// these are the props of the table that initiated the link
|
||||||
modelId: model._id,
|
tableId: table._id,
|
||||||
fieldName: fieldName,
|
fieldName: fieldName,
|
||||||
}
|
}
|
||||||
await this._db.put(linkedModel)
|
await this._db.put(linkedTable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return model
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update a model, this means if a field is removed need to handle removing from other table and removing
|
* Update a table, this means if a field is removed need to handle removing from other table and removing
|
||||||
* any link docs that pertained to it.
|
* any link docs that pertained to it.
|
||||||
* @returns {Promise<Object>} The model which has been saved, same response as with the modelSaved function.
|
* @returns {Promise<Object>} The table which has been saved, same response as with the tableSaved function.
|
||||||
*/
|
*/
|
||||||
async modelUpdated() {
|
async tableUpdated() {
|
||||||
const oldModel = this._oldModel
|
const oldTable = this._oldTable
|
||||||
// first start by checking if any link columns have been deleted
|
// first start by checking if any link columns have been deleted
|
||||||
const newModel = await this.model()
|
const newTable = await this.table()
|
||||||
for (let fieldName of Object.keys(oldModel.schema)) {
|
for (let fieldName of Object.keys(oldTable.schema)) {
|
||||||
const field = oldModel.schema[fieldName]
|
const field = oldTable.schema[fieldName]
|
||||||
// this field has been removed from the model schema
|
// this field has been removed from the table schema
|
||||||
if (field.type === "link" && newModel.schema[fieldName] == null) {
|
if (field.type === "link" && newTable.schema[fieldName] == null) {
|
||||||
await this.removeFieldFromModel(fieldName)
|
await this.removeFieldFromTable(fieldName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// now handle as if its a new save
|
// now handle as if its a new save
|
||||||
return this.modelSaved()
|
return this.tableSaved()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When a model is deleted this will carry out the necessary operations to make sure
|
* When a table is deleted this will carry out the necessary operations to make sure
|
||||||
* any linked models have the joining column correctly removed as well as removing any
|
* any linked tables have the joining column correctly removed as well as removing any
|
||||||
* now stale linking documents.
|
* now stale linking documents.
|
||||||
* @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. Also returns the model that was operated on.
|
* be accurate. Also returns the table that was operated on.
|
||||||
*/
|
*/
|
||||||
async modelDeleted() {
|
async tableDeleted() {
|
||||||
const model = await this.model()
|
const table = await this.table()
|
||||||
const schema = model.schema
|
const schema = table.schema
|
||||||
for (let fieldName of Object.keys(schema)) {
|
for (let fieldName of Object.keys(schema)) {
|
||||||
const field = schema[fieldName]
|
const field = schema[fieldName]
|
||||||
if (field.type === "link") {
|
if (field.type === "link") {
|
||||||
const linkedModel = await this._db.get(field.modelId)
|
const linkedTable = await this._db.get(field.tableId)
|
||||||
delete linkedModel.schema[model.name]
|
delete linkedTable.schema[table.name]
|
||||||
await this._db.put(linkedModel)
|
await this._db.put(linkedTable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// need to get the full link docs to delete them
|
// need to get the full link docs to delete them
|
||||||
const linkDocs = await this.getModelLinkDocs()
|
const linkDocs = await this.getTableLinkDocs()
|
||||||
if (linkDocs.length === 0) {
|
if (linkDocs.length === 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
// get link docs for this model and configure for deletion
|
// get link docs for this table and configure for deletion
|
||||||
const toDelete = linkDocs.map(doc => {
|
const toDelete = linkDocs.map(doc => {
|
||||||
return {
|
return {
|
||||||
...doc,
|
...doc,
|
||||||
|
@ -300,7 +300,7 @@ class LinkController {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
await this._db.bulkDocs(toDelete)
|
await this._db.bulkDocs(toDelete)
|
||||||
return model
|
return table
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,9 @@ const EventType = {
|
||||||
RECORD_SAVE: "record:save",
|
RECORD_SAVE: "record:save",
|
||||||
RECORD_UPDATE: "record:update",
|
RECORD_UPDATE: "record:update",
|
||||||
RECORD_DELETE: "record:delete",
|
RECORD_DELETE: "record:delete",
|
||||||
MODEL_SAVE: "model:save",
|
TABLE_SAVE: "table:save",
|
||||||
MODEL_UPDATED: "model:updated",
|
TABLE_UPDATED: "table:updated",
|
||||||
MODEL_DELETE: "model:delete",
|
TABLE_DELETE: "table:delete",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.EventType = EventType
|
exports.EventType = EventType
|
||||||
|
@ -22,37 +22,37 @@ exports.getLinkDocuments = getLinkDocuments
|
||||||
exports.createLinkView = createLinkView
|
exports.createLinkView = createLinkView
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update link documents for a record or model - this is to be called by the API controller when a change is occurring.
|
* Update link documents for a record 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} modelId The ID of the of the model 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} record The record which is changing, e.g. created, updated or deleted.
|
||||||
* @param {object|null} model If the model 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} oldModel If the model is being updated then the old model 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 record for
|
||||||
* record operations and the model for model operations.
|
* record operations and the table for table operations.
|
||||||
*/
|
*/
|
||||||
exports.updateLinks = async function({
|
exports.updateLinks = async function({
|
||||||
eventType,
|
eventType,
|
||||||
instanceId,
|
instanceId,
|
||||||
record,
|
record,
|
||||||
modelId,
|
tableId,
|
||||||
model,
|
table,
|
||||||
oldModel,
|
oldTable,
|
||||||
}) {
|
}) {
|
||||||
if (instanceId == null) {
|
if (instanceId == null) {
|
||||||
throw "Cannot operate without an instance ID."
|
throw "Cannot operate without an instance ID."
|
||||||
}
|
}
|
||||||
// make sure model ID is set
|
// make sure table ID is set
|
||||||
if (modelId == null && model != null) {
|
if (tableId == null && table != null) {
|
||||||
arguments[0].modelId = model._id
|
arguments[0].tableId = table._id
|
||||||
}
|
}
|
||||||
let linkController = new LinkController(arguments[0])
|
let linkController = new LinkController(arguments[0])
|
||||||
if (
|
if (
|
||||||
!(await linkController.doesModelHaveLinkedFields()) &&
|
!(await linkController.doesTableHaveLinkedFields()) &&
|
||||||
(oldModel == null ||
|
(oldTable == null ||
|
||||||
!(await linkController.doesModelHaveLinkedFields(oldModel)))
|
!(await linkController.doesTableHaveLinkedFields(oldTable)))
|
||||||
) {
|
) {
|
||||||
return record
|
return record
|
||||||
}
|
}
|
||||||
|
@ -62,12 +62,12 @@ exports.updateLinks = async function({
|
||||||
return await linkController.recordSaved()
|
return await linkController.recordSaved()
|
||||||
case EventType.RECORD_DELETE:
|
case EventType.RECORD_DELETE:
|
||||||
return await linkController.recordDeleted()
|
return await linkController.recordDeleted()
|
||||||
case EventType.MODEL_SAVE:
|
case EventType.TABLE_SAVE:
|
||||||
return await linkController.modelSaved()
|
return await linkController.tableSaved()
|
||||||
case EventType.MODEL_UPDATED:
|
case EventType.TABLE_UPDATED:
|
||||||
return await linkController.modelUpdated()
|
return await linkController.tableUpdated()
|
||||||
case EventType.MODEL_DELETE:
|
case EventType.TABLE_DELETE:
|
||||||
return await linkController.modelDeleted()
|
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 record handler requires update."
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ exports.attachLinkInfo = async (instanceId, records) => {
|
||||||
records.map(record =>
|
records.map(record =>
|
||||||
getLinkDocuments({
|
getLinkDocuments({
|
||||||
instanceId,
|
instanceId,
|
||||||
modelId: record.modelId,
|
tableId: record.tableId,
|
||||||
recordId: record._id,
|
recordId: record._id,
|
||||||
includeDocs: IncludeDocs.EXCLUDE,
|
includeDocs: IncludeDocs.EXCLUDE,
|
||||||
})
|
})
|
||||||
|
|
|
@ -25,11 +25,11 @@ 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.modelId, doc1.recordId], {
|
emit([doc1.tableId, doc1.recordId], {
|
||||||
id: doc2.recordId,
|
id: doc2.recordId,
|
||||||
fieldName: doc1.fieldName,
|
fieldName: doc1.fieldName,
|
||||||
})
|
})
|
||||||
emit([doc2.modelId, doc2.recordId], {
|
emit([doc2.tableId, doc2.recordId], {
|
||||||
id: doc1.recordId,
|
id: doc1.recordId,
|
||||||
fieldName: doc2.fieldName,
|
fieldName: doc2.fieldName,
|
||||||
})
|
})
|
||||||
|
@ -46,11 +46,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 records.
|
||||||
* @param {string} modelId The model which we are searching for linked records against.
|
* @param {string} tableId The table which we are searching for linked records 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} recordId The ID of the record which we want to find linking documents for -
|
||||||
* if this is not specified then it will assume model 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
|
||||||
* use this if actually interested in the docs themselves.
|
* use this if actually interested in the docs themselves.
|
||||||
|
@ -59,18 +59,18 @@ exports.createLinkView = async instanceId => {
|
||||||
*/
|
*/
|
||||||
exports.getLinkDocuments = async function({
|
exports.getLinkDocuments = async function({
|
||||||
instanceId,
|
instanceId,
|
||||||
modelId,
|
tableId,
|
||||||
recordId,
|
recordId,
|
||||||
includeDocs,
|
includeDocs,
|
||||||
}) {
|
}) {
|
||||||
const db = new CouchDB(instanceId)
|
const db = new CouchDB(instanceId)
|
||||||
let params
|
let params
|
||||||
if (recordId != null) {
|
if (recordId != null) {
|
||||||
params = { key: [modelId, recordId] }
|
params = { key: [tableId, recordId] }
|
||||||
}
|
}
|
||||||
// only model is known
|
// only table is known
|
||||||
else {
|
else {
|
||||||
params = { startKey: [modelId], endKey: [modelId, {}] }
|
params = { startKey: [tableId], endKey: [tableId, {}] }
|
||||||
}
|
}
|
||||||
params.include_docs = !!includeDocs
|
params.include_docs = !!includeDocs
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -4,7 +4,7 @@ const UNICODE_MAX = "\ufff0"
|
||||||
const SEPARATOR = "_"
|
const SEPARATOR = "_"
|
||||||
|
|
||||||
const DocumentTypes = {
|
const DocumentTypes = {
|
||||||
MODEL: "mo",
|
TABLE: "ta",
|
||||||
RECORD: "re",
|
RECORD: "re",
|
||||||
USER: "us",
|
USER: "us",
|
||||||
AUTOMATION: "au",
|
AUTOMATION: "au",
|
||||||
|
@ -18,11 +18,11 @@ 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. models, 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 records 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, model and so on.
|
* link, app, table and so on.
|
||||||
* @param {string|null} docId The ID of the document minus its type - this is only needed if looking
|
* @param {string|null} docId The ID of the document minus its type - this is only needed if looking
|
||||||
* for a singular document.
|
* for a singular document.
|
||||||
* @param {object} otherProps Add any other properties onto the request, e.g. include_docs.
|
* @param {object} otherProps Add any other properties onto the request, e.g. include_docs.
|
||||||
|
@ -40,46 +40,46 @@ function getDocParams(docType, docId = null, otherProps = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets parameters for retrieving models, this is a utility function for the getDocParams function.
|
* Gets parameters for retrieving tables, this is a utility function for the getDocParams function.
|
||||||
*/
|
*/
|
||||||
exports.getModelParams = (modelId = null, otherProps = {}) => {
|
exports.getTableParams = (tableId = null, otherProps = {}) => {
|
||||||
return getDocParams(DocumentTypes.MODEL, modelId, otherProps)
|
return getDocParams(DocumentTypes.TABLE, tableId, otherProps)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a new model ID.
|
* Generates a new table ID.
|
||||||
* @returns {string} The new model ID which the model doc can be stored under.
|
* @returns {string} The new table ID which the table doc can be stored under.
|
||||||
*/
|
*/
|
||||||
exports.generateModelID = () => {
|
exports.generateTableID = () => {
|
||||||
return `${DocumentTypes.MODEL}${SEPARATOR}${newid()}`
|
return `${DocumentTypes.TABLE}${SEPARATOR}${newid()}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the DB allDocs/query params for retrieving a record.
|
* Gets the DB allDocs/query params for retrieving a record.
|
||||||
* @param {string} modelId The model in which the records have been stored.
|
* @param {string} tableId The table in which the records have been stored.
|
||||||
* @param {string|null} recordId The ID of the record which is being specifically queried for. This can be
|
* @param {string|null} recordId The ID of the record which is being specifically queried for. This can be
|
||||||
* left null to get all the records in the model.
|
* left null to get all the records 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 = (modelId, recordId = null, otherProps = {}) => {
|
exports.getRecordParams = (tableId, recordId = null, otherProps = {}) => {
|
||||||
if (modelId == null) {
|
if (tableId == null) {
|
||||||
throw "Cannot build params for records without a model ID"
|
throw "Cannot build params for records without a table ID"
|
||||||
}
|
}
|
||||||
const endOfKey =
|
const endOfKey =
|
||||||
recordId == null
|
recordId == null
|
||||||
? `${modelId}${SEPARATOR}`
|
? `${tableId}${SEPARATOR}`
|
||||||
: `${modelId}${SEPARATOR}${recordId}`
|
: `${tableId}${SEPARATOR}${recordId}`
|
||||||
return getDocParams(DocumentTypes.RECORD, endOfKey, otherProps)
|
return getDocParams(DocumentTypes.RECORD, endOfKey, otherProps)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a new record ID for the specified model.
|
* Gets a new record ID for the specified table.
|
||||||
* @param {string} modelId The model which the record is being created for.
|
* @param {string} tableId The table which the record is being created for.
|
||||||
* @returns {string} The new ID which a record doc can be stored under.
|
* @returns {string} The new ID which a record doc can be stored under.
|
||||||
*/
|
*/
|
||||||
exports.generateRecordID = modelId => {
|
exports.generateRecordID = tableId => {
|
||||||
return `${DocumentTypes.RECORD}${SEPARATOR}${modelId}${SEPARATOR}${newid()}`
|
return `${DocumentTypes.RECORD}${SEPARATOR}${tableId}${SEPARATOR}${newid()}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -116,14 +116,14 @@ exports.generateAutomationID = () => {
|
||||||
/**
|
/**
|
||||||
* Generates a new link doc ID. This is currently not usable with the alldocs call,
|
* Generates a new link doc ID. This is currently not usable with the alldocs call,
|
||||||
* instead a view is built to make walking to tree easier.
|
* instead a view is built to make walking to tree easier.
|
||||||
* @param {string} modelId1 The ID of the linker model.
|
* @param {string} tableId1 The ID of the linker table.
|
||||||
* @param {string} modelId2 The ID of the linked model.
|
* @param {string} tableId2 The ID of the linked table.
|
||||||
* @param {string} recordId1 The ID of the linker record.
|
* @param {string} recordId1 The ID of the linker record.
|
||||||
* @param {string} recordId2 The ID of the linked record.
|
* @param {string} recordId2 The ID of the linked record.
|
||||||
* @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 = (modelId1, modelId2, recordId1, recordId2) => {
|
exports.generateLinkID = (tableId1, tableId2, recordId1, recordId2) => {
|
||||||
return `${DocumentTypes.AUTOMATION}${SEPARATOR}${modelId1}${SEPARATOR}${modelId2}${SEPARATOR}${recordId1}${SEPARATOR}${recordId2}`
|
return `${DocumentTypes.AUTOMATION}${SEPARATOR}${tableId1}${SEPARATOR}${tableId2}${SEPARATOR}${recordId1}${SEPARATOR}${recordId2}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -11,14 +11,14 @@ 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, model = null) {
|
emitRecord(eventName, instanceId, record, table = null) {
|
||||||
let event = {
|
let event = {
|
||||||
record,
|
record,
|
||||||
instanceId,
|
instanceId,
|
||||||
modelId: record.modelId,
|
tableId: record.tableId,
|
||||||
}
|
}
|
||||||
if (model) {
|
if (table) {
|
||||||
event.model = model
|
event.table = table
|
||||||
}
|
}
|
||||||
event.id = record._id
|
event.id = record._id
|
||||||
if (record._rev) {
|
if (record._rev) {
|
||||||
|
@ -27,19 +27,19 @@ class BudibaseEmitter extends EventEmitter {
|
||||||
this.emit(eventName, event)
|
this.emit(eventName, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
emitModel(eventName, instanceId, model = null) {
|
emitTable(eventName, instanceId, table = null) {
|
||||||
const modelId = model._id
|
const tableId = table._id
|
||||||
let event = {
|
let event = {
|
||||||
model: {
|
table: {
|
||||||
...model,
|
...table,
|
||||||
modelId: modelId,
|
tableId: tableId,
|
||||||
},
|
},
|
||||||
instanceId,
|
instanceId,
|
||||||
modelId: modelId,
|
tableId: tableId,
|
||||||
}
|
}
|
||||||
event.id = modelId
|
event.id = tableId
|
||||||
if (model._rev) {
|
if (table._rev) {
|
||||||
event.revision = model._rev
|
event.revision = table._rev
|
||||||
}
|
}
|
||||||
this.emit(eventName, event)
|
this.emit(eventName, event)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Permissions
|
// Permissions
|
||||||
module.exports.READ_MODEL = "read-model"
|
module.exports.READ_TABLE = "read-table"
|
||||||
module.exports.WRITE_MODEL = "write-model"
|
module.exports.WRITE_TABLE = "write-table"
|
||||||
module.exports.READ_VIEW = "read-view"
|
module.exports.READ_VIEW = "read-view"
|
||||||
module.exports.EXECUTE_AUTOMATION = "execute-automation"
|
module.exports.EXECUTE_AUTOMATION = "execute-automation"
|
||||||
module.exports.USER_MANAGEMENT = "user-management"
|
module.exports.USER_MANAGEMENT = "user-management"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const viewController = require("../api/controllers/view")
|
const viewController = require("../api/controllers/view")
|
||||||
const modelController = require("../api/controllers/model")
|
const tableController = require("../api/controllers/table")
|
||||||
const automationController = require("../api/controllers/automation")
|
const automationController = require("../api/controllers/automation")
|
||||||
const accessLevels = require("./accessLevels")
|
const accessLevels = require("./accessLevels")
|
||||||
|
|
||||||
|
@ -10,13 +10,13 @@ const generateAdminPermissions = async instanceId => [
|
||||||
]
|
]
|
||||||
|
|
||||||
const generatePowerUserPermissions = async instanceId => {
|
const generatePowerUserPermissions = async instanceId => {
|
||||||
const fetchModelsCtx = {
|
const fetchTablesCtx = {
|
||||||
user: {
|
user: {
|
||||||
instanceId,
|
instanceId,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
await modelController.fetch(fetchModelsCtx)
|
await tableController.fetch(fetchTablesCtx)
|
||||||
const models = fetchModelsCtx.body
|
const tables = fetchTablesCtx.body
|
||||||
|
|
||||||
const fetchViewsCtx = {
|
const fetchViewsCtx = {
|
||||||
user: {
|
user: {
|
||||||
|
@ -34,14 +34,14 @@ const generatePowerUserPermissions = async instanceId => {
|
||||||
await automationController.fetch(fetchAutomationsCtx)
|
await automationController.fetch(fetchAutomationsCtx)
|
||||||
const automations = fetchAutomationsCtx.body
|
const automations = fetchAutomationsCtx.body
|
||||||
|
|
||||||
const readModelPermissions = models.map(m => ({
|
const readTablePermissions = tables.map(m => ({
|
||||||
itemId: m._id,
|
itemId: m._id,
|
||||||
name: accessLevels.READ_MODEL,
|
name: accessLevels.READ_TABLE,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const writeModelPermissions = models.map(m => ({
|
const writeTablePermissions = tables.map(m => ({
|
||||||
itemId: m._id,
|
itemId: m._id,
|
||||||
name: accessLevels.WRITE_MODEL,
|
name: accessLevels.WRITE_TABLE,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const viewPermissions = views.map(v => ({
|
const viewPermissions = views.map(v => ({
|
||||||
|
@ -55,8 +55,8 @@ const generatePowerUserPermissions = async instanceId => {
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...readModelPermissions,
|
...readTablePermissions,
|
||||||
...writeModelPermissions,
|
...writeTablePermissions,
|
||||||
...viewPermissions,
|
...viewPermissions,
|
||||||
...executeAutomationPermissions,
|
...executeAutomationPermissions,
|
||||||
{ name: accessLevels.LIST_USERS },
|
{ name: accessLevels.LIST_USERS },
|
||||||
|
|
|
@ -172,10 +172,10 @@
|
||||||
lodash "^4.17.13"
|
lodash "^4.17.13"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
"@budibase/client@^0.1.25":
|
"@budibase/client@^0.2.0":
|
||||||
version "0.1.25"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.1.25.tgz#f08c4a614f9018eb0f0faa6d20bb05f7a3215c70"
|
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.2.0.tgz#247218de40538b31aeac7367f0e323c28fc8ca40"
|
||||||
integrity sha512-vZ0cqJwLYcs7MHihFnJO3qOe7qxibnB4Va1+IYNfnPc9kcxy4KvfQxCx/G/DDxP9CXfEvsguy9ymzR3RUAvBHw==
|
integrity sha512-FJibQ7OqYCQOpIw4GDBPZEfJI+MiVn9irevZ2UjQBGJjbZMxoQTRAD/FoAq2Yu3s9L4tA5mTMutuMNkjpTs85Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
deep-equal "^2.0.1"
|
deep-equal "^2.0.1"
|
||||||
mustache "^4.0.1"
|
mustache "^4.0.1"
|
||||||
|
|
|
@ -208,7 +208,7 @@
|
||||||
"description": "an HTML table that fetches data from a table or view and displays it.",
|
"description": "an HTML table that fetches data from a table or view and displays it.",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"datasource": "models",
|
"datasource": "tables",
|
||||||
"stripeColor": "string",
|
"stripeColor": "string",
|
||||||
"borderColor": "string",
|
"borderColor": "string",
|
||||||
"backgroundColor": "string",
|
"backgroundColor": "string",
|
||||||
|
@ -219,7 +219,7 @@
|
||||||
"description": "an HTML table that fetches data from a table or view and displays it.",
|
"description": "an HTML table that fetches data from a table or view and displays it.",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"model": "models",
|
"table": "tables",
|
||||||
"title": "string",
|
"title": "string",
|
||||||
"buttonText": "string"
|
"buttonText": "string"
|
||||||
}
|
}
|
||||||
|
@ -228,16 +228,16 @@
|
||||||
"description": "an HTML table that fetches data from a table or view and displays it.",
|
"description": "an HTML table that fetches data from a table or view and displays it.",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"model": "models",
|
"table": "tables",
|
||||||
"title": "string",
|
"title": "string",
|
||||||
"buttonText": "string"
|
"buttonText": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"datalist": {
|
"datalist": {
|
||||||
"description": "A configurable data list that attaches to your backend models.",
|
"description": "A configurable data list that attaches to your backend tables.",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"model": "models",
|
"table": "tables",
|
||||||
"layout": {
|
"layout": {
|
||||||
"type": "options",
|
"type": "options",
|
||||||
"default": "list",
|
"default": "list",
|
||||||
|
@ -249,12 +249,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"list": {
|
"list": {
|
||||||
"description": "A configurable data list that attaches to your backend models.",
|
"description": "A configurable data list that attaches to your backend tables.",
|
||||||
"context": "datasource",
|
"context": "datasource",
|
||||||
"children": true,
|
"children": true,
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"datasource": "models"
|
"datasource": "tables"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stackedlist": {
|
"stackedlist": {
|
||||||
|
@ -271,11 +271,11 @@
|
||||||
},
|
},
|
||||||
"recorddetail": {
|
"recorddetail": {
|
||||||
"description": "Loads a record, using an ID in the url",
|
"description": "Loads a record, using an ID in the url",
|
||||||
"context": "model",
|
"context": "table",
|
||||||
"children": true,
|
"children": true,
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"model": "models"
|
"table": "tables"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"card": {
|
"card": {
|
||||||
|
@ -357,7 +357,7 @@
|
||||||
"description": "shiny chart",
|
"description": "shiny chart",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"model": "models"
|
"table": "tables"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"donut": {
|
"donut": {
|
||||||
|
@ -389,7 +389,7 @@
|
||||||
"description": "Sparkline Chart",
|
"description": "Sparkline Chart",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"model": "string",
|
"table": "string",
|
||||||
"areaGradient": "string",
|
"areaGradient": "string",
|
||||||
"height": "number",
|
"height": "number",
|
||||||
"width": "number",
|
"width": "number",
|
||||||
|
@ -405,7 +405,7 @@
|
||||||
"description": "Stacked Bar Chart",
|
"description": "Stacked Bar Chart",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"datasource": "models",
|
"datasource": "tables",
|
||||||
"color": "string",
|
"color": "string",
|
||||||
"height": "number",
|
"height": "number",
|
||||||
"width": "number",
|
"width": "number",
|
||||||
|
@ -434,7 +434,7 @@
|
||||||
"description": "Step Chart",
|
"description": "Step Chart",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"model": "string",
|
"table": "string",
|
||||||
"color": "string",
|
"color": "string",
|
||||||
"height": "number",
|
"height": "number",
|
||||||
"width": "number",
|
"width": "number",
|
||||||
|
@ -468,7 +468,7 @@
|
||||||
"description": "Step Chart",
|
"description": "Step Chart",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"model": "string",
|
"table": "string",
|
||||||
"height": "number",
|
"height": "number",
|
||||||
"width": "number",
|
"width": "number",
|
||||||
"margin": "string",
|
"margin": "string",
|
||||||
|
@ -483,7 +483,7 @@
|
||||||
"description": "Scatterplot Chart",
|
"description": "Scatterplot Chart",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"model": "string",
|
"table": "string",
|
||||||
"color": "string",
|
"color": "string",
|
||||||
"height": "number",
|
"height": "number",
|
||||||
"width": "number",
|
"width": "number",
|
||||||
|
@ -506,7 +506,7 @@
|
||||||
"description": "Bar Chart",
|
"description": "Bar Chart",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"datasource": "models",
|
"datasource": "tables",
|
||||||
"nameLabel": "string",
|
"nameLabel": "string",
|
||||||
"valueLabel": "string",
|
"valueLabel": "string",
|
||||||
"betweenBarsPadding": "number",
|
"betweenBarsPadding": "number",
|
||||||
|
@ -530,7 +530,7 @@
|
||||||
"description": "Line Chart",
|
"description": "Line Chart",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"datasource": "models",
|
"datasource": "tables",
|
||||||
"width": "number",
|
"width": "number",
|
||||||
"height": "number",
|
"height": "number",
|
||||||
"axisTimeCombinations": "string",
|
"axisTimeCombinations": "string",
|
||||||
|
@ -560,7 +560,7 @@
|
||||||
"description": "brush chart",
|
"description": "brush chart",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"model": "string",
|
"table": "string",
|
||||||
"gradient": "string",
|
"gradient": "string",
|
||||||
"height": "number",
|
"height": "number",
|
||||||
"width": "number",
|
"width": "number",
|
||||||
|
@ -577,7 +577,7 @@
|
||||||
"description": "Heatmap chart",
|
"description": "Heatmap chart",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"model": "string",
|
"table": "string",
|
||||||
"color": "string",
|
"color": "string",
|
||||||
"height": "number",
|
"height": "number",
|
||||||
"width": "number",
|
"width": "number",
|
||||||
|
@ -590,7 +590,7 @@
|
||||||
"description": "Groupedbar chart",
|
"description": "Groupedbar chart",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"datasource": "models",
|
"datasource": "tables",
|
||||||
"nameLabel": "string",
|
"nameLabel": "string",
|
||||||
"valueLabel": "string",
|
"valueLabel": "string",
|
||||||
"color": "string",
|
"color": "string",
|
||||||
|
@ -610,7 +610,7 @@
|
||||||
"description": "Bullet chart",
|
"description": "Bullet chart",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"model": "string",
|
"table": "string",
|
||||||
"color": "string",
|
"color": "string",
|
||||||
"customSubtitle": "string",
|
"customSubtitle": "string",
|
||||||
"customTitle": "string",
|
"customTitle": "string",
|
||||||
|
@ -631,7 +631,7 @@
|
||||||
"description": "shiny chart",
|
"description": "shiny chart",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"model": "models",
|
"table": "tables",
|
||||||
"type": {
|
"type": {
|
||||||
"type": "options",
|
"type": "options",
|
||||||
"default": "column2d",
|
"default": "column2d",
|
||||||
|
|
|
@ -21242,7 +21242,7 @@ var app = (function (crypto$1) {
|
||||||
"indexType",
|
"indexType",
|
||||||
"reference index may only exist on a record node",
|
"reference index may only exist on a record node",
|
||||||
index =>
|
index =>
|
||||||
isModel(index.parent()) || index.indexType !== indexTypes.reference
|
isTable(index.parent()) || index.indexType !== indexTypes.reference
|
||||||
),
|
),
|
||||||
makerule(
|
makerule(
|
||||||
"indexType",
|
"indexType",
|
||||||
|
@ -21321,9 +21321,9 @@ var app = (function (crypto$1) {
|
||||||
const isNode = (appHierarchy, key) =>
|
const isNode = (appHierarchy, key) =>
|
||||||
isSomething(getExactNodeForKey(appHierarchy)(key));
|
isSomething(getExactNodeForKey(appHierarchy)(key));
|
||||||
|
|
||||||
const isModel = node => isSomething(node) && node.type === "record";
|
const isTable = node => isSomething(node) && node.type === "record";
|
||||||
const isSingleRecord = node => isModel(node) && node.isSingle;
|
const isSingleRecord = node => isTable(node) && node.isSingle;
|
||||||
const isCollectionRecord = node => isModel(node) && !node.isSingle;
|
const isCollectionRecord = 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) => (
|
||||||
|
@ -22056,7 +22056,7 @@ var app = (function (crypto$1) {
|
||||||
const nodeKeyMaker = node => () =>
|
const nodeKeyMaker = node => () =>
|
||||||
switchCase(
|
switchCase(
|
||||||
[
|
[
|
||||||
n => isModel(n) && !isSingleRecord(n),
|
n => isTable(n) && !isSingleRecord(n),
|
||||||
n =>
|
n =>
|
||||||
joinKey(
|
joinKey(
|
||||||
node.parent().nodeKey(),
|
node.parent().nodeKey(),
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -59,7 +59,7 @@ window["##BUDIBASE_APPDEFINITION##"] = {
|
||||||
getShardName: "",
|
getShardName: "",
|
||||||
getSortKey: "record.id",
|
getSortKey: "record.id",
|
||||||
aggregateGroups: [],
|
aggregateGroups: [],
|
||||||
allowedModelNodeIds: [2],
|
allowedTableNodeIds: [2],
|
||||||
nodeId: 5,
|
nodeId: 5,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -79,7 +79,7 @@ window["##BUDIBASE_APPDEFINITION##"] = {
|
||||||
getShardName: "",
|
getShardName: "",
|
||||||
getSortKey: "record.id",
|
getSortKey: "record.id",
|
||||||
aggregateGroups: [],
|
aggregateGroups: [],
|
||||||
allowedModelNodeIds: [1],
|
allowedTableNodeIds: [1],
|
||||||
nodeId: 4,
|
nodeId: 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -91,7 +91,7 @@ window["##BUDIBASE_APPDEFINITION##"] = {
|
||||||
getShardName: "",
|
getShardName: "",
|
||||||
getSortKey: "record.id",
|
getSortKey: "record.id",
|
||||||
aggregateGroups: [],
|
aggregateGroups: [],
|
||||||
allowedModelNodeIds: [2],
|
allowedTableNodeIds: [2],
|
||||||
nodeId: 6,
|
nodeId: 6,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export let _bb
|
export let _bb
|
||||||
export let model
|
export let table
|
||||||
|
|
||||||
let store = _bb.store
|
let store = _bb.store
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (chart) {
|
if (chart) {
|
||||||
if (model) {
|
if (table) {
|
||||||
await fetchData()
|
await fetchData()
|
||||||
}
|
}
|
||||||
chartContainer = select(`.${chartClass}`)
|
chartContainer = select(`.${chartClass}`)
|
||||||
|
@ -54,12 +54,12 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
const FETCH_RECORDS_URL = `/api/views/all_${model}`
|
const FETCH_RECORDS_URL = `/api/views/all_${table}`
|
||||||
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
const response = await _bb.api.get(FETCH_RECORDS_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 => {
|
||||||
state[model] = json
|
state[table] = json
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -109,7 +109,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: _data = model ? $store[model] : data
|
$: _data = table ? $store[table] : data
|
||||||
|
|
||||||
$: chartGradient = getChartGradient(gradient)
|
$: chartGradient = getChartGradient(gradient)
|
||||||
$: console.log(chartGradient)
|
$: console.log(chartGradient)
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
const _id = shortid.generate()
|
const _id = shortid.generate()
|
||||||
|
|
||||||
export let _bb
|
export let _bb
|
||||||
export let model
|
export let table
|
||||||
|
|
||||||
let store = _bb.store
|
let store = _bb.store
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (chart) {
|
if (chart) {
|
||||||
if (model) {
|
if (table) {
|
||||||
await fetchData()
|
await fetchData()
|
||||||
}
|
}
|
||||||
chartContainer = select(`.${chartClass}`)
|
chartContainer = select(`.${chartClass}`)
|
||||||
|
@ -46,12 +46,12 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
const FETCH_RECORDS_URL = `/api/views/all_${model}`
|
const FETCH_RECORDS_URL = `/api/views/all_${table}`
|
||||||
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
const response = await _bb.api.get(FETCH_RECORDS_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 => {
|
||||||
state[model] = json
|
state[table] = json
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -95,7 +95,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: _data = model ? $store[model] : data
|
$: _data = table ? $store[table] : data
|
||||||
|
|
||||||
$: colorSchema = getColorSchema(color)
|
$: colorSchema = getColorSchema(color)
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
const _id = shortid.generate()
|
const _id = shortid.generate()
|
||||||
|
|
||||||
export let _bb
|
export let _bb
|
||||||
export let model
|
export let table
|
||||||
|
|
||||||
let store = _bb.store
|
let store = _bb.store
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (chart) {
|
if (chart) {
|
||||||
if (model) {
|
if (table) {
|
||||||
await fetchData()
|
await fetchData()
|
||||||
}
|
}
|
||||||
chartContainer = select(`.${chartClass}`)
|
chartContainer = select(`.${chartClass}`)
|
||||||
|
@ -41,12 +41,12 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
const FETCH_RECORDS_URL = `/api/views/all_${model}`
|
const FETCH_RECORDS_URL = `/api/views/all_${table}`
|
||||||
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
const response = await _bb.api.get(FETCH_RECORDS_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 => {
|
||||||
state[model] = json
|
state[table] = json
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -72,7 +72,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: _data = model ? $store[model] : data
|
$: _data = table ? $store[table] : data
|
||||||
|
|
||||||
$: colorSchema = getColorSchema(color)
|
$: colorSchema = getColorSchema(color)
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
const _id = shortid.generate()
|
const _id = shortid.generate()
|
||||||
|
|
||||||
export let _bb
|
export let _bb
|
||||||
export let model
|
export let table
|
||||||
|
|
||||||
let store = _bb.store
|
let store = _bb.store
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (chart) {
|
if (chart) {
|
||||||
if (model) {
|
if (table) {
|
||||||
await fetchData()
|
await fetchData()
|
||||||
}
|
}
|
||||||
chartContainer = select(`.${chartClass}`)
|
chartContainer = select(`.${chartClass}`)
|
||||||
|
@ -70,12 +70,12 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
const FETCH_RECORDS_URL = `/api/views/all_${model}`
|
const FETCH_RECORDS_URL = `/api/views/all_${table}`
|
||||||
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
const response = await _bb.api.get(FETCH_RECORDS_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 => {
|
||||||
state[model] = json
|
state[table] = json
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -176,7 +176,7 @@
|
||||||
tooltipContainer.datum([]).call(tooltip)
|
tooltipContainer.datum([]).call(tooltip)
|
||||||
}
|
}
|
||||||
|
|
||||||
$: _data = model ? $store[model] : data
|
$: _data = table ? $store[table] : data
|
||||||
|
|
||||||
$: colorSchema = getColorSchema(color)
|
$: colorSchema = getColorSchema(color)
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
const _id = shortid.generate()
|
const _id = shortid.generate()
|
||||||
|
|
||||||
export let _bb
|
export let _bb
|
||||||
export let model
|
export let table
|
||||||
|
|
||||||
let store = _bb.store
|
let store = _bb.store
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (chart) {
|
if (chart) {
|
||||||
if (model) {
|
if (table) {
|
||||||
await fetchData()
|
await fetchData()
|
||||||
}
|
}
|
||||||
chartContainer = select(`.${chartClass}`)
|
chartContainer = select(`.${chartClass}`)
|
||||||
|
@ -45,12 +45,12 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
const FETCH_RECORDS_URL = `/api/views/all_${model}`
|
const FETCH_RECORDS_URL = `/api/views/all_${table}`
|
||||||
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
const response = await _bb.api.get(FETCH_RECORDS_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 => {
|
||||||
state[model] = json
|
state[table] = json
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -91,7 +91,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: _data = model ? $store[model] : data
|
$: _data = table ? $store[table] : data
|
||||||
|
|
||||||
$: aGradient = getChartGradient(areaGradient)
|
$: aGradient = getChartGradient(areaGradient)
|
||||||
$: lGradient = getChartGradient(lineGradient)
|
$: lGradient = getChartGradient(lineGradient)
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
const _id = shortid.generate()
|
const _id = shortid.generate()
|
||||||
|
|
||||||
export let _bb
|
export let _bb
|
||||||
export let model
|
export let table
|
||||||
|
|
||||||
let store = _bb.store
|
let store = _bb.store
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (chart) {
|
if (chart) {
|
||||||
if (model) {
|
if (table) {
|
||||||
await fetchData()
|
await fetchData()
|
||||||
}
|
}
|
||||||
chartContainer = select(`.${chartClass}`)
|
chartContainer = select(`.${chartClass}`)
|
||||||
|
@ -70,12 +70,12 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
const FETCH_RECORDS_URL = `/api/views/all_${model}`
|
const FETCH_RECORDS_URL = `/api/views/all_${table}`
|
||||||
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
const response = await _bb.api.get(FETCH_RECORDS_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 => {
|
||||||
state[model] = json
|
state[table] = json
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -170,7 +170,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: _data = model ? $store[model] : data
|
$: _data = table ? $store[table] : data
|
||||||
|
|
||||||
$: colorSchema = getColorSchema(color)
|
$: colorSchema = getColorSchema(color)
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
const legendClass = `legend-container-${_id}`
|
const legendClass = `legend-container-${_id}`
|
||||||
|
|
||||||
export let _bb
|
export let _bb
|
||||||
export let model
|
export let table
|
||||||
|
|
||||||
let store = _bb.store
|
let store = _bb.store
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (chart) {
|
if (chart) {
|
||||||
if (model) {
|
if (table) {
|
||||||
await fetchData()
|
await fetchData()
|
||||||
}
|
}
|
||||||
chartContainer = select(`.${chartClass}`)
|
chartContainer = select(`.${chartClass}`)
|
||||||
|
@ -65,12 +65,12 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
const FETCH_RECORDS_URL = `/api/views/all_${model}`
|
const FETCH_RECORDS_URL = `/api/views/all_${table}`
|
||||||
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
const response = await _bb.api.get(FETCH_RECORDS_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 => {
|
||||||
state[model] = json
|
state[table] = json
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -165,7 +165,7 @@
|
||||||
tooltipContainer.datum([]).call(tooltip)
|
tooltipContainer.datum([]).call(tooltip)
|
||||||
}
|
}
|
||||||
|
|
||||||
$: _data = model ? $store[model] : data
|
$: _data = table ? $store[table] : data
|
||||||
|
|
||||||
$: colorSchema = getColorSchema(color)
|
$: colorSchema = getColorSchema(color)
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
const _id = shortid.generate()
|
const _id = shortid.generate()
|
||||||
|
|
||||||
export let _bb
|
export let _bb
|
||||||
export let model
|
export let table
|
||||||
|
|
||||||
let store = _bb.store
|
let store = _bb.store
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (chart) {
|
if (chart) {
|
||||||
if (model) {
|
if (table) {
|
||||||
await fetchData()
|
await fetchData()
|
||||||
}
|
}
|
||||||
chartContainer = select(`.${chartClass}`)
|
chartContainer = select(`.${chartClass}`)
|
||||||
|
@ -60,12 +60,12 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
const FETCH_RECORDS_URL = `/api/views/all_${model}`
|
const FETCH_RECORDS_URL = `/api/views/all_${table}`
|
||||||
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
const response = await _bb.api.get(FETCH_RECORDS_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 => {
|
||||||
state[model] = json
|
state[table] = json
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -118,7 +118,7 @@
|
||||||
tooltipContainer.datum([]).call(tooltip)
|
tooltipContainer.datum([]).call(tooltip)
|
||||||
}
|
}
|
||||||
|
|
||||||
$: _data = model ? $store[model] : data
|
$: _data = table ? $store[table] : data
|
||||||
|
|
||||||
$: colorSchema = getColorSchema(color)
|
$: colorSchema = getColorSchema(color)
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -31,7 +31,7 @@ amount: 8,
|
||||||
audited: new Date("2020-01-01T16:00:00-08:00"),
|
audited: new Date("2020-01-01T16:00:00-08:00"),
|
||||||
city: "Belfast",
|
city: "Belfast",
|
||||||
name: 1,
|
name: 1,
|
||||||
modelId: "2334751ac0764c1a931bff5b6b6767eb",
|
tableId: "2334751ac0764c1a931bff5b6b6767eb",
|
||||||
type: "record",
|
type: "record",
|
||||||
_id: "ceb87054790f480e80512368545755bb",
|
_id: "ceb87054790f480e80512368545755bb",
|
||||||
_rev: "2-56e401ebaf59e6310b85fb0c6c2fece5",
|
_rev: "2-56e401ebaf59e6310b85fb0c6c2fece5",
|
||||||
|
@ -41,7 +41,7 @@ amount: 12,
|
||||||
audited: new Date("2020-01-03T16:00:00-08:00"),
|
audited: new Date("2020-01-03T16:00:00-08:00"),
|
||||||
city: "Belfast",
|
city: "Belfast",
|
||||||
name: 1,
|
name: 1,
|
||||||
modelId: "2334751ac0764c1a931bff5b6b6767eb",
|
tableId: "2334751ac0764c1a931bff5b6b6767eb",
|
||||||
type: "record",
|
type: "record",
|
||||||
_id: "0a36103b55124f348a23d10b2f3ed0e3",
|
_id: "0a36103b55124f348a23d10b2f3ed0e3",
|
||||||
_rev: "2-50d62530b2edfc63d5fd0b3719dbb286",
|
_rev: "2-50d62530b2edfc63d5fd0b3719dbb286",
|
||||||
|
@ -51,7 +51,7 @@ amount: 6,
|
||||||
audited: new Date("2020-01-04T16:00:00-08:00"),
|
audited: new Date("2020-01-04T16:00:00-08:00"),
|
||||||
city: "Belfast",
|
city: "Belfast",
|
||||||
name: 1,
|
name: 1,
|
||||||
modelId: "2334751ac0764c1a931bff5b6b6767eb",
|
tableId: "2334751ac0764c1a931bff5b6b6767eb",
|
||||||
type: "record",
|
type: "record",
|
||||||
_id: "68ade2bb94754caa8fc62c7084e3cef7",
|
_id: "68ade2bb94754caa8fc62c7084e3cef7",
|
||||||
_rev: "2-a03fe02f3595920adfbcd9c70564fe9d",
|
_rev: "2-a03fe02f3595920adfbcd9c70564fe9d",
|
||||||
|
@ -61,7 +61,7 @@ amount: 2,
|
||||||
audited: new Date("2020-01-01T16:00:00-08:00"),
|
audited: new Date("2020-01-01T16:00:00-08:00"),
|
||||||
city: "Dublin",
|
city: "Dublin",
|
||||||
name: 2,
|
name: 2,
|
||||||
modelId: "2334751ac0764c1a931bff5b6b6767eb",
|
tableId: "2334751ac0764c1a931bff5b6b6767eb",
|
||||||
type: "record",
|
type: "record",
|
||||||
_id: "2ab6dabf833f4d99b3438fa4353ba429",
|
_id: "2ab6dabf833f4d99b3438fa4353ba429",
|
||||||
_rev: "2-45b190489e76842981902cc9f04369ec",
|
_rev: "2-45b190489e76842981902cc9f04369ec",
|
||||||
|
@ -71,7 +71,7 @@ amount: 16,
|
||||||
audited: new Date("2020-01-02T16:00:00-08:00"),
|
audited: new Date("2020-01-02T16:00:00-08:00"),
|
||||||
city: "Dublin",
|
city: "Dublin",
|
||||||
name: 2,
|
name: 2,
|
||||||
modelId: "2334751ac0764c1a931bff5b6b6767eb",
|
tableId: "2334751ac0764c1a931bff5b6b6767eb",
|
||||||
type: "record",
|
type: "record",
|
||||||
_id: "1b2ca36db1724427a98ba95547f946e0",
|
_id: "1b2ca36db1724427a98ba95547f946e0",
|
||||||
_rev: "2-c43def17ada959948b9af5484ad5b6b7",
|
_rev: "2-c43def17ada959948b9af5484ad5b6b7",
|
||||||
|
@ -81,7 +81,7 @@ amount: 7,
|
||||||
audited: new Date("2020-01-03T16:00:00-08:00"),
|
audited: new Date("2020-01-03T16:00:00-08:00"),
|
||||||
city: "Dublin",
|
city: "Dublin",
|
||||||
name: 2,
|
name: 2,
|
||||||
modelId: "2334751ac0764c1a931bff5b6b6767eb",
|
tableId: "2334751ac0764c1a931bff5b6b6767eb",
|
||||||
type: "record",
|
type: "record",
|
||||||
_id: "d9235d884a224ca68ac30cefdbb8ae53",
|
_id: "d9235d884a224ca68ac30cefdbb8ae53",
|
||||||
_rev: "2-695e426a261a25474cbf6b1f069dccb4",
|
_rev: "2-695e426a261a25474cbf6b1f069dccb4",
|
||||||
|
@ -91,7 +91,7 @@ amount: 3,
|
||||||
audited: new Date("2020-01-04T16:00:00-08:00"),
|
audited: new Date("2020-01-04T16:00:00-08:00"),
|
||||||
city: "Dublin",
|
city: "Dublin",
|
||||||
name: 2,
|
name: 2,
|
||||||
modelId: "2334751ac0764c1a931bff5b6b6767eb",
|
tableId: "2334751ac0764c1a931bff5b6b6767eb",
|
||||||
type: "record",
|
type: "record",
|
||||||
_id: "9f8bc39a9cfb4f779da8c998d7622927",
|
_id: "9f8bc39a9cfb4f779da8c998d7622927",
|
||||||
_rev: "2-8ae1aff82e1ffc6ffa75f6b9d074e003",
|
_rev: "2-8ae1aff82e1ffc6ffa75f6b9d074e003",
|
||||||
|
@ -101,7 +101,7 @@ amount: 4,
|
||||||
audited: new Date("2020-01-02T16:00:00-08:00"),
|
audited: new Date("2020-01-02T16:00:00-08:00"),
|
||||||
city: "London",
|
city: "London",
|
||||||
name: 3,
|
name: 3,
|
||||||
modelId: "2334751ac0764c1a931bff5b6b6767eb",
|
tableId: "2334751ac0764c1a931bff5b6b6767eb",
|
||||||
type: "record",
|
type: "record",
|
||||||
_id: "75274e906073493bbf75cda8656e8db0",
|
_id: "75274e906073493bbf75cda8656e8db0",
|
||||||
_rev: "2-6cfc6bb2fccb83c92b50aa5507f2a092"
|
_rev: "2-6cfc6bb2fccb83c92b50aa5507f2a092"
|
||||||
|
@ -111,7 +111,7 @@ amount: 22,
|
||||||
audited: new Date("2020-01-06T16:00:00-08:00"),
|
audited: new Date("2020-01-06T16:00:00-08:00"),
|
||||||
city: "London",
|
city: "London",
|
||||||
name: 3,
|
name: 3,
|
||||||
modelId: "2334751ac0764c1a931bff5b6b6767eb",
|
tableId: "2334751ac0764c1a931bff5b6b6767eb",
|
||||||
type: "record",
|
type: "record",
|
||||||
_id: "da3d4b151bc641f4ace487a2314d2550",
|
_id: "da3d4b151bc641f4ace487a2314d2550",
|
||||||
_rev: "2-ac18490eaa016be0e71bd4c4ea332981",
|
_rev: "2-ac18490eaa016be0e71bd4c4ea332981",
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
fcRoot(FusionCharts, Charts, FusionTheme)
|
fcRoot(FusionCharts, Charts, FusionTheme)
|
||||||
|
|
||||||
export let _bb
|
export let _bb
|
||||||
export let model
|
export let table
|
||||||
export let type = "column2d"
|
export let type = "column2d"
|
||||||
|
|
||||||
let store = _bb.store
|
let store = _bb.store
|
||||||
|
@ -19,19 +19,19 @@
|
||||||
height: "400",
|
height: "400",
|
||||||
dataFormat: "json",
|
dataFormat: "json",
|
||||||
dataSource: {
|
dataSource: {
|
||||||
data: $store[model] || [],
|
data: $store[table] || [],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
$: console.log("CHART CONFIGS", chartConfigs)
|
$: console.log("CHART CONFIGS", chartConfigs)
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
const FETCH_RECORDS_URL = `/api/views/all_${model}`
|
const FETCH_RECORDS_URL = `/api/views/all_${table}`
|
||||||
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
const response = await _bb.api.get(FETCH_RECORDS_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 => {
|
||||||
state[model] = json
|
state[table] = json
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
import Form from "./Form.svelte"
|
import Form from "./Form.svelte"
|
||||||
|
|
||||||
export let _bb
|
export let _bb
|
||||||
export let model
|
export let table
|
||||||
export let title
|
export let title
|
||||||
export let buttonText
|
export let buttonText
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Form {_bb} {model} {title} {buttonText} wide={false} />
|
<Form {_bb} {table} {title} {buttonText} wide={false} />
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
import Form from "./Form.svelte"
|
import Form from "./Form.svelte"
|
||||||
|
|
||||||
export let _bb
|
export let _bb
|
||||||
export let model
|
export let table
|
||||||
export let title
|
export let title
|
||||||
export let buttonText
|
export let buttonText
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Form {_bb} {model} {title} {buttonText} wide={true} />
|
<Form {_bb} {table} {title} {buttonText} wide={true} />
|
||||||
|
|
|
@ -2,22 +2,22 @@
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
export let _bb
|
export let _bb
|
||||||
export let model
|
export let table
|
||||||
export let layout = "list"
|
export let layout = "list"
|
||||||
|
|
||||||
let headers = []
|
let headers = []
|
||||||
let store = _bb.store
|
let store = _bb.store
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
if (!model || !model.length) return
|
if (!table || !table.length) return
|
||||||
|
|
||||||
const FETCH_RECORDS_URL = `/api/views/all_${model}`
|
const FETCH_RECORDS_URL = `/api/views/all_${table}`
|
||||||
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
const response = await _bb.api.get(FETCH_RECORDS_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 => {
|
||||||
state[model] = json
|
state[table] = json
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -25,8 +25,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: data = $store[model] || []
|
$: data = $store[table] || []
|
||||||
$: if (model) fetchData()
|
$: if (table) fetchData()
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await fetchData()
|
await fetchData()
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue