Merge branch 'master' into BUDI-9016/extract-componenterrors-from-client
This commit is contained in:
commit
884895060b
|
@ -41,12 +41,11 @@ module.exports = {
|
||||||
if (
|
if (
|
||||||
/^@budibase\/[^/]+\/.*$/.test(importPath) &&
|
/^@budibase\/[^/]+\/.*$/.test(importPath) &&
|
||||||
importPath !== "@budibase/backend-core/tests" &&
|
importPath !== "@budibase/backend-core/tests" &&
|
||||||
importPath !== "@budibase/string-templates/test/utils" &&
|
importPath !== "@budibase/string-templates/test/utils"
|
||||||
importPath !== "@budibase/client/manifest.json"
|
|
||||||
) {
|
) {
|
||||||
context.report({
|
context.report({
|
||||||
node,
|
node,
|
||||||
message: `Importing from @budibase is not allowed, except for @budibase/backend-core/tests, @budibase/string-templates/test/utils and @budibase/client/manifest.json.`,
|
message: `Importing from @budibase is not allowed, except for @budibase/backend-core/tests and @budibase/string-templates/test/utils.`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||||
"version": "3.3.5",
|
"version": "3.3.6",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"concurrency": 20,
|
"concurrency": 20,
|
||||||
"command": {
|
"command": {
|
||||||
|
|
|
@ -76,13 +76,15 @@ export const getSequentialName = <T extends any>(
|
||||||
{
|
{
|
||||||
getName,
|
getName,
|
||||||
numberFirstItem,
|
numberFirstItem,
|
||||||
|
separator = "",
|
||||||
}: {
|
}: {
|
||||||
getName?: (item: T) => string
|
getName?: (item: T) => string
|
||||||
numberFirstItem?: boolean
|
numberFirstItem?: boolean
|
||||||
|
separator?: string
|
||||||
} = {}
|
} = {}
|
||||||
) => {
|
) => {
|
||||||
if (!prefix?.length) {
|
if (!prefix?.length) {
|
||||||
return null
|
return ""
|
||||||
}
|
}
|
||||||
const trimmedPrefix = prefix.trim()
|
const trimmedPrefix = prefix.trim()
|
||||||
const firstName = numberFirstItem ? `${prefix}1` : trimmedPrefix
|
const firstName = numberFirstItem ? `${prefix}1` : trimmedPrefix
|
||||||
|
@ -107,5 +109,5 @@ export const getSequentialName = <T extends any>(
|
||||||
max = num
|
max = num
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return max === 0 ? firstName : `${prefix}${max + 1}`
|
return max === 0 ? firstName : `${prefix}${separator}${max + 1}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
import { Component, Screen, ScreenProps } from "@budibase/types"
|
|
||||||
import clientManifest from "@budibase/client/manifest.json"
|
|
||||||
|
|
||||||
export function findComponentsBySettingsType(
|
|
||||||
screen: Screen,
|
|
||||||
type: string | string[]
|
|
||||||
) {
|
|
||||||
const typesArray = Array.isArray(type) ? type : [type]
|
|
||||||
|
|
||||||
const result: {
|
|
||||||
component: Component
|
|
||||||
setting: {
|
|
||||||
type: string
|
|
||||||
key: string
|
|
||||||
}
|
|
||||||
}[] = []
|
|
||||||
function recurseFieldComponentsInChildren(component: ScreenProps) {
|
|
||||||
if (!component) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const definition = getManifestDefinition(component)
|
|
||||||
const setting =
|
|
||||||
"settings" in definition &&
|
|
||||||
definition.settings.find((s: any) => typesArray.includes(s.type))
|
|
||||||
if (setting && "type" in setting) {
|
|
||||||
result.push({
|
|
||||||
component,
|
|
||||||
setting: { type: setting.type!, key: setting.key! },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
component._children?.forEach(child => {
|
|
||||||
recurseFieldComponentsInChildren(child)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
recurseFieldComponentsInChildren(screen?.props)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getManifestDefinition(component: Component | string) {
|
|
||||||
const componentType =
|
|
||||||
typeof component === "string"
|
|
||||||
? component
|
|
||||||
: component._component.split("/").slice(-1)[0]
|
|
||||||
const definition =
|
|
||||||
clientManifest[componentType as keyof typeof clientManifest]
|
|
||||||
return definition
|
|
||||||
}
|
|
|
@ -49,7 +49,7 @@ describe("getSequentialName", () => {
|
||||||
|
|
||||||
it("handles nullish prefix", async () => {
|
it("handles nullish prefix", async () => {
|
||||||
const name = getSequentialName([], null)
|
const name = getSequentialName([], null)
|
||||||
expect(name).toBe(null)
|
expect(name).toBe("")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("handles just the prefix", async () => {
|
it("handles just the prefix", async () => {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {
|
||||||
previewStore,
|
previewStore,
|
||||||
tables,
|
tables,
|
||||||
componentTreeNodesStore,
|
componentTreeNodesStore,
|
||||||
|
screenComponents,
|
||||||
} from "@/stores/builder"
|
} from "@/stores/builder"
|
||||||
import { buildFormSchema, getSchemaForDatasource } from "@/dataBinding"
|
import { buildFormSchema, getSchemaForDatasource } from "@/dataBinding"
|
||||||
import {
|
import {
|
||||||
|
@ -37,6 +38,7 @@ import {
|
||||||
Table,
|
Table,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { utils } from "@budibase/shared-core"
|
import { utils } from "@budibase/shared-core"
|
||||||
|
import { getSequentialName } from "@/helpers/duplicate"
|
||||||
|
|
||||||
interface Component extends ComponentType {
|
interface Component extends ComponentType {
|
||||||
_id: string
|
_id: string
|
||||||
|
@ -60,6 +62,7 @@ export interface ComponentDefinition {
|
||||||
features?: Record<string, boolean>
|
features?: Record<string, boolean>
|
||||||
typeSupportPresets?: Record<string, any>
|
typeSupportPresets?: Record<string, any>
|
||||||
legalDirectChildren: string[]
|
legalDirectChildren: string[]
|
||||||
|
requiredAncestors?: string[]
|
||||||
illegalChildren: string[]
|
illegalChildren: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -452,7 +455,7 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
createInstance(
|
createInstance(
|
||||||
componentName: string,
|
componentType: string,
|
||||||
presetProps: any,
|
presetProps: any,
|
||||||
parent: any
|
parent: any
|
||||||
): Component | null {
|
): Component | null {
|
||||||
|
@ -461,11 +464,20 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
||||||
throw "A valid screen must be selected"
|
throw "A valid screen must be selected"
|
||||||
}
|
}
|
||||||
|
|
||||||
const definition = this.getDefinition(componentName)
|
const definition = this.getDefinition(componentType)
|
||||||
if (!definition) {
|
if (!definition) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const componentName = getSequentialName(
|
||||||
|
get(screenComponents),
|
||||||
|
`New ${definition.friendlyName || definition.name}`,
|
||||||
|
{
|
||||||
|
getName: c => c._instanceName,
|
||||||
|
separator: " ",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// Generate basic component structure
|
// Generate basic component structure
|
||||||
let instance: Component = {
|
let instance: Component = {
|
||||||
_id: Helpers.uuid(),
|
_id: Helpers.uuid(),
|
||||||
|
@ -475,7 +487,7 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
||||||
hover: {},
|
hover: {},
|
||||||
active: {},
|
active: {},
|
||||||
},
|
},
|
||||||
_instanceName: `New ${definition.friendlyName || definition.name}`,
|
_instanceName: componentName,
|
||||||
...presetProps,
|
...presetProps,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -500,7 +512,7 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add step name to form steps
|
// Add step name to form steps
|
||||||
if (componentName.endsWith("/formstep")) {
|
if (componentType.endsWith("/formstep")) {
|
||||||
const parentForm = findClosestMatchingComponent(
|
const parentForm = findClosestMatchingComponent(
|
||||||
screen.props,
|
screen.props,
|
||||||
get(selectedComponent)?._id,
|
get(selectedComponent)?._id,
|
||||||
|
@ -529,14 +541,14 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async create(
|
async create(
|
||||||
componentName: string,
|
componentType: string,
|
||||||
presetProps: any,
|
presetProps: any,
|
||||||
parent: Component,
|
parent: Component,
|
||||||
index: number
|
index: number
|
||||||
) {
|
) {
|
||||||
const state = get(this.store)
|
const state = get(this.store)
|
||||||
const componentInstance = this.createInstance(
|
const componentInstance = this.createInstance(
|
||||||
componentName,
|
componentType,
|
||||||
presetProps,
|
presetProps,
|
||||||
parent
|
parent
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { userStore, userSelectedResourceMap, isOnlyUser } from "./users.js"
|
||||||
import { deploymentStore } from "./deployments.js"
|
import { deploymentStore } from "./deployments.js"
|
||||||
import { contextMenuStore } from "./contextMenu.js"
|
import { contextMenuStore } from "./contextMenu.js"
|
||||||
import { snippets } from "./snippets"
|
import { snippets } from "./snippets"
|
||||||
import { screenComponentErrors } from "./screenComponent"
|
import { screenComponents, screenComponentErrors } from "./screenComponent"
|
||||||
|
|
||||||
// Backend
|
// Backend
|
||||||
import { tables } from "./tables"
|
import { tables } from "./tables"
|
||||||
|
@ -68,6 +68,7 @@ export {
|
||||||
snippets,
|
snippets,
|
||||||
rowActions,
|
rowActions,
|
||||||
appPublished,
|
appPublished,
|
||||||
|
screenComponents,
|
||||||
screenComponentErrors,
|
screenComponentErrors,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,21 +2,19 @@ import { derived } from "svelte/store"
|
||||||
import { tables } from "./tables"
|
import { tables } from "./tables"
|
||||||
import { selectedScreen } from "./screens"
|
import { selectedScreen } from "./screens"
|
||||||
import { viewsV2 } from "./viewsV2"
|
import { viewsV2 } from "./viewsV2"
|
||||||
import {
|
|
||||||
findComponentsBySettingsType,
|
|
||||||
getManifestDefinition,
|
|
||||||
} from "@/helpers/screen"
|
|
||||||
import {
|
import {
|
||||||
UIDatasourceType,
|
UIDatasourceType,
|
||||||
Screen,
|
Screen,
|
||||||
Component,
|
Component,
|
||||||
UIComponentError,
|
UIComponentError,
|
||||||
|
ScreenProps,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { queries } from "./queries"
|
import { queries } from "./queries"
|
||||||
import { views } from "./views"
|
import { views } from "./views"
|
||||||
import { findAllComponents } from "@/helpers/components"
|
import { findAllComponents } from "@/helpers/components"
|
||||||
import { bindings, featureFlag } from "@/helpers"
|
import { bindings, featureFlag } from "@/helpers"
|
||||||
import { getBindableProperties } from "@/dataBinding"
|
import { getBindableProperties } from "@/dataBinding"
|
||||||
|
import { componentStore, ComponentDefinition } from "./components"
|
||||||
|
|
||||||
function reduceBy<TItem extends {}, TKey extends keyof TItem>(
|
function reduceBy<TItem extends {}, TKey extends keyof TItem>(
|
||||||
key: TKey,
|
key: TKey,
|
||||||
|
@ -47,13 +45,17 @@ const validationKeyByType: Record<UIDatasourceType, string | null> = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const screenComponentErrors = derived(
|
export const screenComponentErrors = derived(
|
||||||
[selectedScreen, tables, views, viewsV2, queries],
|
[selectedScreen, tables, views, viewsV2, queries, componentStore],
|
||||||
([$selectedScreen, $tables, $views, $viewsV2, $queries]): Record<
|
([
|
||||||
string,
|
$selectedScreen,
|
||||||
UIComponentError[]
|
$tables,
|
||||||
> => {
|
$views,
|
||||||
|
$viewsV2,
|
||||||
|
$queries,
|
||||||
|
$componentStore,
|
||||||
|
]): Record<string, UIComponentError[]> => {
|
||||||
if (
|
if (
|
||||||
!featureFlag.isEnabled("CHECK_SCREEN_COMPONENT_SETTINGS_ERRORS") ||
|
!featureFlag.isEnabled("CHECK_COMPONENT_SETTINGS_ERRORS") ||
|
||||||
!$selectedScreen
|
!$selectedScreen
|
||||||
) {
|
) {
|
||||||
return {}
|
return {}
|
||||||
|
@ -66,10 +68,12 @@ export const screenComponentErrors = derived(
|
||||||
...reduceBy("_id", $queries.list),
|
...reduceBy("_id", $queries.list),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { components: definitions } = $componentStore
|
||||||
|
|
||||||
const errors = {
|
const errors = {
|
||||||
...getInvalidDatasources($selectedScreen, datasources),
|
...getInvalidDatasources($selectedScreen, datasources, definitions),
|
||||||
...getMissingAncestors($selectedScreen),
|
...getMissingAncestors($selectedScreen, definitions),
|
||||||
...getMissingRequiredSettings($selectedScreen),
|
...getMissingRequiredSettings($selectedScreen, definitions),
|
||||||
}
|
}
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
@ -77,13 +81,15 @@ export const screenComponentErrors = derived(
|
||||||
|
|
||||||
function getInvalidDatasources(
|
function getInvalidDatasources(
|
||||||
screen: Screen,
|
screen: Screen,
|
||||||
datasources: Record<string, any>
|
datasources: Record<string, any>,
|
||||||
|
definitions: Record<string, ComponentDefinition>
|
||||||
) {
|
) {
|
||||||
const result: Record<string, UIComponentError[]> = {}
|
const result: Record<string, UIComponentError[]> = {}
|
||||||
for (const { component, setting } of findComponentsBySettingsType(screen, [
|
for (const { component, setting } of findComponentsBySettingsType(
|
||||||
"table",
|
screen,
|
||||||
"dataSource",
|
["table", "dataSource"],
|
||||||
])) {
|
definitions
|
||||||
|
)) {
|
||||||
const componentSettings = component[setting.key]
|
const componentSettings = component[setting.key]
|
||||||
if (!componentSettings) {
|
if (!componentSettings) {
|
||||||
continue
|
continue
|
||||||
|
@ -121,17 +127,20 @@ function getInvalidDatasources(
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMissingRequiredSettings(screen: Screen) {
|
function getMissingRequiredSettings(
|
||||||
|
screen: Screen,
|
||||||
|
definitions: Record<string, ComponentDefinition>
|
||||||
|
) {
|
||||||
const allComponents = findAllComponents(screen.props) as Component[]
|
const allComponents = findAllComponents(screen.props) as Component[]
|
||||||
|
|
||||||
const result: Record<string, UIComponentError[]> = {}
|
const result: Record<string, UIComponentError[]> = {}
|
||||||
for (const component of allComponents) {
|
for (const component of allComponents) {
|
||||||
const definition = getManifestDefinition(component)
|
const definition = definitions[component._component]
|
||||||
if (!("settings" in definition)) {
|
if (!("settings" in definition)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const missingRequiredSettings = definition.settings.filter(
|
const missingRequiredSettings = definition.settings?.filter(
|
||||||
(setting: any) => {
|
(setting: any) => {
|
||||||
let empty =
|
let empty =
|
||||||
component[setting.key] == null || component[setting.key] === ""
|
component[setting.key] == null || component[setting.key] === ""
|
||||||
|
@ -167,7 +176,7 @@ function getMissingRequiredSettings(screen: Screen) {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (missingRequiredSettings.length) {
|
if (missingRequiredSettings?.length) {
|
||||||
result[component._id!] = missingRequiredSettings.map((s: any) => ({
|
result[component._id!] = missingRequiredSettings.map((s: any) => ({
|
||||||
key: s.key,
|
key: s.key,
|
||||||
message: `Add the <mark>${s.label}</mark> setting to start using your component`,
|
message: `Add the <mark>${s.label}</mark> setting to start using your component`,
|
||||||
|
@ -180,7 +189,10 @@ function getMissingRequiredSettings(screen: Screen) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const BudibasePrefix = "@budibase/standard-components/"
|
const BudibasePrefix = "@budibase/standard-components/"
|
||||||
function getMissingAncestors(screen: Screen) {
|
function getMissingAncestors(
|
||||||
|
screen: Screen,
|
||||||
|
definitions: Record<string, ComponentDefinition>
|
||||||
|
) {
|
||||||
const result: Record<string, UIComponentError[]> = {}
|
const result: Record<string, UIComponentError[]> = {}
|
||||||
|
|
||||||
function checkMissingAncestors(component: Component, ancestors: string[]) {
|
function checkMissingAncestors(component: Component, ancestors: string[]) {
|
||||||
|
@ -188,9 +200,9 @@ function getMissingAncestors(screen: Screen) {
|
||||||
checkMissingAncestors(child, [...ancestors, component._component])
|
checkMissingAncestors(child, [...ancestors, component._component])
|
||||||
}
|
}
|
||||||
|
|
||||||
const definition = getManifestDefinition(component)
|
const definition = definitions[component._component]
|
||||||
|
|
||||||
if (!("requiredAncestors" in definition)) {
|
if (!definition?.requiredAncestors?.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +216,7 @@ function getMissingAncestors(screen: Screen) {
|
||||||
}
|
}
|
||||||
|
|
||||||
result[component._id!] = missingAncestors.map(ancestor => {
|
result[component._id!] = missingAncestors.map(ancestor => {
|
||||||
const ancestorDefinition: any = getManifestDefinition(ancestor)
|
const ancestorDefinition = definitions[`${BudibasePrefix}${ancestor}`]
|
||||||
return {
|
return {
|
||||||
message: `${pluralise(definition.name)} need to be inside a
|
message: `${pluralise(definition.name)} need to be inside a
|
||||||
<mark>${ancestorDefinition.name}</mark>`,
|
<mark>${ancestorDefinition.name}</mark>`,
|
||||||
|
@ -221,3 +233,52 @@ function getMissingAncestors(screen: Screen) {
|
||||||
checkMissingAncestors(screen.props, [])
|
checkMissingAncestors(screen.props, [])
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
function findComponentsBySettingsType(
|
||||||
|
screen: Screen,
|
||||||
|
type: string | string[],
|
||||||
|
definitions: Record<string, ComponentDefinition>
|
||||||
|
) {
|
||||||
|
const typesArray = Array.isArray(type) ? type : [type]
|
||||||
|
|
||||||
|
const result: {
|
||||||
|
component: Component
|
||||||
|
setting: {
|
||||||
|
type: string
|
||||||
|
key: string
|
||||||
|
}
|
||||||
|
}[] = []
|
||||||
|
|
||||||
|
function recurseFieldComponentsInChildren(component: ScreenProps) {
|
||||||
|
if (!component) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const definition = definitions[component._component]
|
||||||
|
|
||||||
|
const setting = definition?.settings?.find((s: any) =>
|
||||||
|
typesArray.includes(s.type)
|
||||||
|
)
|
||||||
|
if (setting && "type" in setting) {
|
||||||
|
result.push({
|
||||||
|
component,
|
||||||
|
setting: { type: setting.type!, key: setting.key! },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
component._children?.forEach(child => {
|
||||||
|
recurseFieldComponentsInChildren(child)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
recurseFieldComponentsInChildren(screen?.props)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export const screenComponents = derived(
|
||||||
|
[selectedScreen],
|
||||||
|
([$selectedScreen]) => {
|
||||||
|
if (!$selectedScreen) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return findAllComponents($selectedScreen.props) as Component[]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
export enum FeatureFlag {
|
export enum FeatureFlag {
|
||||||
USE_ZOD_VALIDATOR = "USE_ZOD_VALIDATOR",
|
USE_ZOD_VALIDATOR = "USE_ZOD_VALIDATOR",
|
||||||
CHECK_SCREEN_COMPONENT_SETTINGS_ERRORS = "CHECK_SCREEN_COMPONENT_SETTINGS_ERRORS",
|
CHECK_COMPONENT_SETTINGS_ERRORS = "CHECK_COMPONENT_SETTINGS_ERRORS",
|
||||||
|
|
||||||
// Account-portal
|
// Account-portal
|
||||||
DIRECT_LOGIN_TO_ACCOUNT_PORTAL = "DIRECT_LOGIN_TO_ACCOUNT_PORTAL",
|
DIRECT_LOGIN_TO_ACCOUNT_PORTAL = "DIRECT_LOGIN_TO_ACCOUNT_PORTAL",
|
||||||
|
@ -8,7 +8,7 @@ export enum FeatureFlag {
|
||||||
|
|
||||||
export const FeatureFlagDefaults = {
|
export const FeatureFlagDefaults = {
|
||||||
[FeatureFlag.USE_ZOD_VALIDATOR]: false,
|
[FeatureFlag.USE_ZOD_VALIDATOR]: false,
|
||||||
[FeatureFlag.CHECK_SCREEN_COMPONENT_SETTINGS_ERRORS]: false,
|
[FeatureFlag.CHECK_COMPONENT_SETTINGS_ERRORS]: false,
|
||||||
|
|
||||||
// Account-portal
|
// Account-portal
|
||||||
[FeatureFlag.DIRECT_LOGIN_TO_ACCOUNT_PORTAL]: false,
|
[FeatureFlag.DIRECT_LOGIN_TO_ACCOUNT_PORTAL]: false,
|
||||||
|
|
Loading…
Reference in New Issue