Merge branch 'master' of github.com:Budibase/budibase into bindings-panel
This commit is contained in:
commit
28cdfe0548
|
@ -8,41 +8,15 @@ jobs:
|
||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v8
|
- uses: actions/stale@v8
|
||||||
with:
|
with:
|
||||||
days-before-stale: 330
|
# Issues
|
||||||
operations-per-run: 1
|
days-before-stale: 180
|
||||||
# stale rules for PRs
|
stale-issue-label: stale
|
||||||
days-before-pr-stale: 7
|
days-before-close: 30
|
||||||
stale-issue-label: stale
|
stale-issue-message: "This issue has been automatically marked as stale as there has been no activity for 6 months."
|
||||||
exempt-pr-labels: pinned,security,roadmap
|
# Pull requests
|
||||||
days-before-pr-close: 7
|
days-before-pr-stale: 7
|
||||||
days-before-issue-close: 30
|
days-before-pr-close: 14
|
||||||
|
exempt-pr-labels: pinned,security,roadmap
|
||||||
- uses: actions/stale@v8
|
operations-per-run: 100
|
||||||
with:
|
|
||||||
operations-per-run: 3
|
|
||||||
# stale rules for high priority bugs
|
|
||||||
days-before-stale: 30
|
|
||||||
only-issue-labels: bug,High priority
|
|
||||||
stale-issue-label: warn
|
|
||||||
days-before-close: 30
|
|
||||||
|
|
||||||
- uses: actions/stale@v8
|
|
||||||
with:
|
|
||||||
operations-per-run: 3
|
|
||||||
# stale rules for medium priority bugs
|
|
||||||
days-before-stale: 90
|
|
||||||
only-issue-labels: bug,Medium priority
|
|
||||||
stale-issue-label: warn
|
|
||||||
days-before-close: 30
|
|
||||||
|
|
||||||
- uses: actions/stale@v8
|
|
||||||
with:
|
|
||||||
operations-per-run: 3
|
|
||||||
# stale rules for all bugs
|
|
||||||
days-before-stale: 180
|
|
||||||
stale-issue-label: stale
|
|
||||||
only-issue-labels: bug
|
|
||||||
stale-issue-message: "This issue has been automatically marked as stale because it has not had any activity for six months."
|
|
||||||
days-before-close: 30
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||||
"version": "3.2.40",
|
"version": "3.2.44",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"concurrency": 20,
|
"concurrency": 20,
|
||||||
"command": {
|
"command": {
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prebuild": "rimraf dist/",
|
"prebuild": "rimraf dist/",
|
||||||
"prepack": "cp package.json dist",
|
"prepack": "cp package.json dist",
|
||||||
"build": "node ./scripts/build.js && tsc -p tsconfig.build.json --emitDeclarationOnly --paths null",
|
"build": "node ./scripts/build.js && tsc -p tsconfig.build.json --emitDeclarationOnly --paths null && tsc -p tsconfig.test.json --paths null",
|
||||||
"build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
|
"build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
|
||||||
"build:oss": "node ./scripts/build.js",
|
"build:oss": "node ./scripts/build.js",
|
||||||
"check:types": "tsc -p tsconfig.json --noEmit --paths null",
|
"check:types": "tsc -p tsconfig.json --noEmit --paths null",
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.build.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"include": ["tests/**/*.js", "tests/**/*.ts"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
|
@ -49,7 +49,6 @@
|
||||||
import { RowUtils, canBeDisplayColumn } from "@budibase/frontend-core"
|
import { RowUtils, canBeDisplayColumn } from "@budibase/frontend-core"
|
||||||
import ServerBindingPanel from "@/components/common/bindings/ServerBindingPanel.svelte"
|
import ServerBindingPanel from "@/components/common/bindings/ServerBindingPanel.svelte"
|
||||||
import OptionsEditor from "./OptionsEditor.svelte"
|
import OptionsEditor from "./OptionsEditor.svelte"
|
||||||
import { isEnabled } from "@/helpers/featureFlags"
|
|
||||||
import { getUserBindings } from "@/dataBinding"
|
import { getUserBindings } from "@/dataBinding"
|
||||||
|
|
||||||
export let field
|
export let field
|
||||||
|
@ -168,7 +167,6 @@
|
||||||
// used to select what different options can be displayed for column type
|
// used to select what different options can be displayed for column type
|
||||||
$: canBeDisplay =
|
$: canBeDisplay =
|
||||||
canBeDisplayColumn(editableColumn) && !editableColumn.autocolumn
|
canBeDisplayColumn(editableColumn) && !editableColumn.autocolumn
|
||||||
$: defaultValuesEnabled = isEnabled("DEFAULT_VALUES")
|
|
||||||
$: canHaveDefault = !required && canHaveDefaultColumn(editableColumn.type)
|
$: canHaveDefault = !required && canHaveDefaultColumn(editableColumn.type)
|
||||||
$: canBeRequired =
|
$: canBeRequired =
|
||||||
editableColumn?.type !== FieldType.LINK &&
|
editableColumn?.type !== FieldType.LINK &&
|
||||||
|
@ -300,7 +298,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we don't have a default value if we can't have one
|
// Ensure we don't have a default value if we can't have one
|
||||||
if (!canHaveDefault || !defaultValuesEnabled) {
|
if (!canHaveDefault) {
|
||||||
delete saveColumn.default
|
delete saveColumn.default
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -848,51 +846,49 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if defaultValuesEnabled}
|
{#if editableColumn.type === FieldType.OPTIONS}
|
||||||
{#if editableColumn.type === FieldType.OPTIONS}
|
<Select
|
||||||
<Select
|
disabled={!canHaveDefault}
|
||||||
disabled={!canHaveDefault}
|
options={editableColumn.constraints?.inclusion || []}
|
||||||
options={editableColumn.constraints?.inclusion || []}
|
label="Default value"
|
||||||
label="Default value"
|
value={editableColumn.default}
|
||||||
value={editableColumn.default}
|
on:change={e => (editableColumn.default = e.detail)}
|
||||||
on:change={e => (editableColumn.default = e.detail)}
|
placeholder="None"
|
||||||
placeholder="None"
|
/>
|
||||||
/>
|
{:else if editableColumn.type === FieldType.ARRAY}
|
||||||
{:else if editableColumn.type === FieldType.ARRAY}
|
<Multiselect
|
||||||
<Multiselect
|
disabled={!canHaveDefault}
|
||||||
disabled={!canHaveDefault}
|
options={editableColumn.constraints?.inclusion || []}
|
||||||
options={editableColumn.constraints?.inclusion || []}
|
label="Default value"
|
||||||
label="Default value"
|
value={editableColumn.default}
|
||||||
value={editableColumn.default}
|
on:change={e =>
|
||||||
on:change={e =>
|
(editableColumn.default = e.detail?.length ? e.detail : undefined)}
|
||||||
(editableColumn.default = e.detail?.length ? e.detail : undefined)}
|
placeholder="None"
|
||||||
placeholder="None"
|
/>
|
||||||
/>
|
{:else if editableColumn.subtype === BBReferenceFieldSubType.USER}
|
||||||
{:else if editableColumn.subtype === BBReferenceFieldSubType.USER}
|
{@const defaultValue =
|
||||||
{@const defaultValue =
|
editableColumn.type === FieldType.BB_REFERENCE_SINGLE
|
||||||
editableColumn.type === FieldType.BB_REFERENCE_SINGLE
|
? SingleUserDefault
|
||||||
? SingleUserDefault
|
: MultiUserDefault}
|
||||||
: MultiUserDefault}
|
<Toggle
|
||||||
<Toggle
|
disabled={!canHaveDefault}
|
||||||
disabled={!canHaveDefault}
|
text="Default to current user"
|
||||||
text="Default to current user"
|
value={editableColumn.default === defaultValue}
|
||||||
value={editableColumn.default === defaultValue}
|
on:change={e =>
|
||||||
on:change={e =>
|
(editableColumn.default = e.detail ? defaultValue : undefined)}
|
||||||
(editableColumn.default = e.detail ? defaultValue : undefined)}
|
/>
|
||||||
/>
|
{:else}
|
||||||
{:else}
|
<ModalBindableInput
|
||||||
<ModalBindableInput
|
disabled={!canHaveDefault}
|
||||||
disabled={!canHaveDefault}
|
panel={ServerBindingPanel}
|
||||||
panel={ServerBindingPanel}
|
title="Default value"
|
||||||
title="Default value"
|
label="Default value"
|
||||||
label="Default value"
|
placeholder="None"
|
||||||
placeholder="None"
|
value={editableColumn.default}
|
||||||
value={editableColumn.default}
|
on:change={e => (editableColumn.default = e.detail)}
|
||||||
on:change={e => (editableColumn.default = e.detail)}
|
bindings={defaultValueBindings}
|
||||||
bindings={defaultValueBindings}
|
allowJS
|
||||||
allowJS
|
/>
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
{/if}
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,9 @@
|
||||||
let loading = false
|
let loading = false
|
||||||
let deleteConfirmationDialog
|
let deleteConfirmationDialog
|
||||||
|
|
||||||
$: defaultName = getSequentialName($snippets, "MySnippet", x => x.name)
|
$: defaultName = getSequentialName($snippets, "MySnippet", {
|
||||||
|
getName: x => x.name,
|
||||||
|
})
|
||||||
$: key = snippet?.name
|
$: key = snippet?.name
|
||||||
$: name = snippet?.name || defaultName
|
$: name = snippet?.name || defaultName
|
||||||
$: code = snippet?.code ? encodeJSBinding(snippet.code) : ""
|
$: code = snippet?.code ? encodeJSBinding(snippet.code) : ""
|
||||||
|
|
|
@ -16,7 +16,10 @@ export {
|
||||||
|
|
||||||
export const AUTO_COLUMN_SUB_TYPES = AutoFieldSubType
|
export const AUTO_COLUMN_SUB_TYPES = AutoFieldSubType
|
||||||
|
|
||||||
export const AUTO_COLUMN_DISPLAY_NAMES = {
|
export const AUTO_COLUMN_DISPLAY_NAMES: Record<
|
||||||
|
keyof typeof AUTO_COLUMN_SUB_TYPES,
|
||||||
|
string
|
||||||
|
> = {
|
||||||
AUTO_ID: "Auto ID",
|
AUTO_ID: "Auto ID",
|
||||||
CREATED_BY: "Created By",
|
CREATED_BY: "Created By",
|
||||||
CREATED_AT: "Created At",
|
CREATED_AT: "Created At",
|
||||||
|
@ -209,13 +212,6 @@ export const Roles = {
|
||||||
BUILDER: "BUILDER",
|
BUILDER: "BUILDER",
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAutoColumnUserRelationship(subtype) {
|
|
||||||
return (
|
|
||||||
subtype === AUTO_COLUMN_SUB_TYPES.CREATED_BY ||
|
|
||||||
subtype === AUTO_COLUMN_SUB_TYPES.UPDATED_BY
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PrettyRelationshipDefinitions = {
|
export const PrettyRelationshipDefinitions = {
|
||||||
MANY: "Many rows",
|
MANY: "Many rows",
|
||||||
ONE: "One row",
|
ONE: "One row",
|
|
@ -10,13 +10,13 @@
|
||||||
*
|
*
|
||||||
* Repl
|
* Repl
|
||||||
*/
|
*/
|
||||||
export const duplicateName = (name, allNames) => {
|
export const duplicateName = (name: string, allNames: string[]) => {
|
||||||
const duplicatePattern = new RegExp(`\\s(\\d+)$`)
|
const duplicatePattern = new RegExp(`\\s(\\d+)$`)
|
||||||
const baseName = name.split(duplicatePattern)[0]
|
const baseName = name.split(duplicatePattern)[0]
|
||||||
const isDuplicate = new RegExp(`${baseName}\\s(\\d+)$`)
|
const isDuplicate = new RegExp(`${baseName}\\s(\\d+)$`)
|
||||||
|
|
||||||
// get the sequence from matched names
|
// get the sequence from matched names
|
||||||
const sequence = []
|
const sequence: number[] = []
|
||||||
allNames.filter(n => {
|
allNames.filter(n => {
|
||||||
if (n === baseName) {
|
if (n === baseName) {
|
||||||
return true
|
return true
|
||||||
|
@ -70,12 +70,18 @@ export const duplicateName = (name, allNames) => {
|
||||||
* @param getName optional function to extract the name for an item, if not a
|
* @param getName optional function to extract the name for an item, if not a
|
||||||
* flat array of strings
|
* flat array of strings
|
||||||
*/
|
*/
|
||||||
export const getSequentialName = (
|
export const getSequentialName = <T extends any>(
|
||||||
items,
|
items: T[] | null,
|
||||||
prefix,
|
prefix: string | null,
|
||||||
{ getName = x => x, numberFirstItem = false } = {}
|
{
|
||||||
|
getName,
|
||||||
|
numberFirstItem,
|
||||||
|
}: {
|
||||||
|
getName?: (item: T) => string
|
||||||
|
numberFirstItem?: boolean
|
||||||
|
} = {}
|
||||||
) => {
|
) => {
|
||||||
if (!prefix?.length || !getName) {
|
if (!prefix?.length) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const trimmedPrefix = prefix.trim()
|
const trimmedPrefix = prefix.trim()
|
||||||
|
@ -85,7 +91,7 @@ export const getSequentialName = (
|
||||||
}
|
}
|
||||||
let max = 0
|
let max = 0
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const name = getName(item)
|
const name = getName?.(item) ?? item
|
||||||
if (typeof name !== "string" || !name.startsWith(trimmedPrefix)) {
|
if (typeof name !== "string" || !name.startsWith(trimmedPrefix)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
|
import { FeatureFlag } from "@budibase/types"
|
||||||
import { auth } from "../stores/portal"
|
import { auth } from "../stores/portal"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
|
|
||||||
export const isEnabled = featureFlag => {
|
export const isEnabled = (featureFlag: FeatureFlag | `${FeatureFlag}`) => {
|
||||||
const user = get(auth).user
|
const user = get(auth).user
|
||||||
return !!user?.flags?.[featureFlag]
|
return !!user?.flags?.[featureFlag]
|
||||||
}
|
}
|
|
@ -1,13 +1,21 @@
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
|
|
||||||
export default function (url) {
|
export default function (url: string) {
|
||||||
const store = writable({ status: "LOADING", data: {}, error: {} })
|
const store = writable<{
|
||||||
|
status: "LOADING" | "SUCCESS" | "ERROR"
|
||||||
|
data: object
|
||||||
|
error?: unknown
|
||||||
|
}>({
|
||||||
|
status: "LOADING",
|
||||||
|
data: {},
|
||||||
|
error: {},
|
||||||
|
})
|
||||||
|
|
||||||
async function get() {
|
async function get() {
|
||||||
store.update(u => ({ ...u, status: "LOADING" }))
|
store.update(u => ({ ...u, status: "LOADING" }))
|
||||||
try {
|
try {
|
||||||
const data = await API.get({ url })
|
const data = await API.get<object>({ url })
|
||||||
store.set({ data, status: "SUCCESS" })
|
store.set({ data, status: "SUCCESS" })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
store.set({ data: {}, error: e, status: "ERROR" })
|
store.set({ data: {}, error: e, status: "ERROR" })
|
|
@ -1,46 +0,0 @@
|
||||||
import { last, flow } from "lodash/fp"
|
|
||||||
|
|
||||||
export const buildStyle = styles => {
|
|
||||||
let str = ""
|
|
||||||
for (let s in styles) {
|
|
||||||
if (styles[s]) {
|
|
||||||
let key = convertCamel(s)
|
|
||||||
str += `${key}: ${styles[s]}; `
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
export const convertCamel = str => {
|
|
||||||
return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const pipe = (arg, funcs) => flow(funcs)(arg)
|
|
||||||
|
|
||||||
export const capitalise = s => {
|
|
||||||
if (!s) {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return s.substring(0, 1).toUpperCase() + s.substring(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const lowercase = s => s.substring(0, 1).toLowerCase() + s.substring(1)
|
|
||||||
|
|
||||||
export const lowercaseExceptFirst = s =>
|
|
||||||
s.charAt(0) + s.substring(1).toLowerCase()
|
|
||||||
|
|
||||||
export const get_name = s => (!s ? "" : last(s.split("/")))
|
|
||||||
|
|
||||||
export const get_capitalised_name = name => pipe(name, [get_name, capitalise])
|
|
||||||
|
|
||||||
export const isBuilderInputFocused = e => {
|
|
||||||
const activeTag = document.activeElement?.tagName.toLowerCase()
|
|
||||||
const inCodeEditor = document.activeElement?.classList?.contains("cm-content")
|
|
||||||
if (
|
|
||||||
(inCodeEditor || ["input", "textarea"].indexOf(activeTag) !== -1) &&
|
|
||||||
e.key !== "Escape"
|
|
||||||
) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import type { Many } from "lodash"
|
||||||
|
import { last, flow } from "lodash/fp"
|
||||||
|
|
||||||
|
export const buildStyle = (styles: Record<string, any>) => {
|
||||||
|
let str = ""
|
||||||
|
for (let s in styles) {
|
||||||
|
if (styles[s]) {
|
||||||
|
let key = convertCamel(s)
|
||||||
|
str += `${key}: ${styles[s]}; `
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
export const convertCamel = (str: string) => {
|
||||||
|
return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const pipe = (arg: string, funcs: Many<(...args: any[]) => any>) =>
|
||||||
|
flow(funcs)(arg)
|
||||||
|
|
||||||
|
export const capitalise = (s: string) => {
|
||||||
|
if (!s) {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return s.substring(0, 1).toUpperCase() + s.substring(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const lowercase = (s: string) =>
|
||||||
|
s.substring(0, 1).toLowerCase() + s.substring(1)
|
||||||
|
|
||||||
|
export const lowercaseExceptFirst = (s: string) =>
|
||||||
|
s.charAt(0) + s.substring(1).toLowerCase()
|
||||||
|
|
||||||
|
export const get_name = (s: string) => (!s ? "" : last(s.split("/")))
|
||||||
|
|
||||||
|
export const get_capitalised_name = (name: string) =>
|
||||||
|
pipe(name, [get_name, capitalise])
|
||||||
|
|
||||||
|
export const isBuilderInputFocused = (e: KeyboardEvent) => {
|
||||||
|
const activeTag = document.activeElement?.tagName.toLowerCase()
|
||||||
|
const inCodeEditor = document.activeElement?.classList?.contains("cm-content")
|
||||||
|
if (
|
||||||
|
(inCodeEditor || ["input", "textarea"].indexOf(activeTag!) !== -1) &&
|
||||||
|
e.key !== "Escape"
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -1,7 +0,0 @@
|
||||||
function handleEnter(fnc) {
|
|
||||||
return e => e.key === "Enter" && fnc()
|
|
||||||
}
|
|
||||||
|
|
||||||
export const keyUtils = {
|
|
||||||
handleEnter,
|
|
||||||
}
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
function handleEnter(fnc: () => void) {
|
||||||
|
return (e: KeyboardEvent) => e.key === "Enter" && fnc()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const keyUtils = {
|
||||||
|
handleEnter,
|
||||||
|
}
|
|
@ -1,6 +1,16 @@
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
|
|
||||||
function defaultValue() {
|
interface PaginationStore {
|
||||||
|
nextPage: string | null | undefined
|
||||||
|
page: string | null | undefined
|
||||||
|
hasPrevPage: boolean
|
||||||
|
hasNextPage: boolean
|
||||||
|
loading: boolean
|
||||||
|
pageNumber: number
|
||||||
|
pages: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultValue(): PaginationStore {
|
||||||
return {
|
return {
|
||||||
nextPage: null,
|
nextPage: null,
|
||||||
page: undefined,
|
page: undefined,
|
||||||
|
@ -29,13 +39,13 @@ export function createPaginationStore() {
|
||||||
update(state => {
|
update(state => {
|
||||||
state.pageNumber++
|
state.pageNumber++
|
||||||
state.page = state.nextPage
|
state.page = state.nextPage
|
||||||
state.pages.push(state.page)
|
state.pages.push(state.page!)
|
||||||
state.hasPrevPage = state.pageNumber > 1
|
state.hasPrevPage = state.pageNumber > 1
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetched(hasNextPage, nextPage) {
|
function fetched(hasNextPage: boolean, nextPage: string) {
|
||||||
update(state => {
|
update(state => {
|
||||||
state.hasNextPage = hasNextPage
|
state.hasNextPage = hasNextPage
|
||||||
state.nextPage = nextPage
|
state.nextPage = nextPage
|
|
@ -1,6 +1,6 @@
|
||||||
import { PlanType } from "@budibase/types"
|
import { PlanType } from "@budibase/types"
|
||||||
|
|
||||||
export function getFormattedPlanName(userPlanType) {
|
export function getFormattedPlanName(userPlanType: PlanType) {
|
||||||
let planName
|
let planName
|
||||||
switch (userPlanType) {
|
switch (userPlanType) {
|
||||||
case PlanType.PRO:
|
case PlanType.PRO:
|
||||||
|
@ -29,6 +29,6 @@ export function getFormattedPlanName(userPlanType) {
|
||||||
return `${planName} Plan`
|
return `${planName} Plan`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isPremiumOrAbove(userPlanType) {
|
export function isPremiumOrAbove(userPlanType: PlanType) {
|
||||||
return ![PlanType.PRO, PlanType.TEAM, PlanType.FREE].includes(userPlanType)
|
return ![PlanType.PRO, PlanType.TEAM, PlanType.FREE].includes(userPlanType)
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
export default function (url) {
|
export default function (url: string) {
|
||||||
return url
|
return url
|
||||||
.split("/")
|
.split("/")
|
||||||
.map(part => {
|
.map(part => {
|
|
@ -1,75 +0,0 @@
|
||||||
import { FieldType } from "@budibase/types"
|
|
||||||
import { ActionStepID } from "@/constants/backend/automations"
|
|
||||||
import { TableNames } from "@/constants"
|
|
||||||
import {
|
|
||||||
AUTO_COLUMN_DISPLAY_NAMES,
|
|
||||||
AUTO_COLUMN_SUB_TYPES,
|
|
||||||
FIELDS,
|
|
||||||
isAutoColumnUserRelationship,
|
|
||||||
} from "@/constants/backend"
|
|
||||||
import { isEnabled } from "@/helpers/featureFlags"
|
|
||||||
|
|
||||||
export function getAutoColumnInformation(enabled = true) {
|
|
||||||
let info = {}
|
|
||||||
for (const [key, subtype] of Object.entries(AUTO_COLUMN_SUB_TYPES)) {
|
|
||||||
// Because it's possible to replicate the functionality of CREATED_AT and
|
|
||||||
// CREATED_BY columns, we disable their creation when the DEFAULT_VALUES
|
|
||||||
// feature flag is enabled.
|
|
||||||
if (isEnabled("DEFAULT_VALUES")) {
|
|
||||||
if (
|
|
||||||
subtype === AUTO_COLUMN_SUB_TYPES.CREATED_AT ||
|
|
||||||
subtype === AUTO_COLUMN_SUB_TYPES.CREATED_BY
|
|
||||||
) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info[subtype] = { enabled, name: AUTO_COLUMN_DISPLAY_NAMES[key] }
|
|
||||||
}
|
|
||||||
return info
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildAutoColumn(tableName, name, subtype) {
|
|
||||||
let type, constraints
|
|
||||||
switch (subtype) {
|
|
||||||
case AUTO_COLUMN_SUB_TYPES.UPDATED_BY:
|
|
||||||
case AUTO_COLUMN_SUB_TYPES.CREATED_BY:
|
|
||||||
type = FieldType.LINK
|
|
||||||
constraints = FIELDS.LINK.constraints
|
|
||||||
break
|
|
||||||
case AUTO_COLUMN_SUB_TYPES.AUTO_ID:
|
|
||||||
type = FieldType.NUMBER
|
|
||||||
constraints = FIELDS.NUMBER.constraints
|
|
||||||
break
|
|
||||||
case AUTO_COLUMN_SUB_TYPES.UPDATED_AT:
|
|
||||||
case AUTO_COLUMN_SUB_TYPES.CREATED_AT:
|
|
||||||
type = FieldType.DATETIME
|
|
||||||
constraints = FIELDS.DATETIME.constraints
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
type = FieldType.STRING
|
|
||||||
constraints = FIELDS.STRING.constraints
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if (Object.values(AUTO_COLUMN_SUB_TYPES).indexOf(subtype) === -1) {
|
|
||||||
throw "Cannot build auto column with supplied subtype"
|
|
||||||
}
|
|
||||||
const base = {
|
|
||||||
name,
|
|
||||||
type,
|
|
||||||
subtype,
|
|
||||||
icon: "ri-magic-line",
|
|
||||||
autocolumn: true,
|
|
||||||
constraints,
|
|
||||||
}
|
|
||||||
if (isAutoColumnUserRelationship(subtype)) {
|
|
||||||
base.tableId = TableNames.USERS
|
|
||||||
base.fieldName = `${tableName}-${name}`
|
|
||||||
}
|
|
||||||
return base
|
|
||||||
}
|
|
||||||
|
|
||||||
export function checkForCollectStep(automation) {
|
|
||||||
return automation.definition.steps.some(
|
|
||||||
step => step.stepId === ActionStepID.COLLECT
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
import {
|
||||||
|
AutoFieldSubType,
|
||||||
|
Automation,
|
||||||
|
DateFieldMetadata,
|
||||||
|
FieldType,
|
||||||
|
NumberFieldMetadata,
|
||||||
|
RelationshipFieldMetadata,
|
||||||
|
RelationshipType,
|
||||||
|
} from "@budibase/types"
|
||||||
|
import { ActionStepID } from "@/constants/backend/automations"
|
||||||
|
import { TableNames } from "@/constants"
|
||||||
|
import {
|
||||||
|
AUTO_COLUMN_DISPLAY_NAMES,
|
||||||
|
AUTO_COLUMN_SUB_TYPES,
|
||||||
|
FIELDS,
|
||||||
|
} from "@/constants/backend"
|
||||||
|
import { utils } from "@budibase/shared-core"
|
||||||
|
|
||||||
|
type AutoColumnInformation = Partial<
|
||||||
|
Record<AutoFieldSubType, { enabled: boolean; name: string }>
|
||||||
|
>
|
||||||
|
|
||||||
|
export function getAutoColumnInformation(
|
||||||
|
enabled = true
|
||||||
|
): AutoColumnInformation {
|
||||||
|
const info: AutoColumnInformation = {}
|
||||||
|
for (const [key, subtype] of Object.entries(AUTO_COLUMN_SUB_TYPES)) {
|
||||||
|
// Because it's possible to replicate the functionality of CREATED_AT and
|
||||||
|
// CREATED_BY columns with user column default values, we disable their creation
|
||||||
|
if (
|
||||||
|
subtype === AUTO_COLUMN_SUB_TYPES.CREATED_AT ||
|
||||||
|
subtype === AUTO_COLUMN_SUB_TYPES.CREATED_BY
|
||||||
|
) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const typedKey = key as keyof typeof AUTO_COLUMN_SUB_TYPES
|
||||||
|
info[subtype] = {
|
||||||
|
enabled,
|
||||||
|
name: AUTO_COLUMN_DISPLAY_NAMES[typedKey],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildAutoColumn(
|
||||||
|
tableName: string,
|
||||||
|
name: string,
|
||||||
|
subtype: AutoFieldSubType
|
||||||
|
): RelationshipFieldMetadata | NumberFieldMetadata | DateFieldMetadata {
|
||||||
|
const base = {
|
||||||
|
name,
|
||||||
|
icon: "ri-magic-line",
|
||||||
|
autocolumn: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (subtype) {
|
||||||
|
case AUTO_COLUMN_SUB_TYPES.UPDATED_BY:
|
||||||
|
case AUTO_COLUMN_SUB_TYPES.CREATED_BY:
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
type: FieldType.LINK,
|
||||||
|
subtype,
|
||||||
|
constraints: FIELDS.LINK.constraints,
|
||||||
|
tableId: TableNames.USERS,
|
||||||
|
fieldName: `${tableName}-${name}`,
|
||||||
|
relationshipType: RelationshipType.MANY_TO_ONE,
|
||||||
|
}
|
||||||
|
|
||||||
|
case AUTO_COLUMN_SUB_TYPES.AUTO_ID:
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
type: FieldType.NUMBER,
|
||||||
|
subtype,
|
||||||
|
constraints: FIELDS.NUMBER.constraints,
|
||||||
|
}
|
||||||
|
case AUTO_COLUMN_SUB_TYPES.UPDATED_AT:
|
||||||
|
case AUTO_COLUMN_SUB_TYPES.CREATED_AT:
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
type: FieldType.DATETIME,
|
||||||
|
subtype,
|
||||||
|
constraints: FIELDS.DATETIME.constraints,
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw utils.unreachable(subtype, {
|
||||||
|
message: "Cannot build auto column with supplied subtype",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkForCollectStep(automation: Automation) {
|
||||||
|
return automation.definition.steps.some(
|
||||||
|
step => step.stepId === ActionStepID.COLLECT
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
export const suppressWarnings = warnings => {
|
export const suppressWarnings = (warnings: string[]) => {
|
||||||
if (!warnings?.length) {
|
if (!warnings?.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
|
@ -1,27 +0,0 @@
|
||||||
import { writable } from "svelte/store"
|
|
||||||
import { API } from "@/api"
|
|
||||||
|
|
||||||
export function createPermissionStore() {
|
|
||||||
const { subscribe } = writable([])
|
|
||||||
|
|
||||||
return {
|
|
||||||
subscribe,
|
|
||||||
save: async ({ level, role, resource }) => {
|
|
||||||
return await API.updatePermissionForResource(resource, role, level)
|
|
||||||
},
|
|
||||||
remove: async ({ level, role, resource }) => {
|
|
||||||
return await API.removePermissionFromResource(resource, role, level)
|
|
||||||
},
|
|
||||||
forResource: async resourceId => {
|
|
||||||
return (await API.getPermissionForResource(resourceId)).permissions
|
|
||||||
},
|
|
||||||
forResourceDetailed: async resourceId => {
|
|
||||||
return await API.getPermissionForResource(resourceId)
|
|
||||||
},
|
|
||||||
getDependantsInfo: async resourceId => {
|
|
||||||
return await API.getDependants(resourceId)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const permissions = createPermissionStore()
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { BudiStore } from "../BudiStore"
|
||||||
|
import { API } from "@/api"
|
||||||
|
import {
|
||||||
|
PermissionLevel,
|
||||||
|
GetResourcePermsResponse,
|
||||||
|
GetDependantResourcesResponse,
|
||||||
|
ResourcePermissionInfo,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
|
interface Permission {
|
||||||
|
level: PermissionLevel
|
||||||
|
role: string
|
||||||
|
resource: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PermissionStore extends BudiStore<Permission[]> {
|
||||||
|
constructor() {
|
||||||
|
super([])
|
||||||
|
}
|
||||||
|
|
||||||
|
save = async (permission: Permission) => {
|
||||||
|
const { level, role, resource } = permission
|
||||||
|
return await API.updatePermissionForResource(resource, role, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
remove = async (permission: Permission) => {
|
||||||
|
const { level, role, resource } = permission
|
||||||
|
return await API.removePermissionFromResource(resource, role, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
forResource = async (
|
||||||
|
resourceId: string
|
||||||
|
): Promise<Record<string, ResourcePermissionInfo>> => {
|
||||||
|
return (await API.getPermissionForResource(resourceId)).permissions
|
||||||
|
}
|
||||||
|
|
||||||
|
forResourceDetailed = async (
|
||||||
|
resourceId: string
|
||||||
|
): Promise<GetResourcePermsResponse> => {
|
||||||
|
return await API.getPermissionForResource(resourceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
getDependantsInfo = async (
|
||||||
|
resourceId: string
|
||||||
|
): Promise<GetDependantResourcesResponse> => {
|
||||||
|
return await API.getDependants(resourceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const permissions = new PermissionStore()
|
|
@ -62,7 +62,7 @@ export class RowActionStore extends BudiStore<RowActionState> {
|
||||||
const existingRowActions = get(this)[tableId] || []
|
const existingRowActions = get(this)[tableId] || []
|
||||||
name = getSequentialName(existingRowActions, "New row action ", {
|
name = getSequentialName(existingRowActions, "New row action ", {
|
||||||
getName: x => x.name,
|
getName: x => x.name,
|
||||||
})
|
})!
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
import { writable, derived } from "svelte/store"
|
|
||||||
import { tables } from "./tables"
|
|
||||||
import { API } from "@/api"
|
|
||||||
|
|
||||||
export function createViewsStore() {
|
|
||||||
const store = writable({
|
|
||||||
selectedViewName: null,
|
|
||||||
})
|
|
||||||
const derivedStore = derived([store, tables], ([$store, $tables]) => {
|
|
||||||
let list = []
|
|
||||||
$tables.list?.forEach(table => {
|
|
||||||
const views = Object.values(table?.views || {}).filter(view => {
|
|
||||||
return view.version !== 2
|
|
||||||
})
|
|
||||||
list = list.concat(views)
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
...$store,
|
|
||||||
list,
|
|
||||||
selected: list.find(view => view.name === $store.selectedViewName),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const select = name => {
|
|
||||||
store.update(state => ({
|
|
||||||
...state,
|
|
||||||
selectedViewName: name,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteView = async view => {
|
|
||||||
await API.deleteView(view.name)
|
|
||||||
|
|
||||||
// Update tables
|
|
||||||
tables.update(state => {
|
|
||||||
const table = state.list.find(table => table._id === view.tableId)
|
|
||||||
delete table.views[view.name]
|
|
||||||
return { ...state }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const save = async view => {
|
|
||||||
const savedView = await API.saveView(view)
|
|
||||||
select(view.name)
|
|
||||||
|
|
||||||
// Update tables
|
|
||||||
tables.update(state => {
|
|
||||||
const table = state.list.find(table => table._id === view.tableId)
|
|
||||||
if (table) {
|
|
||||||
if (view.originalName) {
|
|
||||||
delete table.views[view.originalName]
|
|
||||||
}
|
|
||||||
table.views[view.name] = savedView
|
|
||||||
}
|
|
||||||
return { ...state }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
subscribe: derivedStore.subscribe,
|
|
||||||
select,
|
|
||||||
delete: deleteView,
|
|
||||||
save,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const views = createViewsStore()
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
import { DerivedBudiStore } from "../BudiStore"
|
||||||
|
import { tables } from "./tables"
|
||||||
|
import { API } from "@/api"
|
||||||
|
import { View } from "@budibase/types"
|
||||||
|
import { helpers } from "@budibase/shared-core"
|
||||||
|
import { derived, Writable } from "svelte/store"
|
||||||
|
|
||||||
|
interface BuilderViewStore {
|
||||||
|
selectedViewName: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DerivedViewStore extends BuilderViewStore {
|
||||||
|
list: View[]
|
||||||
|
selected?: View
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ViewsStore extends DerivedBudiStore<
|
||||||
|
BuilderViewStore,
|
||||||
|
DerivedViewStore
|
||||||
|
> {
|
||||||
|
constructor() {
|
||||||
|
const makeDerivedStore = (store: Writable<BuilderViewStore>) => {
|
||||||
|
return derived([store, tables], ([$store, $tables]): DerivedViewStore => {
|
||||||
|
let list: View[] = []
|
||||||
|
$tables.list?.forEach(table => {
|
||||||
|
const views = Object.values(table?.views || {}).filter(
|
||||||
|
(view): view is View => !helpers.views.isV2(view)
|
||||||
|
)
|
||||||
|
list = list.concat(views)
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
selectedViewName: $store.selectedViewName,
|
||||||
|
list,
|
||||||
|
selected: list.find(view => view.name === $store.selectedViewName),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
selectedViewName: null,
|
||||||
|
},
|
||||||
|
makeDerivedStore
|
||||||
|
)
|
||||||
|
|
||||||
|
this.select = this.select.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
select = (name: string) => {
|
||||||
|
this.store.update(state => ({
|
||||||
|
...state,
|
||||||
|
selectedViewName: name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
delete = async (view: View) => {
|
||||||
|
if (!view.name) {
|
||||||
|
throw new Error("View name is required")
|
||||||
|
}
|
||||||
|
await API.deleteView(view.name)
|
||||||
|
|
||||||
|
// Update tables
|
||||||
|
tables.update(state => {
|
||||||
|
const table = state.list.find(table => table._id === view.tableId)
|
||||||
|
if (table?.views && view.name) {
|
||||||
|
delete table.views[view.name]
|
||||||
|
}
|
||||||
|
return { ...state }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
save = async (view: View & { originalName?: string }) => {
|
||||||
|
if (!view.name) {
|
||||||
|
throw new Error("View name is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
const savedView = await API.saveView(view)
|
||||||
|
this.select(view.name)
|
||||||
|
|
||||||
|
// Update tables
|
||||||
|
tables.update(state => {
|
||||||
|
const table = state.list.find(table => table._id === view.tableId)
|
||||||
|
if (table?.views && view.name) {
|
||||||
|
if (view.originalName) {
|
||||||
|
delete table.views[view.originalName]
|
||||||
|
}
|
||||||
|
table.views[view.name] = savedView
|
||||||
|
}
|
||||||
|
return { ...state }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const views = new ViewsStore()
|
|
@ -39,7 +39,7 @@
|
||||||
getActionContextKey,
|
getActionContextKey,
|
||||||
getActionDependentContextKeys,
|
getActionDependentContextKeys,
|
||||||
} from "../utils/buttonActions.js"
|
} from "../utils/buttonActions.js"
|
||||||
import { gridLayout } from "utils/grid.js"
|
import { gridLayout } from "utils/grid"
|
||||||
|
|
||||||
export let instance = {}
|
export let instance = {}
|
||||||
export let parent = null
|
export let parent = null
|
||||||
|
|
|
@ -1,22 +1,39 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import { Pagination, ProgressCircle } from "@budibase/bbui"
|
import { Pagination, ProgressCircle } from "@budibase/bbui"
|
||||||
import { fetchData, QueryUtils } from "@budibase/frontend-core"
|
import { fetchData, QueryUtils } from "@budibase/frontend-core"
|
||||||
import { LogicalOperator, EmptyFilterOption } from "@budibase/types"
|
import {
|
||||||
|
LogicalOperator,
|
||||||
|
EmptyFilterOption,
|
||||||
|
TableSchema,
|
||||||
|
SortOrder,
|
||||||
|
SearchFilters,
|
||||||
|
UISearchFilter,
|
||||||
|
DataFetchDatasource,
|
||||||
|
UserDatasource,
|
||||||
|
GroupUserDatasource,
|
||||||
|
DataFetchOptions,
|
||||||
|
} from "@budibase/types"
|
||||||
|
import { SDK, Component } from "../../index"
|
||||||
|
|
||||||
export let dataSource
|
type ProviderDatasource = Exclude<
|
||||||
export let filter
|
DataFetchDatasource,
|
||||||
export let sortColumn
|
UserDatasource | GroupUserDatasource
|
||||||
export let sortOrder
|
>
|
||||||
export let limit
|
|
||||||
export let paginate
|
|
||||||
export let autoRefresh
|
|
||||||
|
|
||||||
const { styleable, Provider, ActionTypes, API } = getContext("sdk")
|
export let dataSource: ProviderDatasource
|
||||||
const component = getContext("component")
|
export let filter: UISearchFilter
|
||||||
|
export let sortColumn: string
|
||||||
|
export let sortOrder: SortOrder
|
||||||
|
export let limit: number
|
||||||
|
export let paginate: boolean
|
||||||
|
export let autoRefresh: number
|
||||||
|
|
||||||
let interval
|
const { styleable, Provider, ActionTypes, API } = getContext<SDK>("sdk")
|
||||||
let queryExtensions = {}
|
const component = getContext<Component>("component")
|
||||||
|
|
||||||
|
let interval: ReturnType<typeof setInterval>
|
||||||
|
let queryExtensions: Record<string, any> = {}
|
||||||
|
|
||||||
$: defaultQuery = QueryUtils.buildQuery(filter)
|
$: defaultQuery = QueryUtils.buildQuery(filter)
|
||||||
|
|
||||||
|
@ -49,8 +66,14 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ActionTypes.SetDataProviderSorting,
|
type: ActionTypes.SetDataProviderSorting,
|
||||||
callback: ({ column, order }) => {
|
callback: ({
|
||||||
let newOptions = {}
|
column,
|
||||||
|
order,
|
||||||
|
}: {
|
||||||
|
column: string
|
||||||
|
order: SortOrder | undefined
|
||||||
|
}) => {
|
||||||
|
let newOptions: Partial<DataFetchOptions> = {}
|
||||||
if (column) {
|
if (column) {
|
||||||
newOptions.sortColumn = column
|
newOptions.sortColumn = column
|
||||||
}
|
}
|
||||||
|
@ -63,6 +86,7 @@
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
$: dataContext = {
|
$: dataContext = {
|
||||||
rows: $fetch.rows,
|
rows: $fetch.rows,
|
||||||
info: $fetch.info,
|
info: $fetch.info,
|
||||||
|
@ -75,14 +99,12 @@
|
||||||
id: $component?.id,
|
id: $component?.id,
|
||||||
state: {
|
state: {
|
||||||
query: $fetch.query,
|
query: $fetch.query,
|
||||||
sortColumn: $fetch.sortColumn,
|
|
||||||
sortOrder: $fetch.sortOrder,
|
|
||||||
},
|
},
|
||||||
limit,
|
limit,
|
||||||
primaryDisplay: $fetch.definition?.primaryDisplay,
|
primaryDisplay: ($fetch.definition as any)?.primaryDisplay,
|
||||||
}
|
}
|
||||||
|
|
||||||
const createFetch = datasource => {
|
const createFetch = (datasource: ProviderDatasource) => {
|
||||||
return fetchData({
|
return fetchData({
|
||||||
API,
|
API,
|
||||||
datasource,
|
datasource,
|
||||||
|
@ -96,7 +118,7 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const sanitizeSchema = schema => {
|
const sanitizeSchema = (schema: TableSchema | null) => {
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
|
@ -109,14 +131,14 @@
|
||||||
return cloned
|
return cloned
|
||||||
}
|
}
|
||||||
|
|
||||||
const addQueryExtension = (key, extension) => {
|
const addQueryExtension = (key: string, extension: any) => {
|
||||||
if (!key || !extension) {
|
if (!key || !extension) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
queryExtensions = { ...queryExtensions, [key]: extension }
|
queryExtensions = { ...queryExtensions, [key]: extension }
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeQueryExtension = key => {
|
const removeQueryExtension = (key: string) => {
|
||||||
if (!key) {
|
if (!key) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -125,11 +147,14 @@
|
||||||
queryExtensions = newQueryExtensions
|
queryExtensions = newQueryExtensions
|
||||||
}
|
}
|
||||||
|
|
||||||
const extendQuery = (defaultQuery, extensions) => {
|
const extendQuery = (
|
||||||
|
defaultQuery: SearchFilters,
|
||||||
|
extensions: Record<string, any>
|
||||||
|
): SearchFilters => {
|
||||||
if (!Object.keys(extensions).length) {
|
if (!Object.keys(extensions).length) {
|
||||||
return defaultQuery
|
return defaultQuery
|
||||||
}
|
}
|
||||||
const extended = {
|
const extended: SearchFilters = {
|
||||||
[LogicalOperator.AND]: {
|
[LogicalOperator.AND]: {
|
||||||
conditions: [
|
conditions: [
|
||||||
...(defaultQuery ? [defaultQuery] : []),
|
...(defaultQuery ? [defaultQuery] : []),
|
||||||
|
@ -140,12 +165,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are no conditions applied at all, clear the request.
|
// If there are no conditions applied at all, clear the request.
|
||||||
return extended[LogicalOperator.AND]?.conditions?.length > 0
|
return (extended[LogicalOperator.AND]?.conditions?.length ?? 0) > 0
|
||||||
? extended
|
? extended
|
||||||
: null
|
: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const setUpAutoRefresh = autoRefresh => {
|
const setUpAutoRefresh = (autoRefresh: number) => {
|
||||||
clearInterval(interval)
|
clearInterval(interval)
|
||||||
if (autoRefresh) {
|
if (autoRefresh) {
|
||||||
interval = setInterval(fetch.refresh, Math.max(10000, autoRefresh * 1000))
|
interval = setInterval(fetch.refresh, Math.max(10000, autoRefresh * 1000))
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { APIClient } from "@budibase/frontend-core"
|
||||||
|
import type { ActionTypes } from "./constants"
|
||||||
|
import { Readable } from "svelte/store"
|
||||||
|
|
||||||
|
export interface SDK {
|
||||||
|
API: APIClient
|
||||||
|
styleable: any
|
||||||
|
Provider: any
|
||||||
|
ActionTypes: typeof ActionTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Component = Readable<{
|
||||||
|
id: string
|
||||||
|
styles: any
|
||||||
|
}>
|
|
@ -74,6 +74,7 @@ export default {
|
||||||
fetchData,
|
fetchData,
|
||||||
QueryUtils,
|
QueryUtils,
|
||||||
ContextScopes: Constants.ContextScopes,
|
ContextScopes: Constants.ContextScopes,
|
||||||
|
// This is not used internally but exposed to users to be used in plugins
|
||||||
getAPIKey,
|
getAPIKey,
|
||||||
enrichButtonActions,
|
enrichButtonActions,
|
||||||
processStringSync,
|
processStringSync,
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
import { API } from "../api"
|
import { API } from "../api"
|
||||||
import { UILogicalOperator } from "@budibase/types"
|
import {
|
||||||
|
BasicOperator,
|
||||||
|
LegacyFilter,
|
||||||
|
UIColumn,
|
||||||
|
UILogicalOperator,
|
||||||
|
UISearchFilter,
|
||||||
|
} from "@budibase/types"
|
||||||
import { Constants } from "@budibase/frontend-core"
|
import { Constants } from "@budibase/frontend-core"
|
||||||
|
|
||||||
// Map of data types to component types for search fields inside blocks
|
// Map of data types to component types for search fields inside blocks
|
||||||
const schemaComponentMap = {
|
const schemaComponentMap: Record<string, string> = {
|
||||||
string: "stringfield",
|
string: "stringfield",
|
||||||
options: "optionsfield",
|
options: "optionsfield",
|
||||||
number: "numberfield",
|
number: "numberfield",
|
||||||
|
@ -19,7 +25,16 @@ const schemaComponentMap = {
|
||||||
* @param searchColumns the search columns to use
|
* @param searchColumns the search columns to use
|
||||||
* @param schema the datasource schema
|
* @param schema the datasource schema
|
||||||
*/
|
*/
|
||||||
export const enrichSearchColumns = async (searchColumns, schema) => {
|
export const enrichSearchColumns = async (
|
||||||
|
searchColumns: string[],
|
||||||
|
schema: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
tableId: string
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
>
|
||||||
|
) => {
|
||||||
if (!searchColumns?.length || !schema) {
|
if (!searchColumns?.length || !schema) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
@ -61,12 +76,16 @@ export const enrichSearchColumns = async (searchColumns, schema) => {
|
||||||
* @param columns the enriched search column structure
|
* @param columns the enriched search column structure
|
||||||
* @param formId the ID of the form containing the search fields
|
* @param formId the ID of the form containing the search fields
|
||||||
*/
|
*/
|
||||||
export const enrichFilter = (filter, columns, formId) => {
|
export const enrichFilter = (
|
||||||
|
filter: UISearchFilter,
|
||||||
|
columns: UIColumn[],
|
||||||
|
formId: string
|
||||||
|
) => {
|
||||||
if (!columns?.length) {
|
if (!columns?.length) {
|
||||||
return filter
|
return filter
|
||||||
}
|
}
|
||||||
|
|
||||||
let newFilters = []
|
const newFilters: LegacyFilter[] = []
|
||||||
columns?.forEach(column => {
|
columns?.forEach(column => {
|
||||||
const safePath = column.name.split(".").map(safe).join(".")
|
const safePath = column.name.split(".").map(safe).join(".")
|
||||||
const stringType = column.type === "string" || column.type === "formula"
|
const stringType = column.type === "string" || column.type === "formula"
|
||||||
|
@ -99,7 +118,7 @@ export const enrichFilter = (filter, columns, formId) => {
|
||||||
newFilters.push({
|
newFilters.push({
|
||||||
field: column.name,
|
field: column.name,
|
||||||
type: column.type,
|
type: column.type,
|
||||||
operator: stringType ? "string" : "equal",
|
operator: stringType ? BasicOperator.STRING : BasicOperator.EQUAL,
|
||||||
valueType: "Binding",
|
valueType: "Binding",
|
||||||
value: `{{ ${binding} }}`,
|
value: `{{ ${binding} }}`,
|
||||||
})
|
})
|
|
@ -1,7 +1,27 @@
|
||||||
import { GridSpacing, GridRowHeight } from "constants"
|
import { GridSpacing, GridRowHeight } from "@/constants"
|
||||||
import { builderStore } from "stores"
|
import { builderStore } from "stores"
|
||||||
import { buildStyleString } from "utils/styleable.js"
|
import { buildStyleString } from "utils/styleable.js"
|
||||||
|
|
||||||
|
interface GridMetadata {
|
||||||
|
id: string
|
||||||
|
styles: Record<string, string | number> & {
|
||||||
|
"--default-width"?: number
|
||||||
|
"--default-height"?: number
|
||||||
|
}
|
||||||
|
interactive: boolean
|
||||||
|
errored: boolean
|
||||||
|
definition?: {
|
||||||
|
size?: {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
grid?: { hAlign: string; vAlign: string }
|
||||||
|
}
|
||||||
|
draggable: boolean
|
||||||
|
insideGrid: boolean
|
||||||
|
ignoresLayout: boolean
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We use CSS variables on components to control positioning and layout of
|
* We use CSS variables on components to control positioning and layout of
|
||||||
* components inside grids.
|
* components inside grids.
|
||||||
|
@ -44,14 +64,17 @@ export const GridDragModes = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Builds a CSS variable name for a certain piece of grid metadata
|
// Builds a CSS variable name for a certain piece of grid metadata
|
||||||
export const getGridVar = (device, param) => `--grid-${device}-${param}`
|
export const getGridVar = (device: string, param: string) =>
|
||||||
|
`--grid-${device}-${param}`
|
||||||
|
|
||||||
// Determines whether a JS event originated from immediately within a grid
|
// Determines whether a JS event originated from immediately within a grid
|
||||||
export const isGridEvent = e => {
|
export const isGridEvent = (e: Event & { target: HTMLElement }): boolean => {
|
||||||
return (
|
return (
|
||||||
e.target.dataset?.indicator === "true" ||
|
e.target.dataset?.indicator === "true" ||
|
||||||
|
// @ts-expect-error: api is not properly typed
|
||||||
e.target
|
e.target
|
||||||
.closest?.(".component")
|
.closest?.(".component")
|
||||||
|
// @ts-expect-error
|
||||||
?.parentNode.closest(".component")
|
?.parentNode.closest(".component")
|
||||||
?.childNodes[0]?.classList?.contains("grid")
|
?.childNodes[0]?.classList?.contains("grid")
|
||||||
)
|
)
|
||||||
|
@ -59,11 +82,11 @@ export const isGridEvent = e => {
|
||||||
|
|
||||||
// Svelte action to apply required class names and styles to our component
|
// Svelte action to apply required class names and styles to our component
|
||||||
// wrappers
|
// wrappers
|
||||||
export const gridLayout = (node, metadata) => {
|
export const gridLayout = (node: HTMLDivElement, metadata: GridMetadata) => {
|
||||||
let selectComponent
|
let selectComponent: ((e: Event) => void) | null
|
||||||
|
|
||||||
// Applies the required listeners, CSS and classes to a component DOM node
|
// Applies the required listeners, CSS and classes to a component DOM node
|
||||||
const applyMetadata = metadata => {
|
const applyMetadata = (metadata: GridMetadata) => {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
styles,
|
styles,
|
||||||
|
@ -86,7 +109,7 @@ export const gridLayout = (node, metadata) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback to select the component when clicking on the wrapper
|
// Callback to select the component when clicking on the wrapper
|
||||||
selectComponent = e => {
|
selectComponent = (e: Event) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
builderStore.actions.selectComponent(id)
|
builderStore.actions.selectComponent(id)
|
||||||
}
|
}
|
||||||
|
@ -100,7 +123,7 @@ export const gridLayout = (node, metadata) => {
|
||||||
}
|
}
|
||||||
width += 2 * GridSpacing
|
width += 2 * GridSpacing
|
||||||
height += 2 * GridSpacing
|
height += 2 * GridSpacing
|
||||||
let vars = {
|
const vars: Record<string, string | number> = {
|
||||||
"--default-width": width,
|
"--default-width": width,
|
||||||
"--default-height": height,
|
"--default-height": height,
|
||||||
}
|
}
|
||||||
|
@ -135,7 +158,7 @@ export const gridLayout = (node, metadata) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply some metadata to data attributes to speed up lookups
|
// Apply some metadata to data attributes to speed up lookups
|
||||||
const addDataTag = (tagName, device, param) => {
|
const addDataTag = (tagName: string, device: string, param: string) => {
|
||||||
const val = `${vars[getGridVar(device, param)]}`
|
const val = `${vars[getGridVar(device, param)]}`
|
||||||
if (node.dataset[tagName] !== val) {
|
if (node.dataset[tagName] !== val) {
|
||||||
node.dataset[tagName] = val
|
node.dataset[tagName] = val
|
||||||
|
@ -147,11 +170,12 @@ export const gridLayout = (node, metadata) => {
|
||||||
addDataTag("gridMobileHAlign", Devices.Mobile, GridParams.HAlign)
|
addDataTag("gridMobileHAlign", Devices.Mobile, GridParams.HAlign)
|
||||||
addDataTag("gridDesktopVAlign", Devices.Desktop, GridParams.VAlign)
|
addDataTag("gridDesktopVAlign", Devices.Desktop, GridParams.VAlign)
|
||||||
addDataTag("gridMobileVAlign", Devices.Mobile, GridParams.VAlign)
|
addDataTag("gridMobileVAlign", Devices.Mobile, GridParams.VAlign)
|
||||||
if (node.dataset.insideGrid !== true) {
|
if (node.dataset.insideGrid !== "true") {
|
||||||
node.dataset.insideGrid = true
|
node.dataset.insideGrid = "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply all CSS variables to the wrapper
|
// Apply all CSS variables to the wrapper
|
||||||
|
// @ts-expect-error TODO
|
||||||
node.style = buildStyleString(vars)
|
node.style = buildStyleString(vars)
|
||||||
|
|
||||||
// Add a listener to select this node on click
|
// Add a listener to select this node on click
|
||||||
|
@ -160,7 +184,7 @@ export const gridLayout = (node, metadata) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add draggable attribute
|
// Add draggable attribute
|
||||||
node.setAttribute("draggable", !!draggable)
|
node.setAttribute("draggable", (!!draggable).toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removes the previously set up listeners
|
// Removes the previously set up listeners
|
||||||
|
@ -176,7 +200,7 @@ export const gridLayout = (node, metadata) => {
|
||||||
applyMetadata(metadata)
|
applyMetadata(metadata)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
update(newMetadata) {
|
update(newMetadata: GridMetadata) {
|
||||||
removeListeners()
|
removeListeners()
|
||||||
applyMetadata(newMetadata)
|
applyMetadata(newMetadata)
|
||||||
},
|
},
|
|
@ -1,8 +1,8 @@
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { link } from "svelte-spa-router"
|
import { link, LinkActionOpts } from "svelte-spa-router"
|
||||||
import { builderStore } from "stores"
|
import { builderStore } from "stores"
|
||||||
|
|
||||||
export const linkable = (node, href) => {
|
export const linkable = (node: HTMLElement, href?: LinkActionOpts) => {
|
||||||
if (get(builderStore).inBuilder) {
|
if (get(builderStore).inBuilder) {
|
||||||
node.onclick = e => {
|
node.onclick = e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
|
@ -14,6 +14,7 @@
|
||||||
"../*",
|
"../*",
|
||||||
"../../node_modules/@budibase/*"
|
"../../node_modules/@budibase/*"
|
||||||
],
|
],
|
||||||
|
"@/*": ["./src/*"],
|
||||||
"*": ["./src/*"]
|
"*": ["./src/*"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,10 @@ export default defineConfig(({ mode }) => {
|
||||||
find: "constants",
|
find: "constants",
|
||||||
replacement: path.resolve("./src/constants"),
|
replacement: path.resolve("./src/constants"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
find: "@/constants",
|
||||||
|
replacement: path.resolve("./src/constants"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
find: "sdk",
|
find: "sdk",
|
||||||
replacement: path.resolve("./src/sdk"),
|
replacement: path.resolve("./src/sdk"),
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
// 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.
|
// 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 { derived, get, Readable, Writable } from "svelte/store"
|
||||||
import { getDatasourceDefinition, getDatasourceSchema } from "../../../fetch"
|
import {
|
||||||
|
DataFetchDefinition,
|
||||||
|
getDatasourceDefinition,
|
||||||
|
getDatasourceSchema,
|
||||||
|
} from "../../../fetch"
|
||||||
import { enrichSchemaWithRelColumns, memo } from "../../../utils"
|
import { enrichSchemaWithRelColumns, memo } from "../../../utils"
|
||||||
import { cloneDeep } from "lodash"
|
import { cloneDeep } from "lodash"
|
||||||
import {
|
import {
|
||||||
|
@ -18,7 +22,7 @@ import { Store as StoreContext, BaseStoreProps } from "."
|
||||||
import { DatasourceActions } from "./datasources"
|
import { DatasourceActions } from "./datasources"
|
||||||
|
|
||||||
interface DatasourceStore {
|
interface DatasourceStore {
|
||||||
definition: Writable<UIDatasource | null>
|
definition: Writable<DataFetchDefinition | null>
|
||||||
schemaMutations: Writable<Record<string, UIFieldMutation>>
|
schemaMutations: Writable<Record<string, UIFieldMutation>>
|
||||||
subSchemaMutations: Writable<Record<string, Record<string, UIFieldMutation>>>
|
subSchemaMutations: Writable<Record<string, Record<string, UIFieldMutation>>>
|
||||||
}
|
}
|
||||||
|
@ -131,11 +135,17 @@ export const deriveStores = (context: StoreContext): DerivedDatasourceStore => {
|
||||||
[datasource, definition],
|
[datasource, definition],
|
||||||
([$datasource, $definition]) => {
|
([$datasource, $definition]) => {
|
||||||
let type = $datasource?.type
|
let type = $datasource?.type
|
||||||
|
// @ts-expect-error
|
||||||
if (type === "provider") {
|
if (type === "provider") {
|
||||||
type = ($datasource as any).value?.datasource?.type // TODO: see line 1
|
type = ($datasource as any).value?.datasource?.type // TODO: see line 1
|
||||||
}
|
}
|
||||||
// Handle calculation views
|
// Handle calculation views
|
||||||
if (type === "viewV2" && $definition?.type === ViewV2Type.CALCULATION) {
|
if (
|
||||||
|
type === "viewV2" &&
|
||||||
|
$definition &&
|
||||||
|
"type" in $definition &&
|
||||||
|
$definition.type === ViewV2Type.CALCULATION
|
||||||
|
) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return !!type && ["table", "viewV2", "link"].includes(type)
|
return !!type && ["table", "viewV2", "link"].includes(type)
|
||||||
|
@ -197,7 +207,7 @@ export const createActions = (context: StoreContext): ActionDatasourceStore => {
|
||||||
) => {
|
) => {
|
||||||
// Update local state
|
// Update local state
|
||||||
const originalDefinition = get(definition)
|
const originalDefinition = get(definition)
|
||||||
definition.set(newDefinition as UIDatasource)
|
definition.set(newDefinition)
|
||||||
|
|
||||||
// Update server
|
// Update server
|
||||||
if (get(config).canSaveSchema) {
|
if (get(config).canSaveSchema) {
|
||||||
|
@ -225,13 +235,15 @@ export const createActions = (context: StoreContext): ActionDatasourceStore => {
|
||||||
// Update primary display
|
// Update primary display
|
||||||
newDefinition.primaryDisplay = column
|
newDefinition.primaryDisplay = column
|
||||||
|
|
||||||
// Sanitise schema to ensure field is required and has no default value
|
if (newDefinition.schema) {
|
||||||
if (!newDefinition.schema[column].constraints) {
|
// Sanitise schema to ensure field is required and has no default value
|
||||||
newDefinition.schema[column].constraints = {}
|
if (!newDefinition.schema[column].constraints) {
|
||||||
}
|
newDefinition.schema[column].constraints = {}
|
||||||
newDefinition.schema[column].constraints.presence = { allowEmpty: false }
|
}
|
||||||
if ("default" in newDefinition.schema[column]) {
|
newDefinition.schema[column].constraints.presence = { allowEmpty: false }
|
||||||
delete newDefinition.schema[column].default
|
if ("default" in newDefinition.schema[column]) {
|
||||||
|
delete newDefinition.schema[column].default
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return await saveDefinition(newDefinition as any) // TODO: see line 1
|
return await saveDefinition(newDefinition as any) // TODO: see line 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { Store as StoreContext } from ".."
|
import { Store as StoreContext } from ".."
|
||||||
import { DatasourceTableActions } from "."
|
import { DatasourceTableActions } from "."
|
||||||
|
import TableFetch from "../../../../fetch/TableFetch"
|
||||||
|
|
||||||
const SuppressErrors = true
|
const SuppressErrors = true
|
||||||
|
|
||||||
|
@ -119,7 +120,7 @@ export const initialise = (context: StoreContext) => {
|
||||||
unsubscribers.push(
|
unsubscribers.push(
|
||||||
allFilters.subscribe($allFilters => {
|
allFilters.subscribe($allFilters => {
|
||||||
// Ensure we're updating the correct fetch
|
// Ensure we're updating the correct fetch
|
||||||
const $fetch = get(fetch)
|
const $fetch = get(fetch) as TableFetch | null
|
||||||
if ($fetch?.options?.datasource?.tableId !== $datasource.tableId) {
|
if ($fetch?.options?.datasource?.tableId !== $datasource.tableId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -133,7 +134,7 @@ export const initialise = (context: StoreContext) => {
|
||||||
unsubscribers.push(
|
unsubscribers.push(
|
||||||
sort.subscribe($sort => {
|
sort.subscribe($sort => {
|
||||||
// Ensure we're updating the correct fetch
|
// Ensure we're updating the correct fetch
|
||||||
const $fetch = get(fetch)
|
const $fetch = get(fetch) as TableFetch | null
|
||||||
if ($fetch?.options?.datasource?.tableId !== $datasource.tableId) {
|
if ($fetch?.options?.datasource?.tableId !== $datasource.tableId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,11 @@ import {
|
||||||
SaveRowRequest,
|
SaveRowRequest,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
UIDatasource,
|
UIDatasource,
|
||||||
UIView,
|
|
||||||
UpdateViewRequest,
|
UpdateViewRequest,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { Store as StoreContext } from ".."
|
import { Store as StoreContext } from ".."
|
||||||
import { DatasourceViewActions } from "."
|
import { DatasourceViewActions } from "."
|
||||||
|
import ViewV2Fetch from "../../../../fetch/ViewV2Fetch"
|
||||||
|
|
||||||
const SuppressErrors = true
|
const SuppressErrors = true
|
||||||
|
|
||||||
|
@ -134,6 +134,9 @@ export const initialise = (context: StoreContext) => {
|
||||||
if (!get(config).canSaveSchema) {
|
if (!get(config).canSaveSchema) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (!$definition || !("id" in $definition)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if ($definition?.id !== $datasource.id) {
|
if ($definition?.id !== $datasource.id) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -184,7 +187,10 @@ export const initialise = (context: StoreContext) => {
|
||||||
unsubscribers.push(
|
unsubscribers.push(
|
||||||
sort.subscribe(async $sort => {
|
sort.subscribe(async $sort => {
|
||||||
// Ensure we're updating the correct view
|
// Ensure we're updating the correct view
|
||||||
const $view = get(definition) as UIView
|
const $view = get(definition)
|
||||||
|
if (!$view || !("id" in $view)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if ($view?.id !== $datasource.id) {
|
if ($view?.id !== $datasource.id) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -207,7 +213,7 @@ export const initialise = (context: StoreContext) => {
|
||||||
|
|
||||||
// Also update the fetch to ensure the new sort is respected.
|
// Also update the fetch to ensure the new sort is respected.
|
||||||
// Ensure we're updating the correct fetch.
|
// Ensure we're updating the correct fetch.
|
||||||
const $fetch = get(fetch)
|
const $fetch = get(fetch) as ViewV2Fetch | null
|
||||||
if ($fetch?.options?.datasource?.id !== $datasource.id) {
|
if ($fetch?.options?.datasource?.id !== $datasource.id) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -225,6 +231,9 @@ export const initialise = (context: StoreContext) => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const $view = get(definition)
|
const $view = get(definition)
|
||||||
|
if (!$view || !("id" in $view)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if ($view?.id !== $datasource.id) {
|
if ($view?.id !== $datasource.id) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -246,7 +255,7 @@ export const initialise = (context: StoreContext) => {
|
||||||
if (!get(config).canSaveSchema) {
|
if (!get(config).canSaveSchema) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const $fetch = get(fetch)
|
const $fetch = get(fetch) as ViewV2Fetch | null
|
||||||
if ($fetch?.options?.datasource?.id !== $datasource.id) {
|
if ($fetch?.options?.datasource?.id !== $datasource.id) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -262,7 +271,7 @@ export const initialise = (context: StoreContext) => {
|
||||||
if (get(config).canSaveSchema) {
|
if (get(config).canSaveSchema) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const $fetch = get(fetch)
|
const $fetch = get(fetch) as ViewV2Fetch | null
|
||||||
if ($fetch?.options?.datasource?.id !== $datasource.id) {
|
if ($fetch?.options?.datasource?.id !== $datasource.id) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { writable, derived, get, Writable, Readable } from "svelte/store"
|
import { writable, derived, get, Writable, Readable } from "svelte/store"
|
||||||
import { fetchData } from "../../../fetch"
|
import { DataFetch, fetchData } from "../../../fetch"
|
||||||
import { NewRowID, RowPageSize } from "../lib/constants"
|
import { NewRowID, RowPageSize } from "../lib/constants"
|
||||||
import {
|
import {
|
||||||
generateRowID,
|
generateRowID,
|
||||||
|
@ -13,7 +13,6 @@ import { sleep } from "../../../utils/utils"
|
||||||
import { FieldType, Row, UIRow } from "@budibase/types"
|
import { FieldType, Row, UIRow } from "@budibase/types"
|
||||||
import { getRelatedTableValues } from "../../../utils"
|
import { getRelatedTableValues } from "../../../utils"
|
||||||
import { Store as StoreContext } from "."
|
import { Store as StoreContext } from "."
|
||||||
import DataFetch from "../../../fetch/DataFetch"
|
|
||||||
|
|
||||||
interface IndexedUIRow extends UIRow {
|
interface IndexedUIRow extends UIRow {
|
||||||
__idx: number
|
__idx: number
|
||||||
|
@ -21,7 +20,7 @@ interface IndexedUIRow extends UIRow {
|
||||||
|
|
||||||
interface RowStore {
|
interface RowStore {
|
||||||
rows: Writable<UIRow[]>
|
rows: Writable<UIRow[]>
|
||||||
fetch: Writable<DataFetch<any, any, any> | null> // TODO: type this properly, having a union of all the possible options
|
fetch: Writable<DataFetch | null>
|
||||||
loaded: Writable<boolean>
|
loaded: Writable<boolean>
|
||||||
refreshing: Writable<boolean>
|
refreshing: Writable<boolean>
|
||||||
loading: Writable<boolean>
|
loading: Writable<boolean>
|
||||||
|
@ -254,7 +253,7 @@ export const createActions = (context: StoreContext): RowActionStore => {
|
||||||
|
|
||||||
// Reset state properties when dataset changes
|
// Reset state properties when dataset changes
|
||||||
if (!$instanceLoaded || resetRows) {
|
if (!$instanceLoaded || resetRows) {
|
||||||
definition.set($fetch.definition as any) // 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.
|
definition.set($fetch.definition ?? null)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset scroll state when data changes
|
// Reset scroll state when data changes
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
import DataFetch from "./DataFetch"
|
import { CustomDatasource } from "@budibase/types"
|
||||||
|
import BaseDataFetch from "./DataFetch"
|
||||||
interface CustomDatasource {
|
|
||||||
type: "custom"
|
|
||||||
data: any
|
|
||||||
}
|
|
||||||
|
|
||||||
type CustomDefinition = Record<string, any>
|
type CustomDefinition = Record<string, any>
|
||||||
|
|
||||||
export default class CustomFetch extends DataFetch<
|
export default class CustomFetch extends BaseDataFetch<
|
||||||
CustomDatasource,
|
CustomDatasource,
|
||||||
CustomDefinition
|
CustomDefinition
|
||||||
> {
|
> {
|
||||||
|
|
|
@ -3,14 +3,13 @@ import { cloneDeep } from "lodash/fp"
|
||||||
import { QueryUtils } from "../utils"
|
import { QueryUtils } from "../utils"
|
||||||
import { convertJSONSchemaToTableSchema } from "../utils/json"
|
import { convertJSONSchemaToTableSchema } from "../utils/json"
|
||||||
import {
|
import {
|
||||||
|
DataFetchOptions,
|
||||||
FieldType,
|
FieldType,
|
||||||
LegacyFilter,
|
|
||||||
Row,
|
Row,
|
||||||
SearchFilters,
|
SearchFilters,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
SortType,
|
SortType,
|
||||||
TableSchema,
|
TableSchema,
|
||||||
UISearchFilter,
|
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { APIClient } from "../api/types"
|
import { APIClient } from "../api/types"
|
||||||
import { DataFetchType } from "."
|
import { DataFetchType } from "."
|
||||||
|
@ -44,14 +43,11 @@ interface DataFetchDerivedStore<TDefinition, TQuery>
|
||||||
supportsPagination: boolean
|
supportsPagination: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataFetchParams<
|
export interface DataFetchParams<TDatasource, TQuery = SearchFilters> {
|
||||||
TDatasource,
|
|
||||||
TQuery = SearchFilters | undefined
|
|
||||||
> {
|
|
||||||
API: APIClient
|
API: APIClient
|
||||||
datasource: TDatasource
|
datasource: TDatasource
|
||||||
query: TQuery
|
query: TQuery
|
||||||
options?: {}
|
options?: Partial<DataFetchOptions<TQuery>>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,7 +55,7 @@ export interface DataFetchParams<
|
||||||
* internal table or datasource plus.
|
* internal table or datasource plus.
|
||||||
* For other types of datasource, this class is overridden and extended.
|
* For other types of datasource, this class is overridden and extended.
|
||||||
*/
|
*/
|
||||||
export default abstract class DataFetch<
|
export default abstract class BaseDataFetch<
|
||||||
TDatasource extends { type: DataFetchType },
|
TDatasource extends { type: DataFetchType },
|
||||||
TDefinition extends {
|
TDefinition extends {
|
||||||
schema?: Record<string, any> | null
|
schema?: Record<string, any> | null
|
||||||
|
@ -73,18 +69,11 @@ export default abstract class DataFetch<
|
||||||
supportsSort: boolean
|
supportsSort: boolean
|
||||||
supportsPagination: boolean
|
supportsPagination: boolean
|
||||||
}
|
}
|
||||||
options: {
|
options: DataFetchOptions<TQuery> & {
|
||||||
datasource: TDatasource
|
datasource: TDatasource
|
||||||
limit: number
|
|
||||||
// Search config
|
|
||||||
filter: UISearchFilter | LegacyFilter[] | null
|
|
||||||
query: TQuery
|
|
||||||
// Sorting config
|
|
||||||
sortColumn: string | null
|
|
||||||
sortOrder: SortOrder
|
|
||||||
sortType: SortType | null
|
sortType: SortType | null
|
||||||
// Pagination config
|
|
||||||
paginate: boolean
|
|
||||||
// Client side feature customisation
|
// Client side feature customisation
|
||||||
clientSideSearching: boolean
|
clientSideSearching: boolean
|
||||||
clientSideSorting: boolean
|
clientSideSorting: boolean
|
||||||
|
@ -267,6 +256,7 @@ export default abstract class DataFetch<
|
||||||
|
|
||||||
// Build the query
|
// Build the query
|
||||||
let query = this.options.query
|
let query = this.options.query
|
||||||
|
|
||||||
if (!query) {
|
if (!query) {
|
||||||
query = buildQuery(filter ?? undefined) as TQuery
|
query = buildQuery(filter ?? undefined) as TQuery
|
||||||
}
|
}
|
||||||
|
@ -430,7 +420,7 @@ export default abstract class DataFetch<
|
||||||
* Resets the data set and updates options
|
* Resets the data set and updates options
|
||||||
* @param newOptions any new options
|
* @param newOptions any new options
|
||||||
*/
|
*/
|
||||||
async update(newOptions: any) {
|
async update(newOptions: Partial<DataFetchOptions<TQuery>>) {
|
||||||
// Check if any settings have actually changed
|
// Check if any settings have actually changed
|
||||||
let refresh = false
|
let refresh = false
|
||||||
for (const [key, value] of Object.entries(newOptions || {})) {
|
for (const [key, value] of Object.entries(newOptions || {})) {
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
import { Row } from "@budibase/types"
|
import {
|
||||||
import DataFetch from "./DataFetch"
|
FieldDatasource,
|
||||||
|
JSONArrayFieldDatasource,
|
||||||
type Types = "field" | "queryarray" | "jsonarray"
|
QueryArrayFieldDatasource,
|
||||||
|
Row,
|
||||||
export interface FieldDatasource<TType extends Types> {
|
} from "@budibase/types"
|
||||||
type: TType
|
import BaseDataFetch from "./DataFetch"
|
||||||
tableId: string
|
|
||||||
fieldType: "attachment" | "array"
|
|
||||||
value: string[] | Row[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FieldDefinition {
|
export interface FieldDefinition {
|
||||||
schema?: Record<string, { type: string }> | null
|
schema?: Record<string, { type: string }> | null
|
||||||
|
@ -18,10 +14,12 @@ function isArrayOfStrings(value: string[] | Row[]): value is string[] {
|
||||||
return Array.isArray(value) && !!value[0] && typeof value[0] !== "object"
|
return Array.isArray(value) && !!value[0] && typeof value[0] !== "object"
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class FieldFetch<TType extends Types> extends DataFetch<
|
export default class FieldFetch<
|
||||||
FieldDatasource<TType>,
|
TDatasource extends
|
||||||
FieldDefinition
|
| FieldDatasource
|
||||||
> {
|
| QueryArrayFieldDatasource
|
||||||
|
| JSONArrayFieldDatasource = FieldDatasource
|
||||||
|
> extends BaseDataFetch<TDatasource, FieldDefinition> {
|
||||||
async getDefinition(): Promise<FieldDefinition | null> {
|
async getDefinition(): Promise<FieldDefinition | null> {
|
||||||
const { datasource } = this.options
|
const { datasource } = this.options
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import DataFetch, { DataFetchParams } from "./DataFetch"
|
import BaseDataFetch, { DataFetchParams } from "./DataFetch"
|
||||||
import { TableNames } from "../constants"
|
import { GroupUserDatasource, InternalTable } from "@budibase/types"
|
||||||
|
|
||||||
interface GroupUserQuery {
|
interface GroupUserQuery {
|
||||||
groupId: string
|
groupId: string
|
||||||
emailSearch: string
|
emailSearch: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GroupUserDatasource {
|
interface GroupUserDefinition {
|
||||||
type: "groupUser"
|
schema?: Record<string, any> | null
|
||||||
tableId: TableNames.USERS
|
primaryDisplay?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class GroupUserFetch extends DataFetch<
|
export default class GroupUserFetch extends BaseDataFetch<
|
||||||
GroupUserDatasource,
|
GroupUserDatasource,
|
||||||
{},
|
GroupUserDefinition,
|
||||||
GroupUserQuery
|
GroupUserQuery
|
||||||
> {
|
> {
|
||||||
constructor(opts: DataFetchParams<GroupUserDatasource, GroupUserQuery>) {
|
constructor(opts: DataFetchParams<GroupUserDatasource, GroupUserQuery>) {
|
||||||
|
@ -22,7 +22,7 @@ export default class GroupUserFetch extends DataFetch<
|
||||||
...opts,
|
...opts,
|
||||||
datasource: {
|
datasource: {
|
||||||
type: "groupUser",
|
type: "groupUser",
|
||||||
tableId: TableNames.USERS,
|
tableId: InternalTable.USER_METADATA,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import FieldFetch from "./FieldFetch"
|
import FieldFetch from "./FieldFetch"
|
||||||
import { getJSONArrayDatasourceSchema } from "../utils/json"
|
import { getJSONArrayDatasourceSchema } from "../utils/json"
|
||||||
|
import { JSONArrayFieldDatasource } from "@budibase/types"
|
||||||
|
|
||||||
export default class JSONArrayFetch extends FieldFetch<"jsonarray"> {
|
export default class JSONArrayFetch extends FieldFetch<JSONArrayFieldDatasource> {
|
||||||
async getDefinition() {
|
async getDefinition() {
|
||||||
const { datasource } = this.options
|
const { datasource } = this.options
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,11 @@
|
||||||
import { Row, TableSchema } from "@budibase/types"
|
import { NestedProviderDatasource, TableSchema } from "@budibase/types"
|
||||||
import DataFetch from "./DataFetch"
|
import BaseDataFetch from "./DataFetch"
|
||||||
|
|
||||||
interface NestedProviderDatasource {
|
|
||||||
type: "provider"
|
|
||||||
value?: {
|
|
||||||
schema: TableSchema
|
|
||||||
primaryDisplay: string
|
|
||||||
rows: Row[]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NestedProviderDefinition {
|
interface NestedProviderDefinition {
|
||||||
schema?: TableSchema
|
schema?: TableSchema
|
||||||
primaryDisplay?: string
|
primaryDisplay?: string
|
||||||
}
|
}
|
||||||
export default class NestedProviderFetch extends DataFetch<
|
export default class NestedProviderFetch extends BaseDataFetch<
|
||||||
NestedProviderDatasource,
|
NestedProviderDatasource,
|
||||||
NestedProviderDefinition
|
NestedProviderDefinition
|
||||||
> {
|
> {
|
||||||
|
|
|
@ -3,8 +3,9 @@ import {
|
||||||
getJSONArrayDatasourceSchema,
|
getJSONArrayDatasourceSchema,
|
||||||
generateQueryArraySchemas,
|
generateQueryArraySchemas,
|
||||||
} from "../utils/json"
|
} from "../utils/json"
|
||||||
|
import { QueryArrayFieldDatasource } from "@budibase/types"
|
||||||
|
|
||||||
export default class QueryArrayFetch extends FieldFetch<"queryarray"> {
|
export default class QueryArrayFetch extends FieldFetch<QueryArrayFieldDatasource> {
|
||||||
async getDefinition() {
|
async getDefinition() {
|
||||||
const { datasource } = this.options
|
const { datasource } = this.options
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,9 @@
|
||||||
import DataFetch from "./DataFetch"
|
import BaseDataFetch from "./DataFetch"
|
||||||
import { Helpers } from "@budibase/bbui"
|
import { Helpers } from "@budibase/bbui"
|
||||||
import { ExecuteQueryRequest, Query } from "@budibase/types"
|
import { ExecuteQueryRequest, Query, QueryDatasource } from "@budibase/types"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
|
|
||||||
interface QueryDatasource {
|
export default class QueryFetch extends BaseDataFetch<QueryDatasource, Query> {
|
||||||
type: "query"
|
|
||||||
_id: string
|
|
||||||
fields: Record<string, any> & {
|
|
||||||
pagination?: {
|
|
||||||
type: string
|
|
||||||
location: string
|
|
||||||
pageParam: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
queryParams?: Record<string, string>
|
|
||||||
parameters: { name: string; default: string }[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class QueryFetch extends DataFetch<QueryDatasource, Query> {
|
|
||||||
async determineFeatureFlags() {
|
async determineFeatureFlags() {
|
||||||
const definition = await this.getDefinition()
|
const definition = await this.getDefinition()
|
||||||
const supportsPagination =
|
const supportsPagination =
|
||||||
|
|
|
@ -1,15 +1,7 @@
|
||||||
import { Table } from "@budibase/types"
|
import { RelationshipDatasource, Table } from "@budibase/types"
|
||||||
import DataFetch from "./DataFetch"
|
import BaseDataFetch from "./DataFetch"
|
||||||
|
|
||||||
interface RelationshipDatasource {
|
export default class RelationshipFetch extends BaseDataFetch<
|
||||||
type: "link"
|
|
||||||
tableId: string
|
|
||||||
rowId: string
|
|
||||||
rowTableId: string
|
|
||||||
fieldName: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class RelationshipFetch extends DataFetch<
|
|
||||||
RelationshipDatasource,
|
RelationshipDatasource,
|
||||||
Table
|
Table
|
||||||
> {
|
> {
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import DataFetch from "./DataFetch"
|
import BaseDataFetch from "./DataFetch"
|
||||||
import { SortOrder, Table } from "@budibase/types"
|
import { SortOrder, Table, TableDatasource } from "@budibase/types"
|
||||||
|
|
||||||
interface TableDatasource {
|
export default class TableFetch extends BaseDataFetch<TableDatasource, Table> {
|
||||||
type: "table"
|
|
||||||
tableId: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class TableFetch extends DataFetch<TableDatasource, Table> {
|
|
||||||
async determineFeatureFlags() {
|
async determineFeatureFlags() {
|
||||||
return {
|
return {
|
||||||
supportsSearch: true,
|
supportsSearch: true,
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import DataFetch, { DataFetchParams } from "./DataFetch"
|
import BaseDataFetch, { DataFetchParams } from "./DataFetch"
|
||||||
import { TableNames } from "../constants"
|
|
||||||
import { utils } from "@budibase/shared-core"
|
import { utils } from "@budibase/shared-core"
|
||||||
import { SearchFilters, SearchUsersRequest } from "@budibase/types"
|
import {
|
||||||
|
InternalTable,
|
||||||
|
SearchFilters,
|
||||||
|
SearchUsersRequest,
|
||||||
|
UserDatasource,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
interface UserFetchQuery {
|
interface UserFetchQuery {
|
||||||
appId: string
|
appId: string
|
||||||
paginated: boolean
|
paginated: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserDatasource {
|
interface UserDefinition {
|
||||||
type: "user"
|
schema?: Record<string, any> | null
|
||||||
tableId: TableNames.USERS
|
primaryDisplay?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserDefinition {}
|
export default class UserFetch extends BaseDataFetch<
|
||||||
|
|
||||||
export default class UserFetch extends DataFetch<
|
|
||||||
UserDatasource,
|
UserDatasource,
|
||||||
UserDefinition,
|
UserDefinition,
|
||||||
UserFetchQuery
|
UserFetchQuery
|
||||||
|
@ -26,7 +28,7 @@ export default class UserFetch extends DataFetch<
|
||||||
...opts,
|
...opts,
|
||||||
datasource: {
|
datasource: {
|
||||||
type: "user",
|
type: "user",
|
||||||
tableId: TableNames.USERS,
|
tableId: InternalTable.USER_METADATA,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,7 @@
|
||||||
import { Table } from "@budibase/types"
|
import { Table, ViewV1Datasource } from "@budibase/types"
|
||||||
import DataFetch from "./DataFetch"
|
import BaseDataFetch from "./DataFetch"
|
||||||
|
|
||||||
type ViewV1Datasource = {
|
export default class ViewFetch extends BaseDataFetch<ViewV1Datasource, Table> {
|
||||||
type: "view"
|
|
||||||
name: string
|
|
||||||
tableId: string
|
|
||||||
calculation: string
|
|
||||||
field: string
|
|
||||||
groupBy: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class ViewFetch extends DataFetch<ViewV1Datasource, Table> {
|
|
||||||
async getDefinition() {
|
async getDefinition() {
|
||||||
const { datasource } = this.options
|
const { datasource } = this.options
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { SortOrder, ViewV2Enriched, ViewV2Type } from "@budibase/types"
|
import {
|
||||||
import DataFetch from "./DataFetch"
|
SortOrder,
|
||||||
|
ViewDatasource,
|
||||||
|
ViewV2Enriched,
|
||||||
|
ViewV2Type,
|
||||||
|
} from "@budibase/types"
|
||||||
|
import BaseDataFetch from "./DataFetch"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { helpers } from "@budibase/shared-core"
|
import { helpers } from "@budibase/shared-core"
|
||||||
|
|
||||||
interface ViewDatasource {
|
export default class ViewV2Fetch extends BaseDataFetch<
|
||||||
type: "viewV2"
|
|
||||||
id: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class ViewV2Fetch extends DataFetch<
|
|
||||||
ViewDatasource,
|
ViewDatasource,
|
||||||
ViewV2Enriched
|
ViewV2Enriched
|
||||||
> {
|
> {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import GroupUserFetch from "./GroupUserFetch"
|
||||||
import CustomFetch from "./CustomFetch"
|
import CustomFetch from "./CustomFetch"
|
||||||
import QueryArrayFetch from "./QueryArrayFetch"
|
import QueryArrayFetch from "./QueryArrayFetch"
|
||||||
import { APIClient } from "../api/types"
|
import { APIClient } from "../api/types"
|
||||||
|
import { DataFetchDatasource, Table, ViewV2Enriched } from "@budibase/types"
|
||||||
|
|
||||||
export type DataFetchType = keyof typeof DataFetchMap
|
export type DataFetchType = keyof typeof DataFetchMap
|
||||||
|
|
||||||
|
@ -26,32 +27,88 @@ export const DataFetchMap = {
|
||||||
|
|
||||||
// Client specific datasource types
|
// Client specific datasource types
|
||||||
provider: NestedProviderFetch,
|
provider: NestedProviderFetch,
|
||||||
field: FieldFetch<"field">,
|
field: FieldFetch,
|
||||||
jsonarray: JSONArrayFetch,
|
jsonarray: JSONArrayFetch,
|
||||||
queryarray: QueryArrayFetch,
|
queryarray: QueryArrayFetch,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DataFetchClassMap {
|
||||||
|
table: TableFetch
|
||||||
|
view: ViewFetch
|
||||||
|
viewV2: ViewV2Fetch
|
||||||
|
query: QueryFetch
|
||||||
|
link: RelationshipFetch
|
||||||
|
user: UserFetch
|
||||||
|
groupUser: GroupUserFetch
|
||||||
|
custom: CustomFetch
|
||||||
|
|
||||||
|
// Client specific datasource types
|
||||||
|
provider: NestedProviderFetch
|
||||||
|
field: FieldFetch
|
||||||
|
jsonarray: JSONArrayFetch
|
||||||
|
queryarray: QueryArrayFetch
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DataFetch =
|
||||||
|
| TableFetch
|
||||||
|
| ViewFetch
|
||||||
|
| ViewV2Fetch
|
||||||
|
| QueryFetch
|
||||||
|
| RelationshipFetch
|
||||||
|
| UserFetch
|
||||||
|
| GroupUserFetch
|
||||||
|
| CustomFetch
|
||||||
|
| NestedProviderFetch
|
||||||
|
| FieldFetch
|
||||||
|
| JSONArrayFetch
|
||||||
|
| QueryArrayFetch
|
||||||
|
|
||||||
|
export type DataFetchDefinition =
|
||||||
|
| Table
|
||||||
|
| ViewV2Enriched
|
||||||
|
| {
|
||||||
|
// These fields are added to allow checking these fields on definition usages without requiring constant castings
|
||||||
|
schema?: Record<string, any> | null
|
||||||
|
primaryDisplay?: string
|
||||||
|
rowHeight?: number
|
||||||
|
type?: string
|
||||||
|
name?: string
|
||||||
|
}
|
||||||
|
|
||||||
// Constructs a new fetch model for a certain datasource
|
// Constructs a new fetch model for a certain datasource
|
||||||
export const fetchData = ({ API, datasource, options }: any) => {
|
export const fetchData = <
|
||||||
const Fetch = DataFetchMap[datasource?.type as DataFetchType] || TableFetch
|
T extends DataFetchDatasource,
|
||||||
|
Type extends T["type"] = T["type"]
|
||||||
|
>({
|
||||||
|
API,
|
||||||
|
datasource,
|
||||||
|
options,
|
||||||
|
}: {
|
||||||
|
API: APIClient
|
||||||
|
datasource: T
|
||||||
|
options: any
|
||||||
|
}): Type extends keyof DataFetchClassMap
|
||||||
|
? DataFetchClassMap[Type]
|
||||||
|
: TableFetch => {
|
||||||
|
const Fetch = DataFetchMap[datasource?.type] || TableFetch
|
||||||
const fetch = new Fetch({ API, datasource, ...options })
|
const fetch = new Fetch({ API, datasource, ...options })
|
||||||
|
|
||||||
// Initially fetch data but don't bother waiting for the result
|
// Initially fetch data but don't bother waiting for the result
|
||||||
fetch.getInitialData()
|
fetch.getInitialData()
|
||||||
|
|
||||||
return fetch
|
return fetch as any
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates an empty fetch instance with no datasource configured, so no data
|
// Creates an empty fetch instance with no datasource configured, so no data
|
||||||
// will initially be loaded
|
// will initially be loaded
|
||||||
const createEmptyFetchInstance = <TDatasource extends { type: DataFetchType }>({
|
const createEmptyFetchInstance = ({
|
||||||
API,
|
API,
|
||||||
datasource,
|
datasource,
|
||||||
}: {
|
}: {
|
||||||
API: APIClient
|
API: APIClient
|
||||||
datasource: TDatasource
|
datasource: DataFetchDatasource
|
||||||
}) => {
|
}) => {
|
||||||
const handler = DataFetchMap[datasource?.type as DataFetchType]
|
const handler = DataFetchMap[datasource?.type]
|
||||||
if (!handler) {
|
if (!handler) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -63,29 +120,25 @@ const createEmptyFetchInstance = <TDatasource extends { type: DataFetchType }>({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetches the definition of any type of datasource
|
// Fetches the definition of any type of datasource
|
||||||
export const getDatasourceDefinition = async <
|
export const getDatasourceDefinition = async ({
|
||||||
TDatasource extends { type: DataFetchType }
|
|
||||||
>({
|
|
||||||
API,
|
API,
|
||||||
datasource,
|
datasource,
|
||||||
}: {
|
}: {
|
||||||
API: APIClient
|
API: APIClient
|
||||||
datasource: TDatasource
|
datasource: DataFetchDatasource
|
||||||
}) => {
|
}) => {
|
||||||
const instance = createEmptyFetchInstance({ API, datasource })
|
const instance = createEmptyFetchInstance({ API, datasource })
|
||||||
return await instance?.getDefinition()
|
return await instance?.getDefinition()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetches the schema of any type of datasource
|
// Fetches the schema of any type of datasource
|
||||||
export const getDatasourceSchema = <
|
export const getDatasourceSchema = ({
|
||||||
TDatasource extends { type: DataFetchType }
|
|
||||||
>({
|
|
||||||
API,
|
API,
|
||||||
datasource,
|
datasource,
|
||||||
definition,
|
definition,
|
||||||
}: {
|
}: {
|
||||||
API: APIClient
|
API: APIClient
|
||||||
datasource: TDatasource
|
datasource: DataFetchDatasource
|
||||||
definition?: any
|
definition?: any
|
||||||
}) => {
|
}) => {
|
||||||
const instance = createEmptyFetchInstance({ API, datasource })
|
const instance = createEmptyFetchInstance({ API, datasource })
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
export { createAPIClient } from "./api"
|
export { createAPIClient } from "./api"
|
||||||
export type { APIClient } from "./api"
|
export type { APIClient } from "./api"
|
||||||
export { fetchData, DataFetchMap } from "./fetch"
|
export { fetchData, DataFetchMap } from "./fetch"
|
||||||
export type { DataFetchType } from "./fetch"
|
|
||||||
export * as Constants from "./constants"
|
export * as Constants from "./constants"
|
||||||
export * from "./stores"
|
export * from "./stores"
|
||||||
export * from "./utils"
|
export * from "./utils"
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 193476cdfade6d3c613e6972f16ee0c527e01ff6
|
Subproject commit 43a5785ccb4f83ce929b29f05ea0a62199fcdf23
|
|
@ -2043,6 +2043,101 @@ if (descriptions.length) {
|
||||||
expect(rows[0].name).toEqual("Clare updated")
|
expect(rows[0].name).toEqual("Clare updated")
|
||||||
expect(rows[1].name).toEqual("Jeff updated")
|
expect(rows[1].name).toEqual("Jeff updated")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should reject bulkImport date only fields with wrong format", async () => {
|
||||||
|
const table = await config.api.table.save(
|
||||||
|
saveTableRequest({
|
||||||
|
schema: {
|
||||||
|
date: {
|
||||||
|
type: FieldType.DATETIME,
|
||||||
|
dateOnly: true,
|
||||||
|
name: "date",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
await config.api.row.bulkImport(
|
||||||
|
table._id!,
|
||||||
|
{
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
date: "01.02.2024",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
message:
|
||||||
|
'Invalid format for field "date": "01.02.2024". Date-only fields must be in the format "YYYY-MM-DD".',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should reject bulkImport date time fields with wrong format", async () => {
|
||||||
|
const table = await config.api.table.save(
|
||||||
|
saveTableRequest({
|
||||||
|
schema: {
|
||||||
|
date: {
|
||||||
|
type: FieldType.DATETIME,
|
||||||
|
name: "date",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
await config.api.row.bulkImport(
|
||||||
|
table._id!,
|
||||||
|
{
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
date: "01.02.2024",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
message:
|
||||||
|
'Invalid format for field "date": "01.02.2024". Datetime fields must be in ISO format, e.g. "YYYY-MM-DDTHH:MM:SSZ".',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should reject bulkImport time fields with wrong format", async () => {
|
||||||
|
const table = await config.api.table.save(
|
||||||
|
saveTableRequest({
|
||||||
|
schema: {
|
||||||
|
time: {
|
||||||
|
type: FieldType.DATETIME,
|
||||||
|
timeOnly: true,
|
||||||
|
name: "time",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
await config.api.row.bulkImport(
|
||||||
|
table._id!,
|
||||||
|
{
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
time: "3pm",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
message:
|
||||||
|
'Invalid format for field "time": "3pm". Time-only fields must be in the format "HH:MM:SS".',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("enrich", () => {
|
describe("enrich", () => {
|
||||||
|
|
|
@ -1705,7 +1705,10 @@ if (descriptions.length) {
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
tableOrViewId = await createTableOrView({
|
tableOrViewId = await createTableOrView({
|
||||||
dateid: { name: "dateid", type: FieldType.STRING },
|
dateid: {
|
||||||
|
name: "dateid",
|
||||||
|
type: FieldType.STRING,
|
||||||
|
},
|
||||||
date: {
|
date: {
|
||||||
name: "date",
|
name: "date",
|
||||||
type: FieldType.DATETIME,
|
type: FieldType.DATETIME,
|
||||||
|
@ -1751,7 +1754,9 @@ if (descriptions.length) {
|
||||||
describe("notEqual", () => {
|
describe("notEqual", () => {
|
||||||
it("successfully finds a row", async () => {
|
it("successfully finds a row", async () => {
|
||||||
await expectQuery({
|
await expectQuery({
|
||||||
notEqual: { date: `${JAN_1ST}${SEARCH_SUFFIX}` },
|
notEqual: {
|
||||||
|
date: `${JAN_1ST}${SEARCH_SUFFIX}`,
|
||||||
|
},
|
||||||
}).toContainExactly([
|
}).toContainExactly([
|
||||||
{ date: JAN_10TH },
|
{ date: JAN_10TH },
|
||||||
{ dateid: NULL_DATE__ID },
|
{ dateid: NULL_DATE__ID },
|
||||||
|
@ -1760,7 +1765,9 @@ if (descriptions.length) {
|
||||||
|
|
||||||
it("fails to find nonexistent row", async () => {
|
it("fails to find nonexistent row", async () => {
|
||||||
await expectQuery({
|
await expectQuery({
|
||||||
notEqual: { date: `${JAN_30TH}${SEARCH_SUFFIX}` },
|
notEqual: {
|
||||||
|
date: `${JAN_30TH}${SEARCH_SUFFIX}`,
|
||||||
|
},
|
||||||
}).toContainExactly([
|
}).toContainExactly([
|
||||||
{ date: JAN_1ST },
|
{ date: JAN_1ST },
|
||||||
{ date: JAN_10TH },
|
{ date: JAN_10TH },
|
||||||
|
@ -1822,6 +1829,60 @@ if (descriptions.length) {
|
||||||
}).toFindNothing()
|
}).toFindNothing()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("sort", () => {
|
||||||
|
it("sorts ascending", async () => {
|
||||||
|
await expectSearch({
|
||||||
|
query: {},
|
||||||
|
sort: "date",
|
||||||
|
sortOrder: SortOrder.ASCENDING,
|
||||||
|
}).toMatchExactly([
|
||||||
|
{ dateid: NULL_DATE__ID },
|
||||||
|
{ date: JAN_1ST },
|
||||||
|
{ date: JAN_10TH },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("sorts descending", async () => {
|
||||||
|
await expectSearch({
|
||||||
|
query: {},
|
||||||
|
sort: "date",
|
||||||
|
sortOrder: SortOrder.DESCENDING,
|
||||||
|
}).toMatchExactly([
|
||||||
|
{ date: JAN_10TH },
|
||||||
|
{ date: JAN_1ST },
|
||||||
|
{ dateid: NULL_DATE__ID },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("sortType STRING", () => {
|
||||||
|
it("sorts ascending", async () => {
|
||||||
|
await expectSearch({
|
||||||
|
query: {},
|
||||||
|
sort: "date",
|
||||||
|
sortType: SortType.STRING,
|
||||||
|
sortOrder: SortOrder.ASCENDING,
|
||||||
|
}).toMatchExactly([
|
||||||
|
{ dateid: NULL_DATE__ID },
|
||||||
|
{ date: JAN_1ST },
|
||||||
|
{ date: JAN_10TH },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("sorts descending", async () => {
|
||||||
|
await expectSearch({
|
||||||
|
query: {},
|
||||||
|
sort: "date",
|
||||||
|
sortType: SortType.STRING,
|
||||||
|
sortOrder: SortOrder.DESCENDING,
|
||||||
|
}).toMatchExactly([
|
||||||
|
{ date: JAN_10TH },
|
||||||
|
{ date: JAN_1ST },
|
||||||
|
{ dateid: NULL_DATE__ID },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
Table,
|
Table,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { ValidColumnNameRegex, helpers, utils } from "@budibase/shared-core"
|
import { ValidColumnNameRegex, helpers, utils } from "@budibase/shared-core"
|
||||||
import { db } from "@budibase/backend-core"
|
import { db, HTTPError, sql } from "@budibase/backend-core"
|
||||||
|
|
||||||
type Rows = Array<Row>
|
type Rows = Array<Row>
|
||||||
|
|
||||||
|
@ -175,15 +175,27 @@ export function parse(rows: Rows, table: Table): Rows {
|
||||||
if ([FieldType.NUMBER].includes(columnType)) {
|
if ([FieldType.NUMBER].includes(columnType)) {
|
||||||
// If provided must be a valid number
|
// If provided must be a valid number
|
||||||
parsedRow[columnName] = columnData ? Number(columnData) : columnData
|
parsedRow[columnName] = columnData ? Number(columnData) : columnData
|
||||||
} else if (
|
} else if (columnType === FieldType.DATETIME) {
|
||||||
columnType === FieldType.DATETIME &&
|
if (columnData && !columnSchema.timeOnly) {
|
||||||
!columnSchema.timeOnly &&
|
if (!sql.utils.isValidISODateString(columnData)) {
|
||||||
!columnSchema.dateOnly
|
let message = `Invalid format for field "${columnName}": "${columnData}".`
|
||||||
) {
|
if (columnSchema.dateOnly) {
|
||||||
// If provided must be a valid date
|
message += ` Date-only fields must be in the format "YYYY-MM-DD".`
|
||||||
|
} else {
|
||||||
|
message += ` Datetime fields must be in ISO format, e.g. "YYYY-MM-DDTHH:MM:SSZ".`
|
||||||
|
}
|
||||||
|
throw new HTTPError(message, 400)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (columnData && columnSchema.timeOnly) {
|
||||||
|
if (!sql.utils.isValidTime(columnData)) {
|
||||||
|
throw new HTTPError(
|
||||||
|
`Invalid format for field "${columnName}": "${columnData}". Time-only fields must be in the format "HH:MM:SS".`,
|
||||||
|
400
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
parsedRow[columnName] = columnData
|
parsedRow[columnName] = columnData
|
||||||
? new Date(columnData).toISOString()
|
|
||||||
: columnData
|
|
||||||
} else if (
|
} else if (
|
||||||
columnType === FieldType.JSON &&
|
columnType === FieldType.JSON &&
|
||||||
typeof columnData === "string"
|
typeof columnData === "string"
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { InternalTable, Row, TableSchema } from "../../documents"
|
||||||
|
|
||||||
|
export type DataFetchDatasource =
|
||||||
|
| TableDatasource
|
||||||
|
| ViewV1Datasource
|
||||||
|
| ViewDatasource
|
||||||
|
| QueryDatasource
|
||||||
|
| RelationshipDatasource
|
||||||
|
| UserDatasource
|
||||||
|
| GroupUserDatasource
|
||||||
|
| CustomDatasource
|
||||||
|
| NestedProviderDatasource
|
||||||
|
| FieldDatasource
|
||||||
|
| QueryArrayFieldDatasource
|
||||||
|
| JSONArrayFieldDatasource
|
||||||
|
|
||||||
|
export interface TableDatasource {
|
||||||
|
type: "table"
|
||||||
|
tableId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ViewV1Datasource = {
|
||||||
|
type: "view"
|
||||||
|
name: string
|
||||||
|
tableId: string
|
||||||
|
calculation: string
|
||||||
|
field: string
|
||||||
|
groupBy: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ViewDatasource {
|
||||||
|
type: "viewV2"
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QueryDatasource {
|
||||||
|
type: "query"
|
||||||
|
_id: string
|
||||||
|
fields: Record<string, any> & {
|
||||||
|
pagination?: {
|
||||||
|
type: string
|
||||||
|
location: string
|
||||||
|
pageParam: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queryParams?: Record<string, string>
|
||||||
|
parameters: { name: string; default: string }[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RelationshipDatasource {
|
||||||
|
type: "link"
|
||||||
|
tableId: string
|
||||||
|
rowId: string
|
||||||
|
rowTableId: string
|
||||||
|
fieldName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserDatasource {
|
||||||
|
type: "user"
|
||||||
|
tableId: InternalTable.USER_METADATA
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GroupUserDatasource {
|
||||||
|
type: "groupUser"
|
||||||
|
tableId: InternalTable.USER_METADATA
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CustomDatasource {
|
||||||
|
type: "custom"
|
||||||
|
data: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NestedProviderDatasource {
|
||||||
|
type: "provider"
|
||||||
|
value?: {
|
||||||
|
schema: TableSchema
|
||||||
|
primaryDisplay: string
|
||||||
|
rows: Row[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BaseFieldDatasource<
|
||||||
|
TType extends "field" | "queryarray" | "jsonarray"
|
||||||
|
> {
|
||||||
|
type: TType
|
||||||
|
tableId: string
|
||||||
|
fieldType: "attachment" | "array"
|
||||||
|
value: string[] | Row[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FieldDatasource = BaseFieldDatasource<"field">
|
||||||
|
export type QueryArrayFieldDatasource = BaseFieldDatasource<"queryarray">
|
||||||
|
export type JSONArrayFieldDatasource = BaseFieldDatasource<"jsonarray">
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { LegacyFilter, SortOrder, UISearchFilter } from "../../api"
|
||||||
|
import { SearchFilters } from "../../sdk"
|
||||||
|
|
||||||
|
export * from "./datasources"
|
||||||
|
|
||||||
|
export interface DataFetchOptions<TQuery = SearchFilters> {
|
||||||
|
// Search config
|
||||||
|
filter: UISearchFilter | LegacyFilter[] | null
|
||||||
|
query: TQuery
|
||||||
|
// Sorting config
|
||||||
|
sortColumn: string | null
|
||||||
|
sortOrder: SortOrder
|
||||||
|
// Pagination config
|
||||||
|
limit: number
|
||||||
|
paginate: boolean
|
||||||
|
}
|
|
@ -1 +1,2 @@
|
||||||
export * from "./stores"
|
export * from "./stores"
|
||||||
|
export * from "./dataFetch"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { UITable, UIView } from "@budibase/types"
|
import { UITable, UIView } from "@budibase/types"
|
||||||
|
|
||||||
export type UIDatasource = UITable | UIView
|
export type UIDatasource = UITable | (Omit<UIView, "type"> & { type: "viewV2" })
|
||||||
|
|
||||||
export interface UIFieldMutation {
|
export interface UIFieldMutation {
|
||||||
visible?: boolean
|
visible?: boolean
|
||||||
|
|
|
@ -8,10 +8,9 @@ import {
|
||||||
UISearchFilter,
|
UISearchFilter,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
export interface UITable extends Omit<Table, "type"> {
|
export interface UITable extends Table {
|
||||||
name: string
|
name: string
|
||||||
id: string
|
id: string
|
||||||
type: string
|
|
||||||
tableId: string
|
tableId: string
|
||||||
primaryDisplay?: string
|
primaryDisplay?: string
|
||||||
sort?: {
|
sort?: {
|
||||||
|
|
|
@ -46,7 +46,10 @@ const svelteCompilePlugin = {
|
||||||
let { argv } = require("yargs")
|
let { argv } = require("yargs")
|
||||||
|
|
||||||
async function runBuild(entry, outfile) {
|
async function runBuild(entry, outfile) {
|
||||||
const isDev = process.env.NODE_ENV !== "production"
|
const isDev = !process.env.CI
|
||||||
|
|
||||||
|
console.log(`Building in mode dev mode: ${isDev}`)
|
||||||
|
|
||||||
const tsconfig = argv["p"] || `tsconfig.build.json`
|
const tsconfig = argv["p"] || `tsconfig.build.json`
|
||||||
|
|
||||||
const { data: tsconfigPathPluginContent } = loadTsConfig(
|
const { data: tsconfigPathPluginContent } = loadTsConfig(
|
||||||
|
@ -58,7 +61,7 @@ async function runBuild(entry, outfile) {
|
||||||
entryPoints: [entry],
|
entryPoints: [entry],
|
||||||
bundle: true,
|
bundle: true,
|
||||||
minify: !isDev,
|
minify: !isDev,
|
||||||
sourcemap: isDev,
|
sourcemap: tsconfigPathPluginContent.compilerOptions.sourceMap,
|
||||||
tsconfig,
|
tsconfig,
|
||||||
plugins: [
|
plugins: [
|
||||||
svelteCompilePlugin,
|
svelteCompilePlugin,
|
||||||
|
@ -125,10 +128,12 @@ async function runBuild(entry, outfile) {
|
||||||
|
|
||||||
await Promise.all([hbsFiles, mainBuild, oldClientVersions])
|
await Promise.all([hbsFiles, mainBuild, oldClientVersions])
|
||||||
|
|
||||||
fs.writeFileSync(
|
if (isDev) {
|
||||||
`dist/${path.basename(outfile)}.meta.json`,
|
fs.writeFileSync(
|
||||||
JSON.stringify((await mainBuild).metafile)
|
`dist/${path.basename(outfile)}.meta.json`,
|
||||||
)
|
JSON.stringify((await mainBuild).metafile)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
"\x1b[32m%s\x1b[0m",
|
"\x1b[32m%s\x1b[0m",
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
"sourceMap": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@budibase/types": ["./packages/types/src"],
|
"@budibase/types": ["./packages/types/src"],
|
||||||
"@budibase/backend-core": ["./packages/backend-core/src"],
|
"@budibase/backend-core": ["./packages/backend-core/src"],
|
||||||
|
|
Loading…
Reference in New Issue