Moving generation to builder because it reduces API calls and has no reason to be carried out server-side, handling array/object schema generation correctly.
This commit is contained in:
parent
ed28bf664d
commit
de0b23dd9f
|
@ -0,0 +1,56 @@
|
||||||
|
import { FIELDS } from "constants/backend"
|
||||||
|
|
||||||
|
function baseConversion(type) {
|
||||||
|
if (type === "string") {
|
||||||
|
return {
|
||||||
|
type: FIELDS.STRING.type,
|
||||||
|
}
|
||||||
|
} else if (type === "boolean") {
|
||||||
|
return {
|
||||||
|
type: FIELDS.BOOLEAN.type,
|
||||||
|
}
|
||||||
|
} else if (type === "number") {
|
||||||
|
return {
|
||||||
|
type: FIELDS.NUMBER.type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function recurse(schemaLevel = {}, objectLevel) {
|
||||||
|
if (!objectLevel) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const baseType = typeof objectLevel
|
||||||
|
if (baseType !== "object") {
|
||||||
|
return baseConversion(baseType)
|
||||||
|
}
|
||||||
|
for (let [key, value] of Object.entries(objectLevel)) {
|
||||||
|
const type = typeof value
|
||||||
|
// check array first, since arrays are objects
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
const schema = recurse(schemaLevel[key], value[0])
|
||||||
|
if (schema) {
|
||||||
|
schemaLevel[key] = {
|
||||||
|
type: FIELDS.ARRAY.type,
|
||||||
|
schema,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (type === "object") {
|
||||||
|
const schema = recurse(schemaLevel[key], objectLevel[key])
|
||||||
|
if (schema) {
|
||||||
|
schemaLevel[key] = schema
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
schemaLevel[key] = baseConversion(type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!schemaLevel.type) {
|
||||||
|
return { type: FIELDS.JSON.type, schema: schemaLevel }
|
||||||
|
} else {
|
||||||
|
return schemaLevel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generate(object) {
|
||||||
|
return recurse({}, object).schema
|
||||||
|
}
|
|
@ -87,7 +87,10 @@
|
||||||
field.subtype !== AUTO_COLUMN_SUB_TYPES.CREATED_BY &&
|
field.subtype !== AUTO_COLUMN_SUB_TYPES.CREATED_BY &&
|
||||||
field.subtype !== AUTO_COLUMN_SUB_TYPES.UPDATED_BY &&
|
field.subtype !== AUTO_COLUMN_SUB_TYPES.UPDATED_BY &&
|
||||||
field.type !== FORMULA_TYPE
|
field.type !== FORMULA_TYPE
|
||||||
$: canBeDisplay = field.type !== LINK_TYPE && field.type !== AUTO_TYPE
|
$: canBeDisplay =
|
||||||
|
field.type !== LINK_TYPE &&
|
||||||
|
field.type !== AUTO_TYPE &&
|
||||||
|
field.type !== JSON_TYPE
|
||||||
$: canBeRequired =
|
$: canBeRequired =
|
||||||
field.type !== LINK_TYPE && !uneditable && field.type !== AUTO_TYPE
|
field.type !== LINK_TYPE && !uneditable && field.type !== AUTO_TYPE
|
||||||
$: relationshipOptions = getRelationshipOptions(field)
|
$: relationshipOptions = getRelationshipOptions(field)
|
||||||
|
@ -454,7 +457,11 @@
|
||||||
<Modal bind:this={jsonSchemaModal}>
|
<Modal bind:this={jsonSchemaModal}>
|
||||||
<JSONSchemaModal
|
<JSONSchemaModal
|
||||||
schema={field.schema}
|
schema={field.schema}
|
||||||
on:save={({ detail }) => (field.schema = detail)}
|
json={field.json}
|
||||||
|
on:save={({ detail }) => {
|
||||||
|
field.schema = detail.schema
|
||||||
|
field.json = detail.json
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
|
|
|
@ -7,20 +7,26 @@
|
||||||
Button,
|
Button,
|
||||||
Input,
|
Input,
|
||||||
Select,
|
Select,
|
||||||
notifications,
|
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { onMount, createEventDispatcher } from "svelte"
|
import { onMount, createEventDispatcher } from "svelte"
|
||||||
import { post } from "builderStore/api"
|
import { FIELDS } from "constants/backend"
|
||||||
|
import { generate } from "builderStore/schemaGenerator"
|
||||||
|
|
||||||
export let schema = {}
|
export let schema = {}
|
||||||
|
export let json
|
||||||
|
|
||||||
let dispatcher = createEventDispatcher()
|
let dispatcher = createEventDispatcher()
|
||||||
let mode = "Key/Value"
|
let mode = "Key/Value"
|
||||||
let json
|
|
||||||
let fieldCount = 0
|
let fieldCount = 0
|
||||||
let fieldKeys = {},
|
let fieldKeys = {},
|
||||||
fieldTypes = {}
|
fieldTypes = {}
|
||||||
let keyValueOptions = ["String", "Number", "Boolean", "Object", "Array"]
|
let keyValueOptions = [
|
||||||
|
{ label: "String", value: FIELDS.STRING.type },
|
||||||
|
{ label: "Number", value: FIELDS.NUMBER.type },
|
||||||
|
{ label: "Boolean", value: FIELDS.BOOLEAN.type },
|
||||||
|
{ label: "Object", value: FIELDS.JSON.type },
|
||||||
|
{ label: "Array", value: FIELDS.ARRAY.type },
|
||||||
|
]
|
||||||
let invalid = false
|
let invalid = false
|
||||||
|
|
||||||
async function onJsonUpdate({ detail }) {
|
async function onJsonUpdate({ detail }) {
|
||||||
|
@ -29,17 +35,9 @@
|
||||||
try {
|
try {
|
||||||
// check json valid first
|
// check json valid first
|
||||||
let inputJson = JSON.parse(input)
|
let inputJson = JSON.parse(input)
|
||||||
const response = await post("/api/tables/schema/generate", {
|
schema = generate(inputJson)
|
||||||
json: inputJson,
|
|
||||||
})
|
|
||||||
if (response.status !== 200) {
|
|
||||||
const error = (await response.text()).message
|
|
||||||
notifications.error(error)
|
|
||||||
} else {
|
|
||||||
schema = await response.json()
|
|
||||||
updateCounts()
|
updateCounts()
|
||||||
invalid = false
|
invalid = false
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// json not currently valid
|
// json not currently valid
|
||||||
invalid = true
|
invalid = true
|
||||||
|
@ -62,11 +60,14 @@
|
||||||
function saveSchema() {
|
function saveSchema() {
|
||||||
for (let i of Object.keys(fieldKeys)) {
|
for (let i of Object.keys(fieldKeys)) {
|
||||||
const key = fieldKeys[i]
|
const key = fieldKeys[i]
|
||||||
|
// they were added to schema, rather than generated
|
||||||
|
if (!schema[key]) {
|
||||||
schema[key] = {
|
schema[key] = {
|
||||||
type: fieldTypes[i],
|
type: fieldTypes[i],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dispatcher("save", schema)
|
}
|
||||||
|
dispatcher("save", { schema, json })
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
@ -90,7 +91,8 @@
|
||||||
label="Type"
|
label="Type"
|
||||||
options={keyValueOptions}
|
options={keyValueOptions}
|
||||||
bind:value={fieldTypes[i]}
|
bind:value={fieldTypes[i]}
|
||||||
getOptionValue={field => field.toLowerCase()}
|
getOptionValue={field => field.value}
|
||||||
|
getOptionLabel={field => field.label}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -9,7 +9,6 @@ const {
|
||||||
BudibaseInternalDB,
|
BudibaseInternalDB,
|
||||||
} = require("../../../db/utils")
|
} = require("../../../db/utils")
|
||||||
const { getTable } = require("./utils")
|
const { getTable } = require("./utils")
|
||||||
const { FieldTypes } = require("../../../constants")
|
|
||||||
|
|
||||||
function pickApi({ tableId, table }) {
|
function pickApi({ tableId, table }) {
|
||||||
if (table && !tableId) {
|
if (table && !tableId) {
|
||||||
|
@ -82,38 +81,6 @@ exports.destroy = async function (ctx) {
|
||||||
ctx.body = { message: `Table ${tableId} deleted.` }
|
ctx.body = { message: `Table ${tableId} deleted.` }
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.schemaGenerate = function (ctx) {
|
|
||||||
const { json } = ctx.request.body
|
|
||||||
function recurse(schemaLevel, objectLevel) {
|
|
||||||
for (let [key, value] of Object.entries(objectLevel)) {
|
|
||||||
const type = typeof value
|
|
||||||
// check array first, since arrays are objects
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
schemaLevel[key] = {
|
|
||||||
type: FieldTypes.ARRAY,
|
|
||||||
}
|
|
||||||
} else if (type === "object") {
|
|
||||||
schemaLevel[key] = recurse(schemaLevel[key], objectLevel)
|
|
||||||
} else if (type === "string") {
|
|
||||||
schemaLevel[key] = {
|
|
||||||
type: FieldTypes.STRING,
|
|
||||||
}
|
|
||||||
} else if (type === "boolean") {
|
|
||||||
schemaLevel[key] = {
|
|
||||||
type: FieldTypes.BOOLEAN,
|
|
||||||
}
|
|
||||||
} else if (type === "number") {
|
|
||||||
schemaLevel[key] = {
|
|
||||||
type: FieldTypes.NUMBER,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return schemaLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.body = recurse({}, json) || {}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.bulkImport = async function (ctx) {
|
exports.bulkImport = async function (ctx) {
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
await pickApi({ tableId }).bulkImport(ctx)
|
await pickApi({ tableId }).bulkImport(ctx)
|
||||||
|
|
|
@ -139,24 +139,6 @@ router
|
||||||
generateSaveValidator(),
|
generateSaveValidator(),
|
||||||
tableController.save
|
tableController.save
|
||||||
)
|
)
|
||||||
/**
|
|
||||||
* @api {post} /api/tables/schema/generate Generate schema from JSON
|
|
||||||
* @apiName Generate schema from JSON
|
|
||||||
* @apiGroup tables
|
|
||||||
* @apiPermission builder
|
|
||||||
* @apiDescription Given a JSON structure this will generate a nested schema that can be used for a key/value data
|
|
||||||
* type in a table.
|
|
||||||
*
|
|
||||||
* @apiParam (Body) {object} json The JSON structure from which a nest schema should be generated.
|
|
||||||
*
|
|
||||||
* @apiSuccess {object} schema The response body will contain the schema, which can now be used for a key/value
|
|
||||||
* data type.
|
|
||||||
*/
|
|
||||||
.post(
|
|
||||||
"/api/tables/schema/generate",
|
|
||||||
authorized(BUILDER),
|
|
||||||
tableController.schemaGenerate
|
|
||||||
)
|
|
||||||
/**
|
/**
|
||||||
* @api {post} /api/tables/csv/validate Validate a CSV for a table
|
* @api {post} /api/tables/csv/validate Validate a CSV for a table
|
||||||
* @apiName Validate a CSV for a table
|
* @apiName Validate a CSV for a table
|
||||||
|
|
Loading…
Reference in New Issue