Merge branch 'develop' into pipeline/fail-if-changes-in-master
This commit is contained in:
commit
d7fb75d10c
|
@ -1,2 +1,3 @@
|
||||||
nodejs 14.21.3
|
nodejs 14.21.3
|
||||||
python 3.10.0
|
python 3.10.0
|
||||||
|
yarn 1.22.19
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.8.28-alpha.1",
|
"version": "2.8.29-alpha.4",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -51,9 +51,9 @@
|
||||||
"kill-builder": "kill-port 3000",
|
"kill-builder": "kill-port 3000",
|
||||||
"kill-server": "kill-port 4001 4002",
|
"kill-server": "kill-port 4001 4002",
|
||||||
"kill-all": "yarn run kill-builder && yarn run kill-server",
|
"kill-all": "yarn run kill-builder && yarn run kill-server",
|
||||||
"dev": "yarn run kill-all && yarn nx run-many --target=dev:builder",
|
"dev": "yarn run kill-all && lerna run --stream --parallel dev:builder",
|
||||||
"dev:noserver": "yarn run kill-builder && lerna run --stream dev:stack:up && yarn nx run-many --target=dev:builder --exclude=@budibase/backend-core,@budibase/server,@budibase/worker",
|
"dev:noserver": "yarn run kill-builder && lerna run --stream dev:stack:up && lerna run --stream --parallel dev:builder --ignore @budibase/backend-core --ignore @budibase/server --ignore @budibase/worker",
|
||||||
"dev:server": "yarn run kill-server && yarn nx run-many --target=dev:builder --projects=@budibase/worker,@budibase/server",
|
"dev:server": "yarn run kill-server && lerna run --stream --parallel dev:builder --scope @budibase/worker --scope @budibase/server",
|
||||||
"dev:built": "yarn run kill-all && cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream dev:built",
|
"dev:built": "yarn run kill-all && cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream dev:built",
|
||||||
"dev:docker": "yarn build:docker:pre && docker-compose -f hosting/docker-compose.build.yaml -f hosting/docker-compose.dev.yaml --env-file hosting/.env up --build --scale proxy-service=0",
|
"dev:docker": "yarn build:docker:pre && docker-compose -f hosting/docker-compose.build.yaml -f hosting/docker-compose.dev.yaml --env-file hosting/.env up --build --scale proxy-service=0",
|
||||||
"test": "lerna run --stream test --stream",
|
"test": "lerna run --stream test --stream",
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { Config } from "@jest/types"
|
import { Config } from "@jest/types"
|
||||||
const preset = require("ts-jest/jest-preset")
|
|
||||||
|
|
||||||
const baseConfig: Config.InitialProjectOptions = {
|
const baseConfig: Config.InitialProjectOptions = {
|
||||||
...preset,
|
|
||||||
preset: "@trendyol/jest-testcontainers",
|
preset: "@trendyol/jest-testcontainers",
|
||||||
setupFiles: ["./tests/jestEnv.ts"],
|
setupFiles: ["./tests/jestEnv.ts"],
|
||||||
setupFilesAfterEnv: ["./tests/jestSetup.ts"],
|
setupFilesAfterEnv: ["./tests/jestSetup.ts"],
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
"@budibase/nano": "10.1.2",
|
"@budibase/nano": "10.1.2",
|
||||||
"@budibase/pouchdb-replication-stream": "1.2.10",
|
"@budibase/pouchdb-replication-stream": "1.2.10",
|
||||||
"@budibase/types": "0.0.0",
|
"@budibase/types": "0.0.0",
|
||||||
"@shopify/jest-koa-mocks": "5.0.1",
|
|
||||||
"@techpass/passport-openidconnect": "0.3.2",
|
"@techpass/passport-openidconnect": "0.3.2",
|
||||||
"aws-cloudfront-sign": "2.2.0",
|
"aws-cloudfront-sign": "2.2.0",
|
||||||
"aws-sdk": "2.1030.0",
|
"aws-sdk": "2.1030.0",
|
||||||
|
@ -58,12 +57,13 @@
|
||||||
"uuid": "8.3.2"
|
"uuid": "8.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/test-sequencer": "29.5.0",
|
"@jest/test-sequencer": "29.6.2",
|
||||||
"@swc/core": "^1.3.25",
|
"@shopify/jest-koa-mocks": "5.1.1",
|
||||||
"@swc/jest": "^0.2.24",
|
"@swc/core": "1.3.71",
|
||||||
|
"@swc/jest": "0.2.27",
|
||||||
"@trendyol/jest-testcontainers": "^2.1.1",
|
"@trendyol/jest-testcontainers": "^2.1.1",
|
||||||
"@types/chance": "1.1.3",
|
"@types/chance": "1.1.3",
|
||||||
"@types/jest": "29.5.0",
|
"@types/jest": "29.5.3",
|
||||||
"@types/koa": "2.13.4",
|
"@types/koa": "2.13.4",
|
||||||
"@types/lodash": "4.14.180",
|
"@types/lodash": "4.14.180",
|
||||||
"@types/node": "14.18.20",
|
"@types/node": "14.18.20",
|
||||||
|
@ -75,15 +75,14 @@
|
||||||
"@types/uuid": "8.3.4",
|
"@types/uuid": "8.3.4",
|
||||||
"chance": "1.1.8",
|
"chance": "1.1.8",
|
||||||
"ioredis-mock": "8.7.0",
|
"ioredis-mock": "8.7.0",
|
||||||
"jest": "29.5.0",
|
"jest": "29.6.2",
|
||||||
"jest-environment-node": "29.5.0",
|
"jest-environment-node": "29.6.2",
|
||||||
"jest-serial-runner": "^1.2.1",
|
"jest-serial-runner": "1.2.1",
|
||||||
"koa": "2.13.4",
|
"koa": "2.13.4",
|
||||||
"nodemon": "2.0.16",
|
"nodemon": "2.0.16",
|
||||||
"pino-pretty": "10.0.0",
|
"pino-pretty": "10.0.0",
|
||||||
"pouchdb-adapter-memory": "7.2.2",
|
"pouchdb-adapter-memory": "7.2.2",
|
||||||
"timekeeper": "2.2.0",
|
"timekeeper": "2.2.0",
|
||||||
"ts-jest": "29.0.5",
|
|
||||||
"ts-node": "10.8.1",
|
"ts-node": "10.8.1",
|
||||||
"tsconfig-paths": "4.0.0",
|
"tsconfig-paths": "4.0.0",
|
||||||
"typescript": "4.7.3"
|
"typescript": "4.7.3"
|
||||||
|
|
|
@ -8,6 +8,6 @@ then
|
||||||
jest --coverage --runInBand --forceExit
|
jest --coverage --runInBand --forceExit
|
||||||
else
|
else
|
||||||
# --maxWorkers performs better in development
|
# --maxWorkers performs better in development
|
||||||
echo "jest --coverage --forceExit"
|
echo "jest --coverage --detectOpenHandles"
|
||||||
jest --coverage --forceExit
|
jest --coverage --detectOpenHandles
|
||||||
fi
|
fi
|
|
@ -1,5 +1,5 @@
|
||||||
const { flatten } = require("lodash")
|
import flatten from "lodash/flatten"
|
||||||
const { cloneDeep } = require("lodash/fp")
|
import cloneDeep from "lodash/fp/cloneDeep"
|
||||||
|
|
||||||
export type RoleHierarchy = {
|
export type RoleHierarchy = {
|
||||||
permissionId: string
|
permissionId: string
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { prefixRoleID, getRoleParams, DocumentType, SEPARATOR } from "../db"
|
||||||
import { getAppDB } from "../context"
|
import { getAppDB } from "../context"
|
||||||
import { doWithDB } from "../db"
|
import { doWithDB } from "../db"
|
||||||
import { Screen, Role as RoleDoc } from "@budibase/types"
|
import { Screen, Role as RoleDoc } from "@budibase/types"
|
||||||
const { cloneDeep } = require("lodash/fp")
|
import cloneDeep from "lodash/fp/cloneDeep"
|
||||||
|
|
||||||
export const BUILTIN_ROLE_IDS = {
|
export const BUILTIN_ROLE_IDS = {
|
||||||
ADMIN: "ADMIN",
|
ADMIN: "ADMIN",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { cloneDeep } from "lodash"
|
import cloneDeep from "lodash/cloneDeep"
|
||||||
import * as permissions from "../permissions"
|
import * as permissions from "../permissions"
|
||||||
import { BUILTIN_ROLE_IDS } from "../roles"
|
import { BUILTIN_ROLE_IDS } from "../roles"
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Feature, License, Quotas } from "@budibase/types"
|
import { Feature, License, Quotas } from "@budibase/types"
|
||||||
import _ from "lodash"
|
import cloneDeep from "lodash/cloneDeep"
|
||||||
|
|
||||||
let CLOUD_FREE_LICENSE: License
|
let CLOUD_FREE_LICENSE: License
|
||||||
let UNLIMITED_LICENSE: License
|
let UNLIMITED_LICENSE: License
|
||||||
|
@ -58,7 +58,7 @@ export const useCloudFree = () => {
|
||||||
// FEATURES
|
// FEATURES
|
||||||
|
|
||||||
const useFeature = (feature: Feature) => {
|
const useFeature = (feature: Feature) => {
|
||||||
const license = _.cloneDeep(UNLIMITED_LICENSE)
|
const license = cloneDeep(UNLIMITED_LICENSE)
|
||||||
const opts: UseLicenseOpts = {
|
const opts: UseLicenseOpts = {
|
||||||
features: [feature],
|
features: [feature],
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ export const useSyncAutomations = () => {
|
||||||
// QUOTAS
|
// QUOTAS
|
||||||
|
|
||||||
export const setAutomationLogsQuota = (value: number) => {
|
export const setAutomationLogsQuota = (value: number) => {
|
||||||
const license = _.cloneDeep(UNLIMITED_LICENSE)
|
const license = cloneDeep(UNLIMITED_LICENSE)
|
||||||
license.quotas.constant.automationLogRetentionDays.value = value
|
license.quotas.constant.automationLogRetentionDays.value = value
|
||||||
return useLicense(license)
|
return useLicense(license)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
CreateAccount,
|
CreateAccount,
|
||||||
CreatePassswordAccount,
|
CreatePassswordAccount,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import _ from "lodash"
|
import sample from "lodash/sample"
|
||||||
|
|
||||||
export const account = (partial: Partial<Account> = {}): Account => {
|
export const account = (partial: Partial<Account> = {}): Account => {
|
||||||
return {
|
return {
|
||||||
|
@ -46,13 +46,11 @@ export const cloudAccount = (): CloudAccount => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function providerType(): AccountSSOProviderType {
|
function providerType(): AccountSSOProviderType {
|
||||||
return _.sample(
|
return sample(Object.values(AccountSSOProviderType)) as AccountSSOProviderType
|
||||||
Object.values(AccountSSOProviderType)
|
|
||||||
) as AccountSSOProviderType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function provider(): AccountSSOProvider {
|
function provider(): AccountSSOProvider {
|
||||||
return _.sample(Object.values(AccountSSOProvider)) as AccountSSOProvider
|
return sample(Object.values(AccountSSOProvider)) as AccountSSOProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ssoAccount(account: Account = cloudAccount()): SSOAccount {
|
export function ssoAccount(account: Account = cloudAccount()): SSOAccount {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { ScimCreateGroupRequest, ScimCreateUserRequest } from "@budibase/types"
|
import { ScimCreateGroupRequest, ScimCreateUserRequest } from "@budibase/types"
|
||||||
import { uuid } from "./common"
|
import { uuid } from "./common"
|
||||||
import { generator } from "./generator"
|
import { generator } from "./generator"
|
||||||
import _ from "lodash"
|
|
||||||
|
|
||||||
interface CreateUserRequestFields {
|
interface CreateUserRequestFields {
|
||||||
externalId: string
|
externalId: string
|
||||||
|
@ -20,10 +19,10 @@ export function createUserRequest(userData?: Partial<CreateUserRequestFields>) {
|
||||||
username: generator.name(),
|
username: generator.name(),
|
||||||
}
|
}
|
||||||
|
|
||||||
const { externalId, email, firstName, lastName, username } = _.assign(
|
const { externalId, email, firstName, lastName, username } = {
|
||||||
defaultValues,
|
...defaultValues,
|
||||||
userData
|
...userData,
|
||||||
)
|
}
|
||||||
|
|
||||||
let user: ScimCreateUserRequest = {
|
let user: ScimCreateUserRequest = {
|
||||||
schemas: [
|
schemas: [
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { generator } from "./generator"
|
||||||
import { email, uuid } from "./common"
|
import { email, uuid } from "./common"
|
||||||
import * as shared from "./shared"
|
import * as shared from "./shared"
|
||||||
import { user } from "./shared"
|
import { user } from "./shared"
|
||||||
import _ from "lodash"
|
import sample from "lodash/sample"
|
||||||
|
|
||||||
export function OAuth(): OAuth2 {
|
export function OAuth(): OAuth2 {
|
||||||
return {
|
return {
|
||||||
|
@ -47,7 +47,7 @@ export function authDetails(userDoc?: User): SSOAuthDetails {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function providerType(): SSOProviderType {
|
export function providerType(): SSOProviderType {
|
||||||
return _.sample(Object.values(SSOProviderType)) as SSOProviderType
|
return sample(Object.values(SSOProviderType)) as SSOProviderType
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ssoProfile(user?: User): SSOProfile {
|
export function ssoProfile(user?: User): SSOProfile {
|
||||||
|
|
|
@ -101,14 +101,14 @@
|
||||||
"@rollup/plugin-replace": "^2.4.2",
|
"@rollup/plugin-replace": "^2.4.2",
|
||||||
"@roxi/routify": "2.18.5",
|
"@roxi/routify": "2.18.5",
|
||||||
"@sveltejs/vite-plugin-svelte": "1.0.1",
|
"@sveltejs/vite-plugin-svelte": "1.0.1",
|
||||||
"@testing-library/jest-dom": "^5.11.10",
|
"@testing-library/jest-dom": "5.17.0",
|
||||||
"@testing-library/svelte": "^3.2.2",
|
"@testing-library/svelte": "^3.2.2",
|
||||||
"babel-jest": "^26.6.3",
|
"babel-jest": "29.6.2",
|
||||||
"cypress": "^9.3.1",
|
"cypress": "^9.3.1",
|
||||||
"cypress-multi-reporters": "^1.6.0",
|
"cypress-multi-reporters": "^1.6.0",
|
||||||
"cypress-terminal-report": "^1.4.1",
|
"cypress-terminal-report": "^1.4.1",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "^26.6.3",
|
"jest": "29.6.2",
|
||||||
"jsdom": "^21.1.1",
|
"jsdom": "^21.1.1",
|
||||||
"mochawesome": "^7.1.3",
|
"mochawesome": "^7.1.3",
|
||||||
"mochawesome-merge": "^4.2.1",
|
"mochawesome-merge": "^4.2.1",
|
||||||
|
|
|
@ -75,6 +75,14 @@
|
||||||
{
|
{
|
||||||
"name": "Chart",
|
"name": "Chart",
|
||||||
"icon": "GraphBarVertical",
|
"icon": "GraphBarVertical",
|
||||||
"children": ["bar", "line", "area", "candlestick", "pie", "donut"]
|
"children": [
|
||||||
|
"bar",
|
||||||
|
"line",
|
||||||
|
"area",
|
||||||
|
"candlestick",
|
||||||
|
"pie",
|
||||||
|
"donut",
|
||||||
|
"histogram"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { Layout, Body, Button } from "@budibase/bbui"
|
import { Layout, Heading, Body, Button } from "@budibase/bbui"
|
||||||
import { downloadStream } from "@budibase/frontend-core"
|
import { downloadStream } from "@budibase/frontend-core"
|
||||||
import Spinner from "components/common/Spinner.svelte"
|
import Spinner from "components/common/Spinner.svelte"
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Layout noPadding>
|
<Layout noPadding>
|
||||||
|
<Heading>System logs</Heading>
|
||||||
<Body>Download your latest logs to share with the Budibase team</Body>
|
<Body>Download your latest logs to share with the Budibase team</Body>
|
||||||
<div class="download-button">
|
<div class="download-button">
|
||||||
<Button cta on:click={download} disabled={loading}>
|
<Button cta on:click={download} disabled={loading}>
|
||||||
|
@ -25,7 +26,7 @@
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<Spinner size="10" />
|
<Spinner size="10" />
|
||||||
{/if}
|
{/if}
|
||||||
Download system logs
|
Download
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -53,9 +53,9 @@
|
||||||
"yaml": "^2.1.1"
|
"yaml": "^2.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@swc/core": "^1.3.25",
|
"@swc/core": "1.3.71",
|
||||||
"@swc/jest": "^0.2.24",
|
"@swc/jest": "0.2.27",
|
||||||
"@types/jest": "^29.4.0",
|
"@types/jest": "29.5.3",
|
||||||
"@types/node-fetch": "2.6.1",
|
"@types/node-fetch": "2.6.1",
|
||||||
"@types/pouchdb": "^6.4.0",
|
"@types/pouchdb": "^6.4.0",
|
||||||
"copyfiles": "^2.4.1",
|
"copyfiles": "^2.4.1",
|
||||||
|
|
|
@ -2212,6 +2212,147 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"histogram": {
|
||||||
|
"name": "Histogram Chart",
|
||||||
|
"description": "Histogram chart",
|
||||||
|
"icon": "Histogram",
|
||||||
|
"size": {
|
||||||
|
"width": 600,
|
||||||
|
"height": 400
|
||||||
|
},
|
||||||
|
"requiredAncestors": ["dataprovider"],
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Title",
|
||||||
|
"key": "title"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dataProvider",
|
||||||
|
"label": "Provider",
|
||||||
|
"key": "dataProvider",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"label": "Data column",
|
||||||
|
"key": "valueColumn",
|
||||||
|
"dependsOn": "dataProvider",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Y axis label",
|
||||||
|
"key": "yAxisLabel",
|
||||||
|
"defaultValue": "Frequency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "X axis label",
|
||||||
|
"key": "xAxisLabel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"label": "Bucket count",
|
||||||
|
"key": "bucketCount",
|
||||||
|
"defaultValue": 10,
|
||||||
|
"min": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Data labels",
|
||||||
|
"key": "dataLabels",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Width",
|
||||||
|
"key": "width"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Height",
|
||||||
|
"key": "height",
|
||||||
|
"defaultValue": "400"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Colors",
|
||||||
|
"key": "palette",
|
||||||
|
"defaultValue": "Palette 1",
|
||||||
|
"options": [
|
||||||
|
"Custom",
|
||||||
|
"Palette 1",
|
||||||
|
"Palette 2",
|
||||||
|
"Palette 3",
|
||||||
|
"Palette 4",
|
||||||
|
"Palette 5",
|
||||||
|
"Palette 6",
|
||||||
|
"Palette 7",
|
||||||
|
"Palette 8",
|
||||||
|
"Palette 9",
|
||||||
|
"Palette 10"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C1",
|
||||||
|
"key": "c1",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C2",
|
||||||
|
"key": "c2",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C3",
|
||||||
|
"key": "c3",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C4",
|
||||||
|
"key": "c4",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C5",
|
||||||
|
"key": "c5",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Animate",
|
||||||
|
"key": "animate",
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Horizontal",
|
||||||
|
"key": "horizontal",
|
||||||
|
"defaultValue": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"form": {
|
"form": {
|
||||||
"name": "Form",
|
"name": "Form",
|
||||||
"icon": "Form",
|
"icon": "Form",
|
||||||
|
@ -3965,6 +4106,10 @@
|
||||||
"label": "Bar",
|
"label": "Bar",
|
||||||
"value": "bar"
|
"value": "bar"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": "Histogram",
|
||||||
|
"value": "histogram"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "Line",
|
"label": "Line",
|
||||||
"value": "line"
|
"value": "line"
|
||||||
|
@ -4215,6 +4360,47 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Histogram Chart",
|
||||||
|
"icon": "Histogram",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "chartType",
|
||||||
|
"value": "histogram"
|
||||||
|
},
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"label": "Value column",
|
||||||
|
"key": "valueColumn",
|
||||||
|
"dependsOn": "dataSource",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Y axis label",
|
||||||
|
"key": "yAxisLabel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "X axis label",
|
||||||
|
"key": "xAxisLabel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Horizontal",
|
||||||
|
"key": "horizontal",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"label": "Bucket count",
|
||||||
|
"key": "bucketCount",
|
||||||
|
"defaultValue": 10,
|
||||||
|
"min": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"section": true,
|
"section": true,
|
||||||
"name": "Line Chart",
|
"name": "Line Chart",
|
||||||
|
|
|
@ -46,6 +46,9 @@
|
||||||
export let lowColumn
|
export let lowColumn
|
||||||
export let dateColumn
|
export let dateColumn
|
||||||
|
|
||||||
|
// Histogram
|
||||||
|
export let bucketCount
|
||||||
|
|
||||||
let dataProviderId
|
let dataProviderId
|
||||||
|
|
||||||
$: colors = c1 && c2 && c3 && c4 && c5 ? [c1, c2, c3, c4, c5] : null
|
$: colors = c1 && c2 && c3 && c4 && c5 ? [c1, c2, c3, c4, c5] : null
|
||||||
|
@ -92,6 +95,7 @@
|
||||||
highColumn,
|
highColumn,
|
||||||
lowColumn,
|
lowColumn,
|
||||||
dateColumn,
|
dateColumn,
|
||||||
|
bucketCount,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
<script>
|
||||||
|
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
|
||||||
|
import ApexChart from "./ApexChart.svelte"
|
||||||
|
|
||||||
|
export let title
|
||||||
|
export let dataProvider
|
||||||
|
export let valueColumn
|
||||||
|
export let xAxisLabel
|
||||||
|
export let yAxisLabel
|
||||||
|
export let height
|
||||||
|
export let width
|
||||||
|
export let dataLabels
|
||||||
|
export let animate
|
||||||
|
export let palette
|
||||||
|
export let c1, c2, c3, c4, c5
|
||||||
|
export let horizontal
|
||||||
|
export let bucketCount = 10
|
||||||
|
|
||||||
|
$: options = setUpChart(
|
||||||
|
title,
|
||||||
|
dataProvider,
|
||||||
|
valueColumn,
|
||||||
|
xAxisLabel || valueColumn,
|
||||||
|
yAxisLabel,
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
dataLabels,
|
||||||
|
animate,
|
||||||
|
palette,
|
||||||
|
horizontal,
|
||||||
|
c1 && c2 && c3 && c4 && c5 ? [c1, c2, c3, c4, c5] : null,
|
||||||
|
customColor,
|
||||||
|
bucketCount
|
||||||
|
)
|
||||||
|
|
||||||
|
$: customColor = palette === "Custom"
|
||||||
|
|
||||||
|
const setUpChart = (
|
||||||
|
title,
|
||||||
|
dataProvider,
|
||||||
|
valueColumn,
|
||||||
|
xAxisLabel, //freqAxisLabel
|
||||||
|
yAxisLabel, //valueAxisLabel
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
dataLabels,
|
||||||
|
animate,
|
||||||
|
palette,
|
||||||
|
horizontal,
|
||||||
|
colors,
|
||||||
|
customColor,
|
||||||
|
bucketCount
|
||||||
|
) => {
|
||||||
|
const allCols = [valueColumn]
|
||||||
|
if (
|
||||||
|
!dataProvider ||
|
||||||
|
!dataProvider.rows?.length ||
|
||||||
|
allCols.find(x => x == null)
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch data
|
||||||
|
const { schema, rows } = dataProvider
|
||||||
|
const reducer = row => (valid, column) => valid && row[column] != null
|
||||||
|
const hasAllColumns = row => allCols.reduce(reducer(row), true)
|
||||||
|
const data = rows.filter(row => hasAllColumns(row)).slice(0, 100)
|
||||||
|
if (!schema || !data.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialise default chart
|
||||||
|
let builder = new ApexOptionsBuilder()
|
||||||
|
.type("bar")
|
||||||
|
.title(title)
|
||||||
|
.width(width)
|
||||||
|
.height(height)
|
||||||
|
.xLabel(horizontal ? yAxisLabel : xAxisLabel)
|
||||||
|
.yLabel(horizontal ? xAxisLabel : yAxisLabel)
|
||||||
|
.dataLabels(dataLabels)
|
||||||
|
.animate(animate)
|
||||||
|
.palette(palette)
|
||||||
|
.horizontal(horizontal)
|
||||||
|
.colors(customColor ? colors : null)
|
||||||
|
|
||||||
|
if (horizontal) {
|
||||||
|
builder = builder.setOption(["plotOptions", "bar", "barHeight"], "90%")
|
||||||
|
} else {
|
||||||
|
builder = builder.setOption(["plotOptions", "bar", "columnWidth"], "99%")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull occurences of the value.
|
||||||
|
let flatlist = data.map(row => {
|
||||||
|
return row[valueColumn]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Build range buckets
|
||||||
|
let interval = Math.max(...flatlist) / bucketCount
|
||||||
|
let counts = Array(bucketCount).fill(0)
|
||||||
|
|
||||||
|
// Assign row data to a bucket
|
||||||
|
let buckets = flatlist.reduce((acc, val) => {
|
||||||
|
let dest = Math.min(Math.floor(val / interval), bucketCount - 1)
|
||||||
|
acc[dest] = acc[dest] + 1
|
||||||
|
return acc
|
||||||
|
}, counts)
|
||||||
|
|
||||||
|
const rangeLabel = bucketIdx => {
|
||||||
|
return `${Math.floor(interval * bucketIdx)} - ${Math.floor(
|
||||||
|
interval * (bucketIdx + 1)
|
||||||
|
)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
name: yAxisLabel,
|
||||||
|
data: Array.from({ length: buckets.length }, (_, i) => ({
|
||||||
|
x: rangeLabel(i),
|
||||||
|
y: buckets[i],
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
builder = builder.setOption(["xaxis", "labels"], {
|
||||||
|
formatter: x => {
|
||||||
|
return x + ""
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
builder = builder.series(series)
|
||||||
|
|
||||||
|
return builder.getOptions()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ApexChart {options} />
|
|
@ -4,3 +4,4 @@ export { default as pie } from "./PieChart.svelte"
|
||||||
export { default as donut } from "./DonutChart.svelte"
|
export { default as donut } from "./DonutChart.svelte"
|
||||||
export { default as area } from "./AreaChart.svelte"
|
export { default as area } from "./AreaChart.svelte"
|
||||||
export { default as candlestick } from "./CandleStickChart.svelte"
|
export { default as candlestick } from "./CandleStickChart.svelte"
|
||||||
|
export { default as histogram } from "./HistogramChart.svelte"
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit a60183319f410d05aaa1c2f2718b772978b54d64
|
Subproject commit 63fa1b15f6e2afa8a264d597157fd798c9ce031c
|
|
@ -2,10 +2,8 @@ import { Config } from "@jest/types"
|
||||||
|
|
||||||
import * as fs from "fs"
|
import * as fs from "fs"
|
||||||
import { join } from "path"
|
import { join } from "path"
|
||||||
const preset = require("ts-jest/jest-preset")
|
|
||||||
|
|
||||||
const baseConfig: Config.InitialProjectOptions = {
|
const baseConfig: Config.InitialProjectOptions = {
|
||||||
...preset,
|
|
||||||
preset: "@trendyol/jest-testcontainers",
|
preset: "@trendyol/jest-testcontainers",
|
||||||
setupFiles: ["./src/tests/jestEnv.ts"],
|
setupFiles: ["./src/tests/jestEnv.ts"],
|
||||||
setupFilesAfterEnv: ["./src/tests/jestSetup.ts"],
|
setupFilesAfterEnv: ["./src/tests/jestSetup.ts"],
|
||||||
|
|
|
@ -131,15 +131,15 @@
|
||||||
"@babel/core": "7.17.4",
|
"@babel/core": "7.17.4",
|
||||||
"@babel/preset-env": "7.16.11",
|
"@babel/preset-env": "7.16.11",
|
||||||
"@budibase/standard-components": "^0.9.139",
|
"@budibase/standard-components": "^0.9.139",
|
||||||
"@jest/test-sequencer": "29.5.0",
|
"@jest/test-sequencer": "29.6.2",
|
||||||
"@swc/core": "^1.3.25",
|
"@swc/core": "1.3.71",
|
||||||
"@swc/jest": "^0.2.24",
|
"@swc/jest": "0.2.27",
|
||||||
"@trendyol/jest-testcontainers": "^2.1.1",
|
"@trendyol/jest-testcontainers": "2.1.1",
|
||||||
"@types/apidoc": "0.50.0",
|
"@types/apidoc": "0.50.0",
|
||||||
"@types/bson": "4.2.0",
|
"@types/bson": "4.2.0",
|
||||||
"@types/global-agent": "2.1.1",
|
"@types/global-agent": "2.1.1",
|
||||||
"@types/google-spreadsheet": "3.1.5",
|
"@types/google-spreadsheet": "3.1.5",
|
||||||
"@types/jest": "29.5.0",
|
"@types/jest": "29.5.3",
|
||||||
"@types/koa": "2.13.4",
|
"@types/koa": "2.13.4",
|
||||||
"@types/koa__router": "8.0.8",
|
"@types/koa__router": "8.0.8",
|
||||||
"@types/lodash": "4.14.180",
|
"@types/lodash": "4.14.180",
|
||||||
|
@ -155,15 +155,15 @@
|
||||||
"@types/tar": "6.1.5",
|
"@types/tar": "6.1.5",
|
||||||
"@typescript-eslint/parser": "5.45.0",
|
"@typescript-eslint/parser": "5.45.0",
|
||||||
"apidoc": "0.50.4",
|
"apidoc": "0.50.4",
|
||||||
"babel-jest": "29.5.0",
|
"babel-jest": "29.6.2",
|
||||||
"copyfiles": "2.4.1",
|
"copyfiles": "2.4.1",
|
||||||
"docker-compose": "0.23.17",
|
"docker-compose": "0.23.17",
|
||||||
"eslint": "6.8.0",
|
"eslint": "6.8.0",
|
||||||
"is-wsl": "2.2.0",
|
"is-wsl": "2.2.0",
|
||||||
"jest": "29.5.0",
|
"jest": "29.6.2",
|
||||||
"jest-openapi": "0.14.2",
|
"jest-openapi": "0.14.2",
|
||||||
"jest-runner": "29.5.0",
|
"jest-runner": "29.6.2",
|
||||||
"jest-serial-runner": "^1.2.1",
|
"jest-serial-runner": "1.2.1",
|
||||||
"nodemon": "2.0.15",
|
"nodemon": "2.0.15",
|
||||||
"openapi-types": "9.3.1",
|
"openapi-types": "9.3.1",
|
||||||
"openapi-typescript": "5.2.0",
|
"openapi-typescript": "5.2.0",
|
||||||
|
@ -172,7 +172,6 @@
|
||||||
"supertest": "6.2.2",
|
"supertest": "6.2.2",
|
||||||
"swagger-jsdoc": "6.1.0",
|
"swagger-jsdoc": "6.1.0",
|
||||||
"timekeeper": "2.2.0",
|
"timekeeper": "2.2.0",
|
||||||
"ts-jest": "29.0.5",
|
|
||||||
"ts-node": "10.8.1",
|
"ts-node": "10.8.1",
|
||||||
"tsconfig-paths": "4.0.0",
|
"tsconfig-paths": "4.0.0",
|
||||||
"typescript": "4.7.3",
|
"typescript": "4.7.3",
|
||||||
|
|
|
@ -8,26 +8,13 @@ import {
|
||||||
Datasource,
|
Datasource,
|
||||||
IncludeRelationship,
|
IncludeRelationship,
|
||||||
Operation,
|
Operation,
|
||||||
|
PatchRowRequest,
|
||||||
|
PatchRowResponse,
|
||||||
Row,
|
Row,
|
||||||
Table,
|
Table,
|
||||||
UserCtx,
|
UserCtx,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import * as utils from "./utils"
|
|
||||||
|
|
||||||
async function getRow(
|
|
||||||
tableId: string,
|
|
||||||
rowId: string,
|
|
||||||
opts?: { relationships?: boolean }
|
|
||||||
) {
|
|
||||||
const response = (await handleRequest(Operation.READ, tableId, {
|
|
||||||
id: breakRowIdField(rowId),
|
|
||||||
includeSqlRelationships: opts?.relationships
|
|
||||||
? IncludeRelationship.INCLUDE
|
|
||||||
: IncludeRelationship.EXCLUDE,
|
|
||||||
})) as Row[]
|
|
||||||
return response ? response[0] : response
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handleRequest(
|
export async function handleRequest(
|
||||||
operation: Operation,
|
operation: Operation,
|
||||||
|
@ -55,14 +42,12 @@ export async function handleRequest(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function patch(ctx: UserCtx) {
|
export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
|
||||||
const inputs = ctx.request.body
|
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
const id = inputs._id
|
const { id, ...rowData } = ctx.request.body
|
||||||
// don't save the ID to db
|
|
||||||
delete inputs._id
|
const validateResult = await sdk.rows.utils.validate({
|
||||||
const validateResult = await utils.validate({
|
row: rowData,
|
||||||
row: inputs,
|
|
||||||
tableId,
|
tableId,
|
||||||
})
|
})
|
||||||
if (!validateResult.valid) {
|
if (!validateResult.valid) {
|
||||||
|
@ -70,9 +55,11 @@ export async function patch(ctx: UserCtx) {
|
||||||
}
|
}
|
||||||
const response = await handleRequest(Operation.UPDATE, tableId, {
|
const response = await handleRequest(Operation.UPDATE, tableId, {
|
||||||
id: breakRowIdField(id),
|
id: breakRowIdField(id),
|
||||||
row: inputs,
|
row: rowData,
|
||||||
|
})
|
||||||
|
const row = await sdk.rows.external.getRow(tableId, id, {
|
||||||
|
relationships: true,
|
||||||
})
|
})
|
||||||
const row = await getRow(tableId, id, { relationships: true })
|
|
||||||
const table = await sdk.tables.getTable(tableId)
|
const table = await sdk.tables.getTable(tableId)
|
||||||
return {
|
return {
|
||||||
...response,
|
...response,
|
||||||
|
@ -84,7 +71,7 @@ export async function patch(ctx: UserCtx) {
|
||||||
export async function save(ctx: UserCtx) {
|
export async function save(ctx: UserCtx) {
|
||||||
const inputs = ctx.request.body
|
const inputs = ctx.request.body
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
const validateResult = await utils.validate({
|
const validateResult = await sdk.rows.utils.validate({
|
||||||
row: inputs,
|
row: inputs,
|
||||||
tableId,
|
tableId,
|
||||||
})
|
})
|
||||||
|
@ -97,7 +84,9 @@ export async function save(ctx: UserCtx) {
|
||||||
const responseRow = response as { row: Row }
|
const responseRow = response as { row: Row }
|
||||||
const rowId = responseRow.row._id
|
const rowId = responseRow.row._id
|
||||||
if (rowId) {
|
if (rowId) {
|
||||||
const row = await getRow(tableId, rowId, { relationships: true })
|
const row = await sdk.rows.external.getRow(tableId, rowId, {
|
||||||
|
relationships: true,
|
||||||
|
})
|
||||||
return {
|
return {
|
||||||
...response,
|
...response,
|
||||||
row,
|
row,
|
||||||
|
@ -110,7 +99,7 @@ export async function save(ctx: UserCtx) {
|
||||||
export async function find(ctx: UserCtx) {
|
export async function find(ctx: UserCtx) {
|
||||||
const id = ctx.params.rowId
|
const id = ctx.params.rowId
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
return getRow(tableId, id)
|
return sdk.rows.external.getRow(tableId, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function destroy(ctx: UserCtx) {
|
export async function destroy(ctx: UserCtx) {
|
||||||
|
|
|
@ -9,6 +9,8 @@ import {
|
||||||
DeleteRow,
|
DeleteRow,
|
||||||
DeleteRows,
|
DeleteRows,
|
||||||
Row,
|
Row,
|
||||||
|
PatchRowRequest,
|
||||||
|
PatchRowResponse,
|
||||||
SearchResponse,
|
SearchResponse,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
SortType,
|
SortType,
|
||||||
|
@ -29,7 +31,9 @@ function pickApi(tableId: any) {
|
||||||
return internal
|
return internal
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function patch(ctx: any): Promise<any> {
|
export async function patch(
|
||||||
|
ctx: UserCtx<PatchRowRequest, PatchRowResponse>
|
||||||
|
): Promise<any> {
|
||||||
const appId = ctx.appId
|
const appId = ctx.appId
|
||||||
const tableId = utils.getTableId(ctx)
|
const tableId = utils.getTableId(ctx)
|
||||||
const body = ctx.request.body
|
const body = ctx.request.body
|
||||||
|
@ -38,7 +42,7 @@ export async function patch(ctx: any): Promise<any> {
|
||||||
return save(ctx)
|
return save(ctx)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { row, table } = await quotas.addQuery<any>(
|
const { row, table } = await quotas.addQuery(
|
||||||
() => pickApi(tableId).patch(ctx),
|
() => pickApi(tableId).patch(ctx),
|
||||||
{
|
{
|
||||||
datasourceId: tableId,
|
datasourceId: tableId,
|
||||||
|
@ -53,7 +57,7 @@ export async function patch(ctx: any): Promise<any> {
|
||||||
ctx.message = `${table.name} updated successfully.`
|
ctx.message = `${table.name} updated successfully.`
|
||||||
ctx.body = row
|
ctx.body = row
|
||||||
gridSocket?.emitRowUpdate(ctx, row)
|
gridSocket?.emitRowUpdate(ctx, row)
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
ctx.throw(400, err)
|
ctx.throw(400, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,6 +82,7 @@ export const save = async (ctx: any) => {
|
||||||
ctx.body = row || squashed
|
ctx.body = row || squashed
|
||||||
gridSocket?.emitRowUpdate(ctx, row || squashed)
|
gridSocket?.emitRowUpdate(ctx, row || squashed)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchView(ctx: any) {
|
export async function fetchView(ctx: any) {
|
||||||
const tableId = utils.getTableId(ctx)
|
const tableId = utils.getTableId(ctx)
|
||||||
const viewName = decodeURIComponent(ctx.params.viewName)
|
const viewName = decodeURIComponent(ctx.params.viewName)
|
||||||
|
@ -267,7 +272,7 @@ export async function searchView(ctx: Ctx<void, SearchResponse>) {
|
||||||
undefined
|
undefined
|
||||||
|
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.body = await quotas.addQuery(
|
const result = await quotas.addQuery(
|
||||||
() =>
|
() =>
|
||||||
sdk.rows.search({
|
sdk.rows.search({
|
||||||
tableId: view.tableId,
|
tableId: view.tableId,
|
||||||
|
@ -279,6 +284,9 @@ export async function searchView(ctx: Ctx<void, SearchResponse>) {
|
||||||
datasourceId: view.tableId,
|
datasourceId: view.tableId,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
result.rows.forEach(r => (r._viewId = view.id))
|
||||||
|
ctx.body = result
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function validate(ctx: Ctx) {
|
export async function validate(ctx: Ctx) {
|
||||||
|
@ -287,7 +295,7 @@ export async function validate(ctx: Ctx) {
|
||||||
if (isExternalTable(tableId)) {
|
if (isExternalTable(tableId)) {
|
||||||
ctx.body = { valid: true }
|
ctx.body = { valid: true }
|
||||||
} else {
|
} else {
|
||||||
ctx.body = await utils.validate({
|
ctx.body = await sdk.rows.utils.validate({
|
||||||
row: ctx.request.body,
|
row: ctx.request.body,
|
||||||
tableId,
|
tableId,
|
||||||
})
|
})
|
||||||
|
|
|
@ -15,19 +15,26 @@ import * as utils from "./utils"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { context, db as dbCore } from "@budibase/backend-core"
|
import { context, db as dbCore } from "@budibase/backend-core"
|
||||||
import { finaliseRow, updateRelatedFormula } from "./staticFormula"
|
import { finaliseRow, updateRelatedFormula } from "./staticFormula"
|
||||||
import { UserCtx, LinkDocumentValue, Row, Table } from "@budibase/types"
|
import {
|
||||||
|
UserCtx,
|
||||||
|
LinkDocumentValue,
|
||||||
|
Row,
|
||||||
|
Table,
|
||||||
|
PatchRowRequest,
|
||||||
|
PatchRowResponse,
|
||||||
|
} from "@budibase/types"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
|
|
||||||
export async function patch(ctx: UserCtx) {
|
export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
|
||||||
const inputs = ctx.request.body
|
const inputs = ctx.request.body
|
||||||
const tableId = inputs.tableId
|
const tableId = inputs.tableId
|
||||||
const isUserTable = tableId === InternalTables.USER_METADATA
|
const isUserTable = tableId === InternalTables.USER_METADATA
|
||||||
let oldRow
|
let oldRow
|
||||||
|
const dbTable = await sdk.tables.getTable(tableId)
|
||||||
try {
|
try {
|
||||||
let dbTable = await sdk.tables.getTable(tableId)
|
|
||||||
oldRow = await outputProcessing(
|
oldRow = await outputProcessing(
|
||||||
dbTable,
|
dbTable,
|
||||||
await utils.findRow(ctx, tableId, inputs._id)
|
await utils.findRow(ctx, tableId, inputs._id!)
|
||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (isUserTable) {
|
if (isUserTable) {
|
||||||
|
@ -40,7 +47,7 @@ export async function patch(ctx: UserCtx) {
|
||||||
throw "Row does not exist"
|
throw "Row does not exist"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let dbTable = await sdk.tables.getTable(tableId)
|
|
||||||
// need to build up full patch fields before coerce
|
// need to build up full patch fields before coerce
|
||||||
let combinedRow: any = cloneDeep(oldRow)
|
let combinedRow: any = cloneDeep(oldRow)
|
||||||
for (let key of Object.keys(inputs)) {
|
for (let key of Object.keys(inputs)) {
|
||||||
|
@ -53,7 +60,7 @@ export async function patch(ctx: UserCtx) {
|
||||||
|
|
||||||
// this returns the table and row incase they have been updated
|
// this returns the table and row incase they have been updated
|
||||||
let { table, row } = inputProcessing(ctx.user, tableClone, combinedRow)
|
let { table, row } = inputProcessing(ctx.user, tableClone, combinedRow)
|
||||||
const validateResult = await utils.validate({
|
const validateResult = await sdk.rows.utils.validate({
|
||||||
row,
|
row,
|
||||||
table,
|
table,
|
||||||
})
|
})
|
||||||
|
@ -74,7 +81,7 @@ export async function patch(ctx: UserCtx) {
|
||||||
|
|
||||||
if (isUserTable) {
|
if (isUserTable) {
|
||||||
// the row has been updated, need to put it into the ctx
|
// the row has been updated, need to put it into the ctx
|
||||||
ctx.request.body = row
|
ctx.request.body = row as any
|
||||||
await userController.updateMetadata(ctx)
|
await userController.updateMetadata(ctx)
|
||||||
return { row: ctx.body as Row, table }
|
return { row: ctx.body as Row, table }
|
||||||
}
|
}
|
||||||
|
@ -102,7 +109,7 @@ export async function save(ctx: UserCtx) {
|
||||||
|
|
||||||
let { table, row } = inputProcessing(ctx.user, tableClone, inputs)
|
let { table, row } = inputProcessing(ctx.user, tableClone, inputs)
|
||||||
|
|
||||||
const validateResult = await utils.validate({
|
const validateResult = await sdk.rows.utils.validate({
|
||||||
row,
|
row,
|
||||||
table,
|
table,
|
||||||
})
|
})
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { context } from "@budibase/backend-core"
|
||||||
import { Table, Row } from "@budibase/types"
|
import { Table, Row } from "@budibase/types"
|
||||||
import * as linkRows from "../../../db/linkedRows"
|
import * as linkRows from "../../../db/linkedRows"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import { isEqual } from "lodash"
|
import isEqual from "lodash/isEqual"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,7 +2,8 @@ import { FieldTypes, FormulaTypes } from "../../../constants"
|
||||||
import { clearColumns } from "./utils"
|
import { clearColumns } from "./utils"
|
||||||
import { doesContainStrings } from "@budibase/string-templates"
|
import { doesContainStrings } from "@budibase/string-templates"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { isEqual, uniq } from "lodash"
|
import isEqual from "lodash/isEqual"
|
||||||
|
import uniq from "lodash/uniq"
|
||||||
import { updateAllFormulasInTable } from "../row/staticFormula"
|
import { updateAllFormulasInTable } from "../row/staticFormula"
|
||||||
import { context } from "@budibase/backend-core"
|
import { context } from "@budibase/backend-core"
|
||||||
import { FieldSchema, Table } from "@budibase/types"
|
import { FieldSchema, Table } from "@budibase/types"
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
import { runStaticFormulaChecks } from "./bulkFormula"
|
import { runStaticFormulaChecks } from "./bulkFormula"
|
||||||
import { Table } from "@budibase/types"
|
import { Table } from "@budibase/types"
|
||||||
import { quotas } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
import { isEqual } from "lodash"
|
import isEqual from "lodash/isEqual"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { parse, isSchema, isRows } from "../../../utilities/schema"
|
import { parse, isSchema, isRows } from "../../../utilities/schema"
|
||||||
import { getRowParams, generateRowID, InternalTables } from "../../../db/utils"
|
import { getRowParams, generateRowID, InternalTables } from "../../../db/utils"
|
||||||
import { isEqual } from "lodash"
|
import isEqual from "lodash/isEqual"
|
||||||
import {
|
import {
|
||||||
AutoFieldSubTypes,
|
AutoFieldSubTypes,
|
||||||
FieldTypes,
|
FieldTypes,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { generateUserMetadataID, generateUserFlagID } from "../../db/utils"
|
import { generateUserFlagID } from "../../db/utils"
|
||||||
import { InternalTables } from "../../db/utils"
|
import { InternalTables } from "../../db/utils"
|
||||||
import { getGlobalUsers } from "../../utilities/global"
|
|
||||||
import { getFullUser } from "../../utilities/users"
|
import { getFullUser } from "../../utilities/users"
|
||||||
import { context } from "@budibase/backend-core"
|
import { context } from "@budibase/backend-core"
|
||||||
import { Ctx, UserCtx } from "@budibase/types"
|
import { Ctx, UserCtx } from "@budibase/types"
|
||||||
|
|
|
@ -17,7 +17,8 @@ import {
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { builderSocket } from "../../../websockets"
|
import { builderSocket } from "../../../websockets"
|
||||||
|
|
||||||
const { cloneDeep, isEqual } = require("lodash")
|
const cloneDeep = require("lodash/cloneDeep")
|
||||||
|
import isEqual from "lodash/isEqual"
|
||||||
|
|
||||||
export async function fetch(ctx: Ctx) {
|
export async function fetch(ctx: Ctx) {
|
||||||
ctx.body = await getViews()
|
ctx.body = await getViews()
|
||||||
|
|
|
@ -5,7 +5,7 @@ tk.freeze(timestamp)
|
||||||
import { outputProcessing } from "../../../utilities/rowProcessor"
|
import { outputProcessing } from "../../../utilities/rowProcessor"
|
||||||
import * as setup from "./utilities"
|
import * as setup from "./utilities"
|
||||||
const { basicRow } = setup.structures
|
const { basicRow } = setup.structures
|
||||||
import { context, db, tenancy } from "@budibase/backend-core"
|
import { context, tenancy } from "@budibase/backend-core"
|
||||||
import { quotas } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
import {
|
import {
|
||||||
QuotaUsageType,
|
QuotaUsageType,
|
||||||
|
@ -16,6 +16,7 @@ import {
|
||||||
FieldType,
|
FieldType,
|
||||||
SortType,
|
SortType,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
|
PatchRowRequest,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import {
|
import {
|
||||||
expectAnyInternalColsAttributes,
|
expectAnyInternalColsAttributes,
|
||||||
|
@ -399,17 +400,12 @@ describe("/rows", () => {
|
||||||
const rowUsage = await getRowUsage()
|
const rowUsage = await getRowUsage()
|
||||||
const queryUsage = await getQueryUsage()
|
const queryUsage = await getQueryUsage()
|
||||||
|
|
||||||
const res = await request
|
const res = await config.api.row.patch(table._id!, {
|
||||||
.patch(`/api/${table._id}/rows`)
|
_id: existing._id!,
|
||||||
.send({
|
_rev: existing._rev!,
|
||||||
_id: existing._id,
|
tableId: table._id!,
|
||||||
_rev: existing._rev,
|
name: "Updated Name",
|
||||||
tableId: table._id,
|
})
|
||||||
name: "Updated Name",
|
|
||||||
})
|
|
||||||
.set(config.defaultHeaders())
|
|
||||||
.expect("Content-Type", /json/)
|
|
||||||
.expect(200)
|
|
||||||
|
|
||||||
expect((res as any).res.statusMessage).toEqual(
|
expect((res as any).res.statusMessage).toEqual(
|
||||||
`${table.name} updated successfully.`
|
`${table.name} updated successfully.`
|
||||||
|
@ -430,16 +426,16 @@ describe("/rows", () => {
|
||||||
const rowUsage = await getRowUsage()
|
const rowUsage = await getRowUsage()
|
||||||
const queryUsage = await getQueryUsage()
|
const queryUsage = await getQueryUsage()
|
||||||
|
|
||||||
await request
|
await config.api.row.patch(
|
||||||
.patch(`/api/${table._id}/rows`)
|
table._id!,
|
||||||
.send({
|
{
|
||||||
_id: existing._id,
|
_id: existing._id!,
|
||||||
_rev: existing._rev,
|
_rev: existing._rev!,
|
||||||
tableId: table._id,
|
tableId: table._id!,
|
||||||
name: 1,
|
name: 1,
|
||||||
})
|
},
|
||||||
.set(config.defaultHeaders())
|
{ expectStatus: 400 }
|
||||||
.expect(400)
|
)
|
||||||
|
|
||||||
await assertRowUsage(rowUsage)
|
await assertRowUsage(rowUsage)
|
||||||
await assertQueryUsage(queryUsage)
|
await assertQueryUsage(queryUsage)
|
||||||
|
@ -986,16 +982,17 @@ describe("/rows", () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const createViewResponse = await config.api.viewV2.create({
|
const view = await config.api.viewV2.create({
|
||||||
columns: { name: { visible: true } },
|
columns: { name: { visible: true } },
|
||||||
})
|
})
|
||||||
const response = await config.api.viewV2.search(createViewResponse.id)
|
const response = await config.api.viewV2.search(view.id)
|
||||||
|
|
||||||
expect(response.body.rows).toHaveLength(10)
|
expect(response.body.rows).toHaveLength(10)
|
||||||
expect(response.body.rows).toEqual(
|
expect(response.body.rows).toEqual(
|
||||||
expect.arrayContaining(
|
expect.arrayContaining(
|
||||||
rows.map(r => ({
|
rows.map(r => ({
|
||||||
...expectAnyInternalColsAttributes,
|
...expectAnyInternalColsAttributes,
|
||||||
|
_viewId: view.id,
|
||||||
name: r.name,
|
name: r.name,
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,6 +4,7 @@ jest.mock("../../utilities/redis", () => ({
|
||||||
checkTestFlag: () => {
|
checkTestFlag: () => {
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
|
shutdown: jest.fn(),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
jest.spyOn(global.console, "error")
|
jest.spyOn(global.console, "error")
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { merge } from "lodash"
|
import merge from "lodash/merge"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
|
|
||||||
export const AWS_REGION = env.AWS_REGION ? env.AWS_REGION : "eu-west-1"
|
export const AWS_REGION = env.AWS_REGION ? env.AWS_REGION : "eu-west-1"
|
||||||
|
|
|
@ -8,10 +8,10 @@ import {
|
||||||
getLinkedTableIDs,
|
getLinkedTableIDs,
|
||||||
getLinkedTable,
|
getLinkedTable,
|
||||||
} from "./linkUtils"
|
} from "./linkUtils"
|
||||||
import { flatten } from "lodash"
|
import flatten from "lodash/flatten"
|
||||||
import { FieldTypes } from "../../constants"
|
import { FieldTypes } from "../../constants"
|
||||||
import { getMultiIDParams, USER_METDATA_PREFIX } from "../utils"
|
import { getMultiIDParams, USER_METDATA_PREFIX } from "../utils"
|
||||||
import { partition } from "lodash"
|
import partition from "lodash/partition"
|
||||||
import { getGlobalUsersFromMetadata } from "../../utilities/global"
|
import { getGlobalUsersFromMetadata } from "../../utilities/global"
|
||||||
import { processFormulas } from "../../utilities/rowProcessor"
|
import { processFormulas } from "../../utilities/rowProcessor"
|
||||||
import { context } from "@budibase/backend-core"
|
import { context } from "@budibase/backend-core"
|
||||||
|
|
|
@ -17,7 +17,7 @@ import oracle from "./oracle"
|
||||||
import { SourceName, Integration, PluginType } from "@budibase/types"
|
import { SourceName, Integration, PluginType } from "@budibase/types"
|
||||||
import { getDatasourcePlugin } from "../utilities/fileSystem"
|
import { getDatasourcePlugin } from "../utilities/fileSystem"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import { cloneDeep } from "lodash"
|
import cloneDeep from "lodash/cloneDeep"
|
||||||
import sdk from "../sdk"
|
import sdk from "../sdk"
|
||||||
|
|
||||||
const DEFINITIONS: Record<SourceName, Integration | undefined> = {
|
const DEFINITIONS: Record<SourceName, Integration | undefined> = {
|
||||||
|
@ -54,7 +54,6 @@ const INTEGRATIONS: Record<SourceName, any> = {
|
||||||
[SourceName.FIRESTORE]: firebase.integration,
|
[SourceName.FIRESTORE]: firebase.integration,
|
||||||
[SourceName.GOOGLE_SHEETS]: googlesheets.integration,
|
[SourceName.GOOGLE_SHEETS]: googlesheets.integration,
|
||||||
[SourceName.REDIS]: redis.integration,
|
[SourceName.REDIS]: redis.integration,
|
||||||
[SourceName.FIRESTORE]: firebase.integration,
|
|
||||||
[SourceName.SNOWFLAKE]: snowflake.integration,
|
[SourceName.SNOWFLAKE]: snowflake.integration,
|
||||||
[SourceName.ORACLE]: undefined,
|
[SourceName.ORACLE]: undefined,
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
RestBasicAuthConfig,
|
RestBasicAuthConfig,
|
||||||
RestBearerAuthConfig,
|
RestBearerAuthConfig,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { get } from "lodash"
|
import get from "lodash/get"
|
||||||
import * as https from "https"
|
import * as https from "https"
|
||||||
import qs from "querystring"
|
import qs from "querystring"
|
||||||
import fetch from "node-fetch"
|
import fetch from "node-fetch"
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { getEnvironmentVariables } from "../../utils"
|
import { getEnvironmentVariables } from "../../utils"
|
||||||
import { getDefinitions, getDefinition } from "../../../integrations"
|
import { getDefinitions, getDefinition } from "../../../integrations"
|
||||||
import _ from "lodash"
|
import merge from "lodash/merge"
|
||||||
import {
|
import {
|
||||||
BudibaseInternalDB,
|
BudibaseInternalDB,
|
||||||
getDatasourceParams,
|
getDatasourceParams,
|
||||||
|
@ -227,7 +227,7 @@ export function mergeConfigs(update: Datasource, old: Datasource) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (old.config?.auth) {
|
if (old.config?.auth) {
|
||||||
update.config = _.merge(old.config, update.config)
|
update.config = merge(old.config, update.config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// update back to actual passwords for everything else
|
// update back to actual passwords for everything else
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { IncludeRelationship, Operation, Row } from "@budibase/types"
|
||||||
|
import { handleRequest } from "../../../api/controllers/row/external"
|
||||||
|
import { breakRowIdField } from "../../../integrations/utils"
|
||||||
|
|
||||||
|
export async function getRow(
|
||||||
|
tableId: string,
|
||||||
|
rowId: string,
|
||||||
|
opts?: { relationships?: boolean }
|
||||||
|
) {
|
||||||
|
const response = (await handleRequest(Operation.READ, tableId, {
|
||||||
|
id: breakRowIdField(rowId),
|
||||||
|
includeSqlRelationships: opts?.relationships
|
||||||
|
? IncludeRelationship.INCLUDE
|
||||||
|
: IncludeRelationship.EXCLUDE,
|
||||||
|
})) as Row[]
|
||||||
|
return response ? response[0] : response
|
||||||
|
}
|
|
@ -2,10 +2,12 @@ import * as attachments from "./attachments"
|
||||||
import * as rows from "./rows"
|
import * as rows from "./rows"
|
||||||
import * as search from "./search"
|
import * as search from "./search"
|
||||||
import * as utils from "./utils"
|
import * as utils from "./utils"
|
||||||
|
import * as external from "./external"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
...attachments,
|
...attachments,
|
||||||
...rows,
|
...rows,
|
||||||
...search,
|
...search,
|
||||||
utils: utils,
|
utils,
|
||||||
|
external,
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,8 +147,8 @@ export async function exportRows(
|
||||||
export async function fetch(tableId: string) {
|
export async function fetch(tableId: string) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
|
|
||||||
let table = await sdk.tables.getTable(tableId)
|
const table = await sdk.tables.getTable(tableId)
|
||||||
let rows = await getRawTableData(db, tableId)
|
const rows = await getRawTableData(db, tableId)
|
||||||
const result = await outputProcessing(table, rows)
|
const result = await outputProcessing(table, rows)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@ -171,7 +171,7 @@ async function getRawTableData(db: Database, tableId: string) {
|
||||||
export async function fetchView(
|
export async function fetchView(
|
||||||
viewName: string,
|
viewName: string,
|
||||||
options: { calculation: string; group: string; field: string }
|
options: { calculation: string; group: string; field: string }
|
||||||
) {
|
): Promise<Row[]> {
|
||||||
// if this is a table view being looked for just transfer to that
|
// if this is a table view being looked for just transfer to that
|
||||||
if (viewName.startsWith(DocumentType.TABLE)) {
|
if (viewName.startsWith(DocumentType.TABLE)) {
|
||||||
return fetch(viewName)
|
return fetch(viewName)
|
||||||
|
@ -197,7 +197,7 @@ export async function fetchView(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let rows
|
let rows: Row[] = []
|
||||||
if (!calculation) {
|
if (!calculation) {
|
||||||
response.rows = response.rows.map(row => row.doc)
|
response.rows = response.rows.map(row => row.doc)
|
||||||
let table: Table
|
let table: Table
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import { TableSchema } from "@budibase/types"
|
import cloneDeep from "lodash/cloneDeep"
|
||||||
|
import validateJs from "validate.js"
|
||||||
|
import { FieldType, Row, Table, TableSchema } from "@budibase/types"
|
||||||
import { FieldTypes } from "../../../constants"
|
import { FieldTypes } from "../../../constants"
|
||||||
import { makeExternalQuery } from "../../../integrations/base/query"
|
import { makeExternalQuery } from "../../../integrations/base/query"
|
||||||
import { Format } from "../../../api/controllers/view/exporters"
|
import { Format } from "../../../api/controllers/view/exporters"
|
||||||
|
@ -46,3 +48,90 @@ export function cleanExportRows(
|
||||||
|
|
||||||
return cleanRows
|
return cleanRows
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isForeignKey(key: string, table: Table) {
|
||||||
|
const relationships = Object.values(table.schema).filter(
|
||||||
|
column => column.type === FieldType.LINK
|
||||||
|
)
|
||||||
|
return relationships.some(relationship => relationship.foreignKey === key)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validate({
|
||||||
|
tableId,
|
||||||
|
row,
|
||||||
|
table,
|
||||||
|
}: {
|
||||||
|
tableId?: string
|
||||||
|
row: Row
|
||||||
|
table?: Table
|
||||||
|
}): Promise<{
|
||||||
|
valid: boolean
|
||||||
|
errors: Record<string, any>
|
||||||
|
}> {
|
||||||
|
let fetchedTable: Table
|
||||||
|
if (!table) {
|
||||||
|
fetchedTable = await sdk.tables.getTable(tableId)
|
||||||
|
} else {
|
||||||
|
fetchedTable = table
|
||||||
|
}
|
||||||
|
const errors: Record<string, any> = {}
|
||||||
|
for (let fieldName of Object.keys(fetchedTable.schema)) {
|
||||||
|
const column = fetchedTable.schema[fieldName]
|
||||||
|
const constraints = cloneDeep(column.constraints)
|
||||||
|
const type = column.type
|
||||||
|
// foreign keys are likely to be enriched
|
||||||
|
if (isForeignKey(fieldName, fetchedTable)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// formulas shouldn't validated, data will be deleted anyway
|
||||||
|
if (type === FieldTypes.FORMULA || column.autocolumn) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// special case for options, need to always allow unselected (empty)
|
||||||
|
if (type === FieldTypes.OPTIONS && constraints?.inclusion) {
|
||||||
|
constraints.inclusion.push(null as any, "")
|
||||||
|
}
|
||||||
|
let res
|
||||||
|
|
||||||
|
// Validate.js doesn't seem to handle array
|
||||||
|
if (type === FieldTypes.ARRAY && row[fieldName]) {
|
||||||
|
if (row[fieldName].length) {
|
||||||
|
if (!Array.isArray(row[fieldName])) {
|
||||||
|
row[fieldName] = row[fieldName].split(",")
|
||||||
|
}
|
||||||
|
row[fieldName].map((val: any) => {
|
||||||
|
if (
|
||||||
|
!constraints?.inclusion?.includes(val) &&
|
||||||
|
constraints?.inclusion?.length !== 0
|
||||||
|
) {
|
||||||
|
errors[fieldName] = "Field not in list"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (constraints?.presence && row[fieldName].length === 0) {
|
||||||
|
// non required MultiSelect creates an empty array, which should not throw errors
|
||||||
|
errors[fieldName] = [`${fieldName} is required`]
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
(type === FieldTypes.ATTACHMENT || type === FieldTypes.JSON) &&
|
||||||
|
typeof row[fieldName] === "string"
|
||||||
|
) {
|
||||||
|
// this should only happen if there is an error
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(row[fieldName])
|
||||||
|
if (type === FieldTypes.ATTACHMENT) {
|
||||||
|
if (Array.isArray(json)) {
|
||||||
|
row[fieldName] = json
|
||||||
|
} else {
|
||||||
|
errors[fieldName] = [`Must be an array`]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
errors[fieldName] = [`Contains invalid JSON`]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res = validateJs.single(row[fieldName], constraints)
|
||||||
|
}
|
||||||
|
if (res) errors[fieldName] = res
|
||||||
|
}
|
||||||
|
return { valid: Object.keys(errors).length === 0, errors }
|
||||||
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ import {
|
||||||
import datasources from "../datasources"
|
import datasources from "../datasources"
|
||||||
import { populateExternalTableSchemas, isEditableColumn } from "./validation"
|
import { populateExternalTableSchemas, isEditableColumn } from "./validation"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import _ from "lodash"
|
|
||||||
|
|
||||||
async function getAllInternalTables(db?: Database): Promise<Table[]> {
|
async function getAllInternalTables(db?: Database): Promise<Table[]> {
|
||||||
if (!db) {
|
if (!db) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { TableSchema, UIFieldMetadata, View, ViewV2 } from "@budibase/types"
|
||||||
|
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import * as utils from "../../../db/utils"
|
import * as utils from "../../../db/utils"
|
||||||
import _ from "lodash"
|
import merge from "lodash/merge"
|
||||||
|
|
||||||
export async function get(viewId: string): Promise<ViewV2 | undefined> {
|
export async function get(viewId: string): Promise<ViewV2 | undefined> {
|
||||||
const { tableId } = utils.extractViewInfoFromID(viewId)
|
const { tableId } = utils.extractViewInfoFromID(viewId)
|
||||||
|
@ -103,7 +103,7 @@ function enrichViewV2Schema(
|
||||||
delete tableFieldSchema.order
|
delete tableFieldSchema.order
|
||||||
}
|
}
|
||||||
|
|
||||||
result[columnName] = _.merge(tableFieldSchema, columnUIMetadata)
|
result[columnName] = merge(tableFieldSchema, columnUIMetadata)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
getUserMetadataParams,
|
getUserMetadataParams,
|
||||||
InternalTables,
|
InternalTables,
|
||||||
} from "../../db/utils"
|
} from "../../db/utils"
|
||||||
import { isEqual } from "lodash"
|
import isEqual from "lodash/isEqual"
|
||||||
import { ContextUser, UserMetadata, User, Database } from "@budibase/types"
|
import { ContextUser, UserMetadata, User, Database } from "@budibase/types"
|
||||||
|
|
||||||
export function combineMetadataAndUser(
|
export function combineMetadataAndUser(
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
import TestConfiguration from "../TestConfiguration"
|
import TestConfiguration from "../TestConfiguration"
|
||||||
|
import { RowAPI } from "./row"
|
||||||
import { TableAPI } from "./table"
|
import { TableAPI } from "./table"
|
||||||
import { ViewV2API } from "./viewV2"
|
import { ViewV2API } from "./viewV2"
|
||||||
|
|
||||||
export default class API {
|
export default class API {
|
||||||
table: TableAPI
|
table: TableAPI
|
||||||
viewV2: ViewV2API
|
viewV2: ViewV2API
|
||||||
|
row: RowAPI
|
||||||
|
|
||||||
constructor(config: TestConfiguration) {
|
constructor(config: TestConfiguration) {
|
||||||
this.table = new TableAPI(config)
|
this.table = new TableAPI(config)
|
||||||
this.viewV2 = new ViewV2API(config)
|
this.viewV2 = new ViewV2API(config)
|
||||||
|
this.row = new RowAPI(config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { PatchRowRequest } from "@budibase/types"
|
||||||
|
import TestConfiguration from "../TestConfiguration"
|
||||||
|
import { TestAPI } from "./base"
|
||||||
|
|
||||||
|
export class RowAPI extends TestAPI {
|
||||||
|
constructor(config: TestConfiguration) {
|
||||||
|
super(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
patch = async (
|
||||||
|
tableId: string,
|
||||||
|
row: PatchRowRequest,
|
||||||
|
{ expectStatus } = { expectStatus: 200 }
|
||||||
|
) => {
|
||||||
|
return this.request
|
||||||
|
.patch(`/api/${tableId}/rows`)
|
||||||
|
.send(row)
|
||||||
|
.set(this.config.defaultHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(expectStatus)
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ import {
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import { groups } from "@budibase/pro"
|
import { groups } from "@budibase/pro"
|
||||||
import { UserCtx, ContextUser, User, UserGroup } from "@budibase/types"
|
import { UserCtx, ContextUser, User, UserGroup } from "@budibase/types"
|
||||||
import { cloneDeep } from "lodash"
|
import cloneDeep from "lodash/cloneDeep"
|
||||||
|
|
||||||
export function updateAppRole(
|
export function updateAppRole(
|
||||||
user: ContextUser,
|
user: ContextUser,
|
||||||
|
|
|
@ -186,18 +186,21 @@ export function inputProcessing(
|
||||||
* @param {object} opts used to set some options for the output, such as disabling relationship squashing.
|
* @param {object} opts used to set some options for the output, such as disabling relationship squashing.
|
||||||
* @returns {object[]|object} the enriched rows will be returned.
|
* @returns {object[]|object} the enriched rows will be returned.
|
||||||
*/
|
*/
|
||||||
export async function outputProcessing(
|
export async function outputProcessing<T extends Row[] | Row>(
|
||||||
table: Table,
|
table: Table,
|
||||||
rows: Row[] | Row,
|
rows: T,
|
||||||
opts = { squash: true }
|
opts = { squash: true }
|
||||||
) {
|
): Promise<T> {
|
||||||
|
let safeRows: Row[]
|
||||||
let wasArray = true
|
let wasArray = true
|
||||||
if (!(rows instanceof Array)) {
|
if (!(rows instanceof Array)) {
|
||||||
rows = [rows]
|
safeRows = [rows]
|
||||||
wasArray = false
|
wasArray = false
|
||||||
|
} else {
|
||||||
|
safeRows = rows
|
||||||
}
|
}
|
||||||
// attach any linked row information
|
// attach any linked row information
|
||||||
let enriched = await linkRows.attachFullLinkedDocs(table, rows as Row[])
|
let enriched = await linkRows.attachFullLinkedDocs(table, safeRows)
|
||||||
|
|
||||||
// process formulas
|
// process formulas
|
||||||
enriched = processFormulas(table, enriched, { dynamic: true }) as Row[]
|
enriched = processFormulas(table, enriched, { dynamic: true }) as Row[]
|
||||||
|
@ -221,7 +224,7 @@ export async function outputProcessing(
|
||||||
enriched
|
enriched
|
||||||
)) as Row[]
|
)) as Row[]
|
||||||
}
|
}
|
||||||
return wasArray ? enriched : enriched[0]
|
return (wasArray ? enriched : enriched[0]) as T
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -36,8 +36,8 @@
|
||||||
"@rollup/plugin-commonjs": "^17.1.0",
|
"@rollup/plugin-commonjs": "^17.1.0",
|
||||||
"@rollup/plugin-json": "^4.1.0",
|
"@rollup/plugin-json": "^4.1.0",
|
||||||
"doctrine": "^3.0.0",
|
"doctrine": "^3.0.0",
|
||||||
"jest": "^26.6.3",
|
"jest": "29.6.2",
|
||||||
"jest-environment-node": "^26.6.2",
|
"jest-environment-node": "29.6.2",
|
||||||
"marked": "^4.0.10",
|
"marked": "^4.0.10",
|
||||||
"rollup": "^2.36.2",
|
"rollup": "^2.36.2",
|
||||||
"rollup-plugin-inject-process-env": "^1.3.1",
|
"rollup-plugin-inject-process-env": "^1.3.1",
|
||||||
|
|
|
@ -1,3 +1,13 @@
|
||||||
|
import { Row } from "../../../documents"
|
||||||
|
|
||||||
|
export interface PatchRowRequest extends Row {
|
||||||
|
_id: string
|
||||||
|
_rev: string
|
||||||
|
tableId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PatchRowResponse extends Row {}
|
||||||
|
|
||||||
export interface SearchResponse {
|
export interface SearchResponse {
|
||||||
rows: any[]
|
rows: any[]
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,5 +30,6 @@ export interface RowAttachment {
|
||||||
export interface Row extends Document {
|
export interface Row extends Document {
|
||||||
type?: string
|
type?: string
|
||||||
tableId?: string
|
tableId?: string
|
||||||
|
_viewId?: string
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import { Config } from "@jest/types"
|
import { Config } from "@jest/types"
|
||||||
import * as fs from "fs"
|
import * as fs from "fs"
|
||||||
const preset = require("ts-jest/jest-preset")
|
|
||||||
|
|
||||||
const config: Config.InitialOptions = {
|
const config: Config.InitialOptions = {
|
||||||
...preset,
|
|
||||||
preset: "@trendyol/jest-testcontainers",
|
preset: "@trendyol/jest-testcontainers",
|
||||||
setupFiles: ["./src/tests/jestEnv.ts"],
|
setupFiles: ["./src/tests/jestEnv.ts"],
|
||||||
setupFilesAfterEnv: ["./src/tests/jestSetup.ts"],
|
setupFilesAfterEnv: ["./src/tests/jestSetup.ts"],
|
||||||
|
|
|
@ -74,10 +74,10 @@
|
||||||
"server-destroy": "1.0.1"
|
"server-destroy": "1.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@swc/core": "^1.3.25",
|
"@swc/core": "1.3.71",
|
||||||
"@swc/jest": "^0.2.24",
|
"@swc/jest": "0.2.27",
|
||||||
"@trendyol/jest-testcontainers": "^2.1.1",
|
"@trendyol/jest-testcontainers": "2.1.1",
|
||||||
"@types/jest": "28.1.1",
|
"@types/jest": "29.5.3",
|
||||||
"@types/jsonwebtoken": "8.5.1",
|
"@types/jsonwebtoken": "8.5.1",
|
||||||
"@types/koa": "2.13.4",
|
"@types/koa": "2.13.4",
|
||||||
"@types/koa__router": "8.0.8",
|
"@types/koa__router": "8.0.8",
|
||||||
|
@ -91,14 +91,13 @@
|
||||||
"@typescript-eslint/parser": "5.45.0",
|
"@typescript-eslint/parser": "5.45.0",
|
||||||
"copyfiles": "2.4.1",
|
"copyfiles": "2.4.1",
|
||||||
"eslint": "6.8.0",
|
"eslint": "6.8.0",
|
||||||
"jest": "28.1.1",
|
"jest": "29.6.2",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"nodemon": "2.0.15",
|
"nodemon": "2.0.15",
|
||||||
"pouchdb-adapter-memory": "7.2.2",
|
"pouchdb-adapter-memory": "7.2.2",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"supertest": "6.2.2",
|
"supertest": "6.2.2",
|
||||||
"timekeeper": "2.2.0",
|
"timekeeper": "2.2.0",
|
||||||
"ts-jest": "28.0.4",
|
|
||||||
"ts-node": "10.8.1",
|
"ts-node": "10.8.1",
|
||||||
"tsconfig-paths": "4.0.0",
|
"tsconfig-paths": "4.0.0",
|
||||||
"typescript": "4.7.3",
|
"typescript": "4.7.3",
|
||||||
|
|
|
@ -314,7 +314,7 @@ describe("scim", () => {
|
||||||
|
|
||||||
const user = await config.getUser(email)
|
const user = await config.getUser(email)
|
||||||
expect(user).toBeDefined()
|
expect(user).toBeDefined()
|
||||||
expect(user.email).toEqual(email)
|
expect(user!.email).toEqual(email)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("if multiple emails are provided, the first primary one is used as email", async () => {
|
it("if multiple emails are provided, the first primary one is used as email", async () => {
|
||||||
|
@ -345,7 +345,7 @@ describe("scim", () => {
|
||||||
|
|
||||||
const user = await config.getUser(email)
|
const user = await config.getUser(email)
|
||||||
expect(user).toBeDefined()
|
expect(user).toBeDefined()
|
||||||
expect(user.email).toEqual(email)
|
expect(user!.email).toEqual(email)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("if no email is provided and the user name is not an email, an exception is thrown", async () => {
|
it("if no email is provided and the user name is not an email, an exception is thrown", async () => {
|
||||||
|
|
|
@ -24,18 +24,18 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@budibase/types": "^2.3.17",
|
"@budibase/types": "^2.3.17",
|
||||||
"@trendyol/jest-testcontainers": "^2.1.1",
|
"@trendyol/jest-testcontainers": "2.1.1",
|
||||||
"@types/jest": "29.0.0",
|
"@types/jest": "29.5.3",
|
||||||
"@types/node-fetch": "2.6.2",
|
"@types/node-fetch": "2.6.2",
|
||||||
"chance": "1.1.8",
|
"chance": "1.1.8",
|
||||||
"dotenv": "16.0.1",
|
"dotenv": "16.0.1",
|
||||||
"jest": "29.0.0",
|
"jest": "29.6.2",
|
||||||
"prettier": "2.7.1",
|
"prettier": "2.7.1",
|
||||||
"start-server-and-test": "1.14.0",
|
"start-server-and-test": "1.14.0",
|
||||||
"@swc/core": "^1.3.25",
|
"@swc/core": "1.3.71",
|
||||||
"@swc/jest": "^0.2.24",
|
"@swc/jest": "0.2.27",
|
||||||
"timekeeper": "2.2.0",
|
"timekeeper": "2.2.0",
|
||||||
"ts-jest": "29.0.0",
|
"ts-jest": "29.1.1",
|
||||||
"ts-node": "10.8.1",
|
"ts-node": "10.8.1",
|
||||||
"tsconfig-paths": "4.0.0",
|
"tsconfig-paths": "4.0.0",
|
||||||
"typescript": "4.7.3"
|
"typescript": "4.7.3"
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue