Merge branch 'master' into state-and-bindings-panels
This commit is contained in:
commit
899e75869a
|
@ -41,11 +41,12 @@ module.exports = {
|
|||
if (
|
||||
/^@budibase\/[^/]+\/.*$/.test(importPath) &&
|
||||
importPath !== "@budibase/backend-core/tests" &&
|
||||
importPath !== "@budibase/string-templates/test/utils"
|
||||
importPath !== "@budibase/string-templates/test/utils" &&
|
||||
importPath !== "@budibase/client/manifest.json"
|
||||
) {
|
||||
context.report({
|
||||
node,
|
||||
message: `Importing from @budibase is not allowed, except for @budibase/backend-core/tests and @budibase/string-templates/test/utils.`,
|
||||
message: `Importing from @budibase is not allowed, except for @budibase/backend-core/tests, @budibase/string-templates/test/utils and @budibase/client/manifest.json.`,
|
||||
})
|
||||
}
|
||||
},
|
||||
|
|
|
@ -293,7 +293,7 @@
|
|||
type: RowSelector,
|
||||
props: {
|
||||
row: inputData["oldRow"] || {
|
||||
tableId: inputData["row"].tableId,
|
||||
tableId: inputData["row"]?.tableId,
|
||||
},
|
||||
meta: {
|
||||
fields: inputData["meta"]?.oldFields || {},
|
||||
|
|
|
@ -7,8 +7,21 @@
|
|||
export let dataSet
|
||||
export let value
|
||||
export let onSelect
|
||||
export let identifiers = ["resourceId"]
|
||||
|
||||
$: displayDatasourceName = $datasources.list.length > 1
|
||||
|
||||
function isSelected(entry) {
|
||||
if (!identifiers.length) {
|
||||
return false
|
||||
}
|
||||
for (const identifier of identifiers) {
|
||||
if (entry[identifier] !== value?.[identifier]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if dividerState}
|
||||
|
@ -24,8 +37,7 @@
|
|||
{#each dataSet as data}
|
||||
<li
|
||||
class="spectrum-Menu-item"
|
||||
class:is-selected={value?.resourceId === data.resourceId &&
|
||||
value?.type === data.type}
|
||||
class:is-selected={isSelected(data) && value?.type === data.type}
|
||||
role="option"
|
||||
aria-selected="true"
|
||||
tabindex="0"
|
||||
|
|
|
@ -291,6 +291,7 @@
|
|||
dataSet={views}
|
||||
{value}
|
||||
onSelect={handleSelected}
|
||||
identifiers={["tableId", "name"]}
|
||||
/>
|
||||
{/if}
|
||||
{#if queries?.length}
|
||||
|
@ -300,6 +301,7 @@
|
|||
dataSet={queries}
|
||||
{value}
|
||||
onSelect={handleSelected}
|
||||
identifiers={["_id"]}
|
||||
/>
|
||||
{/if}
|
||||
{#if links?.length}
|
||||
|
@ -309,6 +311,7 @@
|
|||
dataSet={links}
|
||||
{value}
|
||||
onSelect={handleSelected}
|
||||
identifiers={["tableId", "fieldName"]}
|
||||
/>
|
||||
{/if}
|
||||
{#if fields?.length}
|
||||
|
@ -318,6 +321,7 @@
|
|||
dataSet={fields}
|
||||
{value}
|
||||
onSelect={handleSelected}
|
||||
identifiers={["providerId", "tableId", "fieldName"]}
|
||||
/>
|
||||
{/if}
|
||||
{#if jsonArrays?.length}
|
||||
|
@ -327,6 +331,7 @@
|
|||
dataSet={jsonArrays}
|
||||
{value}
|
||||
onSelect={handleSelected}
|
||||
identifiers={["providerId", "tableId", "fieldName"]}
|
||||
/>
|
||||
{/if}
|
||||
{#if showDataProviders && dataProviders?.length}
|
||||
|
@ -336,6 +341,7 @@
|
|||
dataSet={dataProviders}
|
||||
{value}
|
||||
onSelect={handleSelected}
|
||||
identifiers={["providerId"]}
|
||||
/>
|
||||
{/if}
|
||||
<DataSourceCategory
|
||||
|
|
|
@ -9,3 +9,4 @@ export {
|
|||
lowercase,
|
||||
isBuilderInputFocused,
|
||||
} from "./helpers"
|
||||
export * as featureFlag from "./featureFlags"
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
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
|
||||
}
|
||||
|
||||
function getManifestDefinition(component: Component) {
|
||||
const componentType = component._component.split("/").slice(-1)[0]
|
||||
const definition =
|
||||
clientManifest[componentType as keyof typeof clientManifest]
|
||||
return definition
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
selectedScreen,
|
||||
hoverStore,
|
||||
componentTreeNodesStore,
|
||||
screenComponentErrors,
|
||||
snippets,
|
||||
} from "@/stores/builder"
|
||||
import ConfirmDialog from "@/components/common/ConfirmDialog.svelte"
|
||||
|
@ -68,6 +69,7 @@
|
|||
port: window.location.port,
|
||||
},
|
||||
snippets: $snippets,
|
||||
componentErrors: $screenComponentErrors,
|
||||
}
|
||||
|
||||
// Refresh the preview when required
|
||||
|
|
|
@ -16,6 +16,7 @@ import { userStore, userSelectedResourceMap, isOnlyUser } from "./users.js"
|
|||
import { deploymentStore } from "./deployments.js"
|
||||
import { contextMenuStore } from "./contextMenu.js"
|
||||
import { snippets } from "./snippets"
|
||||
import { screenComponentErrors } from "./screenComponent"
|
||||
|
||||
// Backend
|
||||
import { tables } from "./tables"
|
||||
|
@ -67,6 +68,7 @@ export {
|
|||
snippets,
|
||||
rowActions,
|
||||
appPublished,
|
||||
screenComponentErrors,
|
||||
}
|
||||
|
||||
export const reset = () => {
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
import { derived } from "svelte/store"
|
||||
import { tables } from "./tables"
|
||||
import { selectedScreen } from "./screens"
|
||||
import { viewsV2 } from "./viewsV2"
|
||||
import { findComponentsBySettingsType } from "@/helpers/screen"
|
||||
import { UIDatasourceType, Screen } from "@budibase/types"
|
||||
import { queries } from "./queries"
|
||||
import { views } from "./views"
|
||||
import { featureFlag } from "@/helpers"
|
||||
|
||||
function reduceBy<TItem extends {}, TKey extends keyof TItem>(
|
||||
key: TKey,
|
||||
list: TItem[]
|
||||
) {
|
||||
return list.reduce(
|
||||
(result, item) => ({
|
||||
...result,
|
||||
[item[key] as string]: item,
|
||||
}),
|
||||
{}
|
||||
)
|
||||
}
|
||||
|
||||
const friendlyNameByType: Partial<Record<UIDatasourceType, string>> = {
|
||||
viewV2: "view",
|
||||
}
|
||||
|
||||
const validationKeyByType: Record<UIDatasourceType, string | null> = {
|
||||
table: "tableId",
|
||||
view: "name",
|
||||
viewV2: "id",
|
||||
query: "_id",
|
||||
custom: null,
|
||||
}
|
||||
|
||||
export const screenComponentErrors = derived(
|
||||
[selectedScreen, tables, views, viewsV2, queries],
|
||||
([$selectedScreen, $tables, $views, $viewsV2, $queries]): Record<
|
||||
string,
|
||||
string[]
|
||||
> => {
|
||||
if (!featureFlag.isEnabled("CHECK_SCREEN_COMPONENT_SETTINGS_ERRORS")) {
|
||||
return {}
|
||||
}
|
||||
function getInvalidDatasources(
|
||||
screen: Screen,
|
||||
datasources: Record<string, any>
|
||||
) {
|
||||
const result: Record<string, string[]> = {}
|
||||
for (const { component, setting } of findComponentsBySettingsType(
|
||||
screen,
|
||||
["table", "dataSource"]
|
||||
)) {
|
||||
const componentSettings = component[setting.key]
|
||||
const { label } = componentSettings
|
||||
const type = componentSettings.type as UIDatasourceType
|
||||
|
||||
const validationKey = validationKeyByType[type]
|
||||
if (!validationKey) {
|
||||
continue
|
||||
}
|
||||
const resourceId = componentSettings[validationKey]
|
||||
if (!datasources[resourceId]) {
|
||||
const friendlyTypeName = friendlyNameByType[type] ?? type
|
||||
result[component._id!] = [
|
||||
`The ${friendlyTypeName} named "${label}" could not be found`,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const datasources = {
|
||||
...reduceBy("_id", $tables.list),
|
||||
...reduceBy("name", $views.list),
|
||||
...reduceBy("id", $viewsV2.list),
|
||||
...reduceBy("_id", $queries.list),
|
||||
}
|
||||
|
||||
return getInvalidDatasources($selectedScreen, datasources)
|
||||
}
|
||||
)
|
|
@ -103,6 +103,7 @@
|
|||
let settingsDefinition
|
||||
let settingsDefinitionMap
|
||||
let missingRequiredSettings = false
|
||||
let componentErrors = false
|
||||
|
||||
// Temporary styles which can be added in the app preview for things like
|
||||
// DND. We clear these whenever a new instance is received.
|
||||
|
@ -137,16 +138,21 @@
|
|||
|
||||
// Derive definition properties which can all be optional, so need to be
|
||||
// coerced to booleans
|
||||
$: componentErrors = instance?._meta?.errors
|
||||
$: hasChildren = !!definition?.hasChildren
|
||||
$: showEmptyState = definition?.showEmptyState !== false
|
||||
$: hasMissingRequiredSettings = missingRequiredSettings?.length > 0
|
||||
$: editable = !!definition?.editable && !hasMissingRequiredSettings
|
||||
$: hasComponentErrors = componentErrors?.length > 0
|
||||
$: requiredAncestors = definition?.requiredAncestors || []
|
||||
$: missingRequiredAncestors = requiredAncestors.filter(
|
||||
ancestor => !$component.ancestors.includes(`${BudibasePrefix}${ancestor}`)
|
||||
)
|
||||
$: hasMissingRequiredAncestors = missingRequiredAncestors?.length > 0
|
||||
$: errorState = hasMissingRequiredSettings || hasMissingRequiredAncestors
|
||||
$: errorState =
|
||||
hasMissingRequiredSettings ||
|
||||
hasMissingRequiredAncestors ||
|
||||
hasComponentErrors
|
||||
|
||||
// Interactive components can be selected, dragged and highlighted inside
|
||||
// the builder preview
|
||||
|
@ -692,6 +698,7 @@
|
|||
<ComponentErrorState
|
||||
{missingRequiredSettings}
|
||||
{missingRequiredAncestors}
|
||||
{componentErrors}
|
||||
/>
|
||||
{:else}
|
||||
<svelte:component this={constructor} bind:this={ref} {...initialSettings}>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
| { key: string; label: string }[]
|
||||
| undefined
|
||||
export let missingRequiredAncestors: string[] | undefined
|
||||
export let componentErrors: string[] | undefined
|
||||
|
||||
const component = getContext("component")
|
||||
const { styleable, builderStore } = getContext("sdk")
|
||||
|
@ -15,6 +16,7 @@
|
|||
$: styles = { ...$component.styles, normal: {}, custom: null, empty: true }
|
||||
$: requiredSetting = missingRequiredSettings?.[0]
|
||||
$: requiredAncestor = missingRequiredAncestors?.[0]
|
||||
$: errorMessage = componentErrors?.[0]
|
||||
</script>
|
||||
|
||||
{#if $builderStore.inBuilder}
|
||||
|
@ -23,6 +25,8 @@
|
|||
<Icon name="Alert" color="var(--spectrum-global-color-static-red-600)" />
|
||||
{#if requiredAncestor}
|
||||
<MissingRequiredAncestor {requiredAncestor} />
|
||||
{:else if errorMessage}
|
||||
{errorMessage}
|
||||
{:else if requiredSetting}
|
||||
<MissingRequiredSetting {requiredSetting} />
|
||||
{/if}
|
||||
|
@ -34,7 +38,7 @@
|
|||
.component-placeholder {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--spectrum-global-color-gray-600);
|
||||
font-size: var(--font-size-s);
|
||||
|
|
|
@ -43,6 +43,7 @@ const loadBudibase = async () => {
|
|||
usedPlugins: window["##BUDIBASE_USED_PLUGINS##"],
|
||||
location: window["##BUDIBASE_LOCATION##"],
|
||||
snippets: window["##BUDIBASE_SNIPPETS##"],
|
||||
componentErrors: window["##BUDIBASE_COMPONENT_ERRORS##"],
|
||||
})
|
||||
|
||||
// Set app ID - this window flag is set by both the preview and the real
|
||||
|
|
|
@ -19,6 +19,7 @@ const createBuilderStore = () => {
|
|||
eventResolvers: {},
|
||||
metadata: null,
|
||||
snippets: null,
|
||||
componentErrors: {},
|
||||
|
||||
// Legacy - allow the builder to specify a layout
|
||||
layout: null,
|
||||
|
|
|
@ -42,6 +42,14 @@ const createScreenStore = () => {
|
|||
if ($builderStore.layout) {
|
||||
activeLayout = $builderStore.layout
|
||||
}
|
||||
|
||||
// Attach meta
|
||||
const errors = $builderStore.componentErrors || {}
|
||||
const attachComponentMeta = component => {
|
||||
component._meta = { errors: errors[component._id] || [] }
|
||||
component._children?.forEach(attachComponentMeta)
|
||||
}
|
||||
attachComponentMeta(activeScreen.props)
|
||||
} else {
|
||||
// Find the correct screen by matching the current route
|
||||
screens = $appStore.screens || []
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// TODO: datasource and defitions are unions of the different implementations. At this point, the datasource does not know what type is being used, and the assignations will cause TS exceptions. Casting it "as any" for now. This should be fixed improving the type usages.
|
||||
|
||||
import { derived, get, Readable, Writable } from "svelte/store"
|
||||
import {
|
||||
DataFetchDefinition,
|
||||
|
@ -10,12 +8,10 @@ import { enrichSchemaWithRelColumns, memo } from "../../../utils"
|
|||
import { cloneDeep } from "lodash"
|
||||
import {
|
||||
SaveRowRequest,
|
||||
SaveTableRequest,
|
||||
UIDatasource,
|
||||
UIFieldMutation,
|
||||
UIFieldSchema,
|
||||
UIRow,
|
||||
UpdateViewRequest,
|
||||
ViewV2Type,
|
||||
} from "@budibase/types"
|
||||
import { Store as StoreContext, BaseStoreProps } from "."
|
||||
|
@ -79,7 +75,7 @@ export const deriveStores = (context: StoreContext): DerivedDatasourceStore => {
|
|||
const schema = derived(definition, $definition => {
|
||||
const schema: Record<string, any> | undefined = getDatasourceSchema({
|
||||
API,
|
||||
datasource: get(datasource) as any, // TODO: see line 1
|
||||
datasource: get(datasource),
|
||||
definition: $definition ?? undefined,
|
||||
})
|
||||
if (!schema) {
|
||||
|
@ -137,7 +133,7 @@ export const deriveStores = (context: StoreContext): DerivedDatasourceStore => {
|
|||
let type = $datasource?.type
|
||||
// @ts-expect-error
|
||||
if (type === "provider") {
|
||||
type = ($datasource as any).value?.datasource?.type // TODO: see line 1
|
||||
type = ($datasource as any).value?.datasource?.type
|
||||
}
|
||||
// Handle calculation views
|
||||
if (
|
||||
|
@ -196,15 +192,13 @@ export const createActions = (context: StoreContext): ActionDatasourceStore => {
|
|||
const refreshDefinition = async () => {
|
||||
const def = await getDatasourceDefinition({
|
||||
API,
|
||||
datasource: get(datasource) as any, // TODO: see line 1
|
||||
datasource: get(datasource),
|
||||
})
|
||||
definition.set(def as any) // TODO: see line 1
|
||||
definition.set(def ?? null)
|
||||
}
|
||||
|
||||
// Saves the datasource definition
|
||||
const saveDefinition = async (
|
||||
newDefinition: SaveTableRequest | UpdateViewRequest
|
||||
) => {
|
||||
const saveDefinition = async (newDefinition: DataFetchDefinition) => {
|
||||
// Update local state
|
||||
const originalDefinition = get(definition)
|
||||
definition.set(newDefinition)
|
||||
|
@ -245,7 +239,7 @@ export const createActions = (context: StoreContext): ActionDatasourceStore => {
|
|||
delete newDefinition.schema[column].default
|
||||
}
|
||||
}
|
||||
return await saveDefinition(newDefinition as any) // TODO: see line 1
|
||||
return await saveDefinition(newDefinition)
|
||||
}
|
||||
|
||||
// Adds a schema mutation for a single field
|
||||
|
@ -321,7 +315,7 @@ export const createActions = (context: StoreContext): ActionDatasourceStore => {
|
|||
await saveDefinition({
|
||||
...$definition,
|
||||
schema: newSchema,
|
||||
} as any) // TODO: see line 1
|
||||
})
|
||||
resetSchemaMutations()
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ export default class ViewFetch extends BaseDataFetch<ViewV1Datasource, Table> {
|
|||
|
||||
getSchema(definition: Table) {
|
||||
const { datasource } = this.options
|
||||
return definition?.views?.[datasource.name]?.schema
|
||||
return definition?.views?.[datasource?.name]?.schema
|
||||
}
|
||||
|
||||
async getData() {
|
||||
|
|
|
@ -101,12 +101,12 @@ export const fetchData = <
|
|||
|
||||
// Creates an empty fetch instance with no datasource configured, so no data
|
||||
// will initially be loaded
|
||||
const createEmptyFetchInstance = ({
|
||||
const createEmptyFetchInstance = <T extends DataFetchDatasource>({
|
||||
API,
|
||||
datasource,
|
||||
}: {
|
||||
API: APIClient
|
||||
datasource: DataFetchDatasource
|
||||
datasource: T
|
||||
}) => {
|
||||
const handler = DataFetchMap[datasource?.type]
|
||||
if (!handler) {
|
||||
|
@ -114,7 +114,7 @@ const createEmptyFetchInstance = ({
|
|||
}
|
||||
return new handler({
|
||||
API,
|
||||
datasource: null as never,
|
||||
datasource: datasource as any,
|
||||
query: null as any,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"../shared-core/src",
|
||||
"../string-templates/src"
|
||||
],
|
||||
"ext": "js,ts,json,svelte",
|
||||
"ext": "js,ts,json,svelte,hbs",
|
||||
"ignore": [
|
||||
"**/*.spec.ts",
|
||||
"**/*.spec.js",
|
||||
|
|
|
@ -73,7 +73,8 @@
|
|||
hiddenComponentIds,
|
||||
usedPlugins,
|
||||
location,
|
||||
snippets
|
||||
snippets,
|
||||
componentErrors
|
||||
} = parsed
|
||||
|
||||
// Set some flags so the app knows we're in the builder
|
||||
|
@ -91,6 +92,7 @@
|
|||
window["##BUDIBASE_USED_PLUGINS##"] = usedPlugins
|
||||
window["##BUDIBASE_LOCATION##"] = location
|
||||
window["##BUDIBASE_SNIPPETS##"] = snippets
|
||||
window['##BUDIBASE_COMPONENT_ERRORS##'] = componentErrors
|
||||
|
||||
// Initialise app
|
||||
try {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export enum FeatureFlag {
|
||||
USE_ZOD_VALIDATOR = "USE_ZOD_VALIDATOR",
|
||||
CHECK_SCREEN_COMPONENT_SETTINGS_ERRORS = "CHECK_SCREEN_COMPONENT_SETTINGS_ERRORS",
|
||||
|
||||
// Account-portal
|
||||
DIRECT_LOGIN_TO_ACCOUNT_PORTAL = "DIRECT_LOGIN_TO_ACCOUNT_PORTAL",
|
||||
|
@ -7,6 +8,7 @@ export enum FeatureFlag {
|
|||
|
||||
export const FeatureFlagDefaults = {
|
||||
[FeatureFlag.USE_ZOD_VALIDATOR]: false,
|
||||
[FeatureFlag.CHECK_SCREEN_COMPONENT_SETTINGS_ERRORS]: false,
|
||||
|
||||
// Account-portal
|
||||
[FeatureFlag.DIRECT_LOGIN_TO_ACCOUNT_PORTAL]: false,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export type UIDatasourceType = "table" | "view" | "viewV2" | "query" | "custom"
|
|
@ -2,3 +2,4 @@ export * from "./stores"
|
|||
export * from "./bindings"
|
||||
export * from "./components"
|
||||
export * from "./dataFetch"
|
||||
export * from "./datasource"
|
||||
|
|
Loading…
Reference in New Issue