Merge branch 'master' into labday/monorepo-setup
This commit is contained in:
commit
f6bbbd7b3f
|
@ -1,13 +0,0 @@
|
||||||
node_modules
|
|
||||||
public
|
|
||||||
dist
|
|
||||||
packages/server/builder
|
|
||||||
packages/server/coverage
|
|
||||||
packages/worker/coverage
|
|
||||||
packages/backend-core/coverage
|
|
||||||
packages/server/client
|
|
||||||
packages/server/coverage
|
|
||||||
packages/builder/.routify
|
|
||||||
packages/sdk/sdk
|
|
||||||
**/*.ivm.bundle.js
|
|
||||||
packages/server/build/oldClientVersions/**/**
|
|
129
.eslintrc.json
129
.eslintrc.json
|
@ -1,129 +0,0 @@
|
||||||
{
|
|
||||||
"root": true,
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"es6": true,
|
|
||||||
"jest": true,
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"parser": "@babel/eslint-parser",
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": 2019,
|
|
||||||
"sourceType": "module",
|
|
||||||
"allowImportExportEverywhere": true
|
|
||||||
},
|
|
||||||
"ignorePatterns": [
|
|
||||||
"node_modules",
|
|
||||||
"dist",
|
|
||||||
"public",
|
|
||||||
"*.spec.js",
|
|
||||||
"bundle.js"
|
|
||||||
],
|
|
||||||
"extends": ["eslint:recommended"],
|
|
||||||
"plugins": ["import", "eslint-plugin-local-rules"],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["**/*.svelte"],
|
|
||||||
"extends": "plugin:svelte/recommended",
|
|
||||||
"parser": "svelte-eslint-parser",
|
|
||||||
"parserOptions": {
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"ecmaVersion": 2019,
|
|
||||||
"allowImportExportEverywhere": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["**/*.ts"],
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"plugins": ["@typescript-eslint"],
|
|
||||||
"extends": ["eslint:recommended"],
|
|
||||||
"globals": {
|
|
||||||
"NodeJS": true
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"no-unused-vars": "off",
|
|
||||||
"local-rules/no-barrel-imports": "error",
|
|
||||||
"local-rules/no-budibase-imports": "error",
|
|
||||||
"local-rules/no-console-error": "error",
|
|
||||||
"@typescript-eslint/no-unused-vars": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"varsIgnorePattern": "^_",
|
|
||||||
"argsIgnorePattern": "^_",
|
|
||||||
"destructuredArrayIgnorePattern": "^_",
|
|
||||||
"ignoreRestSiblings": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"no-redeclare": "off",
|
|
||||||
"@typescript-eslint/no-redeclare": "error",
|
|
||||||
// have to turn this off to allow function overloading in typescript
|
|
||||||
"no-dupe-class-members": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["**/*.spec.ts"],
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"plugins": ["jest", "@typescript-eslint"],
|
|
||||||
"extends": ["eslint:recommended", "plugin:jest/recommended"],
|
|
||||||
"env": {
|
|
||||||
"jest/globals": true
|
|
||||||
},
|
|
||||||
"globals": {
|
|
||||||
"NodeJS": true
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"no-unused-vars": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"varsIgnorePattern": "^_",
|
|
||||||
"argsIgnorePattern": "^_",
|
|
||||||
"destructuredArrayIgnorePattern": "^_",
|
|
||||||
"ignoreRestSiblings": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"local-rules/no-test-com": "error",
|
|
||||||
"local-rules/email-domain-example-com": "error",
|
|
||||||
"no-console": "warn",
|
|
||||||
// We have a lot of tests that don't have assertions, they use our test
|
|
||||||
// API client that does the assertions for them
|
|
||||||
"jest/expect-expect": "off",
|
|
||||||
// We do this in some tests where the behaviour of internal tables
|
|
||||||
// differs to external, but the API is broadly the same
|
|
||||||
"jest/no-conditional-expect": "off",
|
|
||||||
// have to turn this off to allow function overloading in typescript
|
|
||||||
"no-dupe-class-members": "off",
|
|
||||||
"no-redeclare": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
"packages/builder/**/*",
|
|
||||||
"packages/client/**/*",
|
|
||||||
"packages/frontend-core/**/*"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"no-console": ["error", { "allow": ["warn", "error", "debug"] }]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"no-self-assign": "off",
|
|
||||||
"no-unused-vars": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"varsIgnorePattern": "^_",
|
|
||||||
"argsIgnorePattern": "^_",
|
|
||||||
"destructuredArrayIgnorePattern": "^_",
|
|
||||||
"ignoreRestSiblings": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"import/no-relative-packages": "error",
|
|
||||||
"import/export": "error",
|
|
||||||
"import/no-duplicates": "error",
|
|
||||||
"import/newline-after-import": "error"
|
|
||||||
},
|
|
||||||
"globals": {
|
|
||||||
"GeolocationPositionError": true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,197 @@
|
||||||
|
import globals from "globals"
|
||||||
|
import babelParser from "@babel/eslint-parser"
|
||||||
|
import svelteParser from "svelte-eslint-parser"
|
||||||
|
import tsParser from "@typescript-eslint/parser"
|
||||||
|
|
||||||
|
import eslintPluginJest from "eslint-plugin-jest"
|
||||||
|
import eslintPluginSvelte from "eslint-plugin-svelte"
|
||||||
|
import eslintPluginLocalRules from "eslint-plugin-local-rules"
|
||||||
|
import eslintPluginVitest from "@vitest/eslint-plugin"
|
||||||
|
|
||||||
|
import eslint from "@eslint/js"
|
||||||
|
import tseslint from "typescript-eslint"
|
||||||
|
|
||||||
|
export default [
|
||||||
|
eslint.configs.recommended,
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"**/node_modules",
|
||||||
|
"**/dist",
|
||||||
|
"**/public",
|
||||||
|
"**/bundle.js",
|
||||||
|
"**/coverage",
|
||||||
|
"packages/server/builder",
|
||||||
|
"packages/server/client",
|
||||||
|
"packages/builder/.routify",
|
||||||
|
"packages/sdk/sdk",
|
||||||
|
"**/*.ivm.bundle.js",
|
||||||
|
"packages/server/build/oldClientVersions/**/**/*",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
"local-rules": eslintPluginLocalRules,
|
||||||
|
},
|
||||||
|
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.jest,
|
||||||
|
...globals.node,
|
||||||
|
GeolocationPositionError: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
parser: babelParser,
|
||||||
|
ecmaVersion: 2019,
|
||||||
|
sourceType: "module",
|
||||||
|
|
||||||
|
parserOptions: {
|
||||||
|
allowImportExportEverywhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
"no-self-compare": "error",
|
||||||
|
"no-template-curly-in-string": "error",
|
||||||
|
"no-unmodified-loop-condition": "error",
|
||||||
|
"no-unreachable-loop": "error",
|
||||||
|
"no-implied-eval": "error",
|
||||||
|
"no-extend-native": "error",
|
||||||
|
"no-labels": "error",
|
||||||
|
"no-lone-blocks": "error",
|
||||||
|
"no-new-wrappers": "error",
|
||||||
|
"no-octal-escape": "error",
|
||||||
|
"no-return-assign": "error",
|
||||||
|
"no-useless-concat": "error",
|
||||||
|
"no-useless-constructor": "error",
|
||||||
|
"no-useless-rename": "error",
|
||||||
|
"no-var": "error",
|
||||||
|
"no-void": "error",
|
||||||
|
|
||||||
|
"no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
varsIgnorePattern: "^_",
|
||||||
|
argsIgnorePattern: "^_",
|
||||||
|
destructuredArrayIgnorePattern: "^_",
|
||||||
|
ignoreRestSiblings: true,
|
||||||
|
caughtErrors: "none",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...eslintPluginSvelte.configs["flat/recommended"].map(config => ({
|
||||||
|
...config,
|
||||||
|
files: ["**/*.svelte"],
|
||||||
|
|
||||||
|
languageOptions: {
|
||||||
|
parser: svelteParser,
|
||||||
|
ecmaVersion: 2019,
|
||||||
|
sourceType: "script",
|
||||||
|
|
||||||
|
parserOptions: {
|
||||||
|
parser: "@typescript-eslint/parser",
|
||||||
|
allowImportExportEverywhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
...tseslint.configs.strict.map(config => ({
|
||||||
|
...config,
|
||||||
|
files: ["**/*.ts"],
|
||||||
|
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
NodeJS: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
parser: tsParser,
|
||||||
|
},
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
"local-rules/no-barrel-imports": "error",
|
||||||
|
"local-rules/no-budibase-imports": "error",
|
||||||
|
"local-rules/no-console-error": "error",
|
||||||
|
|
||||||
|
"@typescript-eslint/no-inferrable-types": "error",
|
||||||
|
"@typescript-eslint/adjacent-overload-signatures": "error",
|
||||||
|
"@typescript-eslint/class-literal-property-style": "error",
|
||||||
|
"@typescript-eslint/no-confusing-non-null-assertion": "error",
|
||||||
|
"@typescript-eslint/no-unnecessary-parameter-property-assignment":
|
||||||
|
"error",
|
||||||
|
"@typescript-eslint/no-useless-empty-export": "error",
|
||||||
|
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
varsIgnorePattern: "^_",
|
||||||
|
argsIgnorePattern: "^_",
|
||||||
|
destructuredArrayIgnorePattern: "^_",
|
||||||
|
ignoreRestSiblings: true,
|
||||||
|
caughtErrors: "none",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
"no-redeclare": "off",
|
||||||
|
"@typescript-eslint/no-redeclare": "error",
|
||||||
|
|
||||||
|
// @typescript-eslint/no-dupe-class-members supersedes no-dupe-class-members
|
||||||
|
"no-dupe-class-members": "off",
|
||||||
|
"@typescript-eslint/no-dupe-class-members": "error",
|
||||||
|
|
||||||
|
"no-useless-constructor": "off",
|
||||||
|
"@typescript-eslint/no-useless-constructor": "error",
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
files: ["**/*.spec.ts", "**/*.spec.js"],
|
||||||
|
|
||||||
|
plugins: {
|
||||||
|
jest: eslintPluginJest,
|
||||||
|
vitest: eslintPluginVitest,
|
||||||
|
},
|
||||||
|
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...eslintPluginJest.environments.globals.globals,
|
||||||
|
...eslintPluginVitest.environments.env.globals,
|
||||||
|
NodeJS: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
parser: tsParser,
|
||||||
|
},
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
...eslintPluginVitest.configs.recommended.rules,
|
||||||
|
...eslintPluginJest.configs.recommended.rules,
|
||||||
|
|
||||||
|
"no-console": "warn",
|
||||||
|
|
||||||
|
"vitest/expect-expect": "off",
|
||||||
|
|
||||||
|
"jest/expect-expect": "off",
|
||||||
|
"jest/no-conditional-expect": "off",
|
||||||
|
"jest/no-disabled-tests": "off",
|
||||||
|
"jest/no-standalone-expect": "off",
|
||||||
|
|
||||||
|
"local-rules/no-test-com": "error",
|
||||||
|
"local-rules/email-domain-example-com": "error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
"packages/builder/**/*",
|
||||||
|
"packages/client/**/*",
|
||||||
|
"packages/frontend-core/**/*",
|
||||||
|
],
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
"no-console": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
allow: ["warn", "error", "debug"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
const os = require("os")
|
const os = require("os")
|
||||||
const exec = require("child_process").exec
|
const exec = require("child_process").exec
|
||||||
const fs = require("fs")
|
|
||||||
const platform = os.platform()
|
const platform = os.platform()
|
||||||
|
|
||||||
const windows = platform === "win32"
|
const windows = platform === "win32"
|
||||||
|
@ -17,10 +16,11 @@ function execute(command) {
|
||||||
|
|
||||||
async function commandExistsUnix(command) {
|
async function commandExistsUnix(command) {
|
||||||
const unixCmd = `command -v ${command} 2>/dev/null && { echo >&1 ${command}; exit 0; }`
|
const unixCmd = `command -v ${command} 2>/dev/null && { echo >&1 ${command}; exit 0; }`
|
||||||
return execute(command)
|
return execute(unixCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function commandExistsWindows(command) {
|
async function commandExistsWindows(command) {
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
if (/[\x00-\x1f<>:"|?*]/.test(command)) {
|
if (/[\x00-\x1f<>:"|?*]/.test(command)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
21
package.json
21
package.json
|
@ -3,21 +3,21 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.22.5",
|
"@babel/core": "^7.22.5",
|
||||||
"@babel/eslint-parser": "^7.22.5",
|
"@babel/eslint-parser": "7.25.9",
|
||||||
"@babel/preset-env": "^7.22.5",
|
"@babel/preset-env": "^7.22.5",
|
||||||
"@esbuild-plugins/tsconfig-paths": "^0.1.2",
|
"@esbuild-plugins/tsconfig-paths": "^0.1.2",
|
||||||
"@types/node": "^20.17.9",
|
"@types/node": "^20.17.9",
|
||||||
"@types/proper-lockfile": "^4.1.4",
|
"@types/proper-lockfile": "^4.1.4",
|
||||||
"@typescript-eslint/parser": "6.9.0",
|
"@typescript-eslint/parser": "8.17.0",
|
||||||
|
"@vitest/eslint-plugin": "^1.1.14",
|
||||||
"cross-spawn": "7.0.6",
|
"cross-spawn": "7.0.6",
|
||||||
"depcheck": "^1.4.7",
|
"depcheck": "^1.4.7",
|
||||||
"esbuild": "^0.18.17",
|
"esbuild": "^0.18.17",
|
||||||
"esbuild-node-externals": "^1.14.0",
|
"esbuild-node-externals": "^1.14.0",
|
||||||
"eslint": "^8.52.0",
|
"eslint": "9.16.0",
|
||||||
"eslint-plugin-import": "^2.29.0",
|
"eslint-plugin-jest": "28.9.0",
|
||||||
"eslint-plugin-jest": "^27.9.0",
|
"eslint-plugin-local-rules": "3.0.2",
|
||||||
"eslint-plugin-local-rules": "^2.0.0",
|
"eslint-plugin-svelte": "2.46.1",
|
||||||
"eslint-plugin-svelte": "^2.34.0",
|
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"kill-port": "^1.6.1",
|
"kill-port": "^1.6.1",
|
||||||
"lerna": "7.4.2",
|
"lerna": "7.4.2",
|
||||||
|
@ -28,9 +28,9 @@
|
||||||
"prettier-plugin-svelte": "^2.3.0",
|
"prettier-plugin-svelte": "^2.3.0",
|
||||||
"proper-lockfile": "^4.1.2",
|
"proper-lockfile": "^4.1.2",
|
||||||
"svelte": "4.2.19",
|
"svelte": "4.2.19",
|
||||||
"svelte-eslint-parser": "^0.33.1",
|
"svelte-eslint-parser": "0.43.0",
|
||||||
"typescript": "5.7.2",
|
"typescript": "5.7.2",
|
||||||
"typescript-eslint": "^7.3.1",
|
"typescript-eslint": "8.17.0",
|
||||||
"yargs": "^17.7.2"
|
"yargs": "^17.7.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -116,7 +116,8 @@
|
||||||
"passport": "0.6.0",
|
"passport": "0.6.0",
|
||||||
"fast-xml-parser": "4.4.1",
|
"fast-xml-parser": "4.4.1",
|
||||||
"@azure/identity": "4.2.1",
|
"@azure/identity": "4.2.1",
|
||||||
"kind-of": "6.0.3"
|
"kind-of": "6.0.3",
|
||||||
|
"globals": "15.13.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0 <21.0.0"
|
"node": ">=20.0.0 <21.0.0"
|
||||||
|
|
|
@ -289,7 +289,7 @@ export class DatabaseImpl implements Database {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let errorFound = false
|
let errorFound = false
|
||||||
let errorMessage: string = "Unable to bulk remove documents: "
|
let errorMessage = "Unable to bulk remove documents: "
|
||||||
for (let res of response) {
|
for (let res of response) {
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
errorFound = true
|
errorFound = true
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { checkSlashesInUrl } from "../../helpers"
|
||||||
|
|
||||||
export async function directCouchCall(
|
export async function directCouchCall(
|
||||||
path: string,
|
path: string,
|
||||||
method: string = "GET",
|
method = "GET",
|
||||||
body?: any
|
body?: any
|
||||||
) {
|
) {
|
||||||
let { url, cookie } = getCouchInfo()
|
let { url, cookie } = getCouchInfo()
|
||||||
|
@ -43,7 +43,7 @@ export async function directCouchUrlCall({
|
||||||
|
|
||||||
export async function directCouchQuery(
|
export async function directCouchQuery(
|
||||||
path: string,
|
path: string,
|
||||||
method: string = "GET",
|
method = "GET",
|
||||||
body?: any
|
body?: any
|
||||||
) {
|
) {
|
||||||
const response = await directCouchCall(path, method, body)
|
const response = await directCouchCall(path, method, body)
|
||||||
|
|
|
@ -279,7 +279,7 @@ export class QueryBuilder<T> {
|
||||||
let query = allOr ? "" : "*:*"
|
let query = allOr ? "" : "*:*"
|
||||||
let allFiltersEmpty = true
|
let allFiltersEmpty = true
|
||||||
const allPreProcessingOpts = { escape: true, lowercase: true, wrap: true }
|
const allPreProcessingOpts = { escape: true, lowercase: true, wrap: true }
|
||||||
let tableId: string = ""
|
let tableId = ""
|
||||||
if (this.#query.equal!.tableId) {
|
if (this.#query.equal!.tableId) {
|
||||||
tableId = this.#query.equal!.tableId
|
tableId = this.#query.equal!.tableId
|
||||||
delete this.#query.equal!.tableId
|
delete this.#query.equal!.tableId
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Event, Identity, Group } from "@budibase/types"
|
||||||
import { EventProcessor } from "./types"
|
import { EventProcessor } from "./types"
|
||||||
|
|
||||||
export default class Processor implements EventProcessor {
|
export default class Processor implements EventProcessor {
|
||||||
initialised: boolean = false
|
initialised = false
|
||||||
processors: EventProcessor[] = []
|
processors: EventProcessor[] = []
|
||||||
|
|
||||||
constructor(processors: EventProcessor[]) {
|
constructor(processors: EventProcessor[]) {
|
||||||
|
|
|
@ -13,9 +13,7 @@ const EXCLUDED_EVENTS: Event[] = [
|
||||||
Event.ROLE_UPDATED,
|
Event.ROLE_UPDATED,
|
||||||
Event.DATASOURCE_UPDATED,
|
Event.DATASOURCE_UPDATED,
|
||||||
Event.QUERY_UPDATED,
|
Event.QUERY_UPDATED,
|
||||||
Event.TABLE_UPDATED,
|
|
||||||
Event.VIEW_UPDATED,
|
Event.VIEW_UPDATED,
|
||||||
Event.VIEW_FILTER_UPDATED,
|
|
||||||
Event.VIEW_CALCULATION_UPDATED,
|
Event.VIEW_CALCULATION_UPDATED,
|
||||||
Event.AUTOMATION_TRIGGER_UPDATED,
|
Event.AUTOMATION_TRIGGER_UPDATED,
|
||||||
Event.USER_GROUP_UPDATED,
|
Event.USER_GROUP_UPDATED,
|
||||||
|
|
|
@ -23,3 +23,4 @@ export { default as plugin } from "./plugin"
|
||||||
export { default as backup } from "./backup"
|
export { default as backup } from "./backup"
|
||||||
export { default as environmentVariable } from "./environmentVariable"
|
export { default as environmentVariable } from "./environmentVariable"
|
||||||
export { default as auditLog } from "./auditLog"
|
export { default as auditLog } from "./auditLog"
|
||||||
|
export { default as rowAction } from "./rowAction"
|
||||||
|
|
|
@ -12,8 +12,6 @@ import {
|
||||||
QueriesRunEvent,
|
QueriesRunEvent,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
const created = async (
|
const created = async (
|
||||||
datasource: Datasource,
|
datasource: Datasource,
|
||||||
query: Query,
|
query: Query,
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { publishEvent } from "../events"
|
||||||
|
import { Event, RowActionCreatedEvent } from "@budibase/types"
|
||||||
|
|
||||||
|
async function created(
|
||||||
|
rowAction: RowActionCreatedEvent,
|
||||||
|
timestamp?: string | number
|
||||||
|
) {
|
||||||
|
await publishEvent(Event.ROW_ACTION_CREATED, rowAction, timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
created,
|
||||||
|
}
|
|
@ -6,8 +6,6 @@ import {
|
||||||
Table,
|
Table,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
const created = async (count: number, timestamp?: string | number) => {
|
const created = async (count: number, timestamp?: string | number) => {
|
||||||
const properties: RowsCreatedEvent = {
|
const properties: RowsCreatedEvent = {
|
||||||
count,
|
count,
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { publishEvent } from "../events"
|
import { publishEvent } from "../events"
|
||||||
import {
|
import {
|
||||||
Event,
|
Event,
|
||||||
TableExportFormat,
|
FieldType,
|
||||||
Table,
|
Table,
|
||||||
TableCreatedEvent,
|
TableCreatedEvent,
|
||||||
TableUpdatedEvent,
|
|
||||||
TableDeletedEvent,
|
TableDeletedEvent,
|
||||||
TableExportedEvent,
|
TableExportedEvent,
|
||||||
|
TableExportFormat,
|
||||||
TableImportedEvent,
|
TableImportedEvent,
|
||||||
|
TableUpdatedEvent,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
async function created(table: Table, timestamp?: string | number) {
|
async function created(table: Table, timestamp?: string | number) {
|
||||||
|
@ -20,14 +21,34 @@ async function created(table: Table, timestamp?: string | number) {
|
||||||
await publishEvent(Event.TABLE_CREATED, properties, timestamp)
|
await publishEvent(Event.TABLE_CREATED, properties, timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updated(table: Table) {
|
async function updated(oldTable: Table, newTable: Table) {
|
||||||
|
// only publish the event if it has fields we are interested in
|
||||||
|
let defaultValues, aiColumn
|
||||||
|
|
||||||
|
// check that new fields have been added
|
||||||
|
for (const key in newTable.schema) {
|
||||||
|
if (!oldTable.schema[key]) {
|
||||||
|
const newColumn = newTable.schema[key]
|
||||||
|
if ("default" in newColumn && newColumn.default != null) {
|
||||||
|
defaultValues = true
|
||||||
|
}
|
||||||
|
if (newColumn.type === FieldType.AI) {
|
||||||
|
aiColumn = newColumn.operation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const properties: TableUpdatedEvent = {
|
const properties: TableUpdatedEvent = {
|
||||||
tableId: table._id as string,
|
tableId: newTable._id as string,
|
||||||
|
defaultValues,
|
||||||
|
aiColumn,
|
||||||
audited: {
|
audited: {
|
||||||
name: table.name,
|
name: newTable.name,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
await publishEvent(Event.TABLE_UPDATED, properties)
|
if (defaultValues || aiColumn) {
|
||||||
|
await publishEvent(Event.TABLE_UPDATED, properties)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleted(table: Table) {
|
async function deleted(table: Table) {
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
import { publishEvent } from "../events"
|
import { publishEvent } from "../events"
|
||||||
import {
|
import {
|
||||||
|
CalculationType,
|
||||||
Event,
|
Event,
|
||||||
|
Table,
|
||||||
|
TableExportFormat,
|
||||||
|
View,
|
||||||
|
ViewCalculation,
|
||||||
ViewCalculationCreatedEvent,
|
ViewCalculationCreatedEvent,
|
||||||
ViewCalculationDeletedEvent,
|
ViewCalculationDeletedEvent,
|
||||||
ViewCalculationUpdatedEvent,
|
ViewCalculationUpdatedEvent,
|
||||||
|
@ -11,22 +16,20 @@ import {
|
||||||
ViewFilterDeletedEvent,
|
ViewFilterDeletedEvent,
|
||||||
ViewFilterUpdatedEvent,
|
ViewFilterUpdatedEvent,
|
||||||
ViewUpdatedEvent,
|
ViewUpdatedEvent,
|
||||||
View,
|
ViewV2,
|
||||||
ViewCalculation,
|
ViewJoinCreatedEvent,
|
||||||
Table,
|
|
||||||
TableExportFormat,
|
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
/* eslint-disable */
|
async function created(view: ViewV2, timestamp?: string | number) {
|
||||||
|
|
||||||
async function created(view: View, timestamp?: string | number) {
|
|
||||||
const properties: ViewCreatedEvent = {
|
const properties: ViewCreatedEvent = {
|
||||||
|
name: view.name,
|
||||||
|
type: view.type,
|
||||||
tableId: view.tableId,
|
tableId: view.tableId,
|
||||||
}
|
}
|
||||||
await publishEvent(Event.VIEW_CREATED, properties, timestamp)
|
await publishEvent(Event.VIEW_CREATED, properties, timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updated(view: View) {
|
async function updated(view: ViewV2) {
|
||||||
const properties: ViewUpdatedEvent = {
|
const properties: ViewUpdatedEvent = {
|
||||||
tableId: view.tableId,
|
tableId: view.tableId,
|
||||||
}
|
}
|
||||||
|
@ -48,16 +51,27 @@ async function exported(table: Table, format: TableExportFormat) {
|
||||||
await publishEvent(Event.VIEW_EXPORTED, properties)
|
await publishEvent(Event.VIEW_EXPORTED, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function filterCreated(view: View, timestamp?: string | number) {
|
async function filterCreated(
|
||||||
|
{ tableId, filterGroups }: { tableId: string; filterGroups: number },
|
||||||
|
timestamp?: string | number
|
||||||
|
) {
|
||||||
const properties: ViewFilterCreatedEvent = {
|
const properties: ViewFilterCreatedEvent = {
|
||||||
tableId: view.tableId,
|
tableId,
|
||||||
|
filterGroups,
|
||||||
}
|
}
|
||||||
await publishEvent(Event.VIEW_FILTER_CREATED, properties, timestamp)
|
await publishEvent(Event.VIEW_FILTER_CREATED, properties, timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function filterUpdated(view: View) {
|
async function filterUpdated({
|
||||||
|
tableId,
|
||||||
|
filterGroups,
|
||||||
|
}: {
|
||||||
|
tableId: string
|
||||||
|
filterGroups: number
|
||||||
|
}) {
|
||||||
const properties: ViewFilterUpdatedEvent = {
|
const properties: ViewFilterUpdatedEvent = {
|
||||||
tableId: view.tableId,
|
tableId: tableId,
|
||||||
|
filterGroups,
|
||||||
}
|
}
|
||||||
await publishEvent(Event.VIEW_FILTER_UPDATED, properties)
|
await publishEvent(Event.VIEW_FILTER_UPDATED, properties)
|
||||||
}
|
}
|
||||||
|
@ -69,10 +83,16 @@ async function filterDeleted(view: View) {
|
||||||
await publishEvent(Event.VIEW_FILTER_DELETED, properties)
|
await publishEvent(Event.VIEW_FILTER_DELETED, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function calculationCreated(view: View, timestamp?: string | number) {
|
async function calculationCreated(
|
||||||
|
{
|
||||||
|
tableId,
|
||||||
|
calculationType,
|
||||||
|
}: { tableId: string; calculationType: CalculationType },
|
||||||
|
timestamp?: string | number
|
||||||
|
) {
|
||||||
const properties: ViewCalculationCreatedEvent = {
|
const properties: ViewCalculationCreatedEvent = {
|
||||||
tableId: view.tableId,
|
tableId,
|
||||||
calculation: view.calculation as ViewCalculation,
|
calculation: calculationType,
|
||||||
}
|
}
|
||||||
await publishEvent(Event.VIEW_CALCULATION_CREATED, properties, timestamp)
|
await publishEvent(Event.VIEW_CALCULATION_CREATED, properties, timestamp)
|
||||||
}
|
}
|
||||||
|
@ -93,6 +113,13 @@ async function calculationDeleted(existingView: View) {
|
||||||
await publishEvent(Event.VIEW_CALCULATION_DELETED, properties)
|
await publishEvent(Event.VIEW_CALCULATION_DELETED, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function viewJoinCreated(tableId: any, timestamp?: string | number) {
|
||||||
|
const properties: ViewJoinCreatedEvent = {
|
||||||
|
tableId,
|
||||||
|
}
|
||||||
|
await publishEvent(Event.VIEW_JOIN_CREATED, properties, timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
created,
|
created,
|
||||||
updated,
|
updated,
|
||||||
|
@ -104,4 +131,5 @@ export default {
|
||||||
calculationCreated,
|
calculationCreated,
|
||||||
calculationUpdated,
|
calculationUpdated,
|
||||||
calculationDeleted,
|
calculationDeleted,
|
||||||
|
viewJoinCreated,
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,9 +137,9 @@ export default function (
|
||||||
}
|
}
|
||||||
|
|
||||||
const tenantId = getHeader(ctx, Header.TENANT_ID)
|
const tenantId = getHeader(ctx, Header.TENANT_ID)
|
||||||
let authenticated: boolean = false,
|
let authenticated = false,
|
||||||
user: User | { tenantId: string } | undefined = undefined,
|
user: User | { tenantId: string } | undefined = undefined,
|
||||||
internal: boolean = false,
|
internal = false,
|
||||||
loginMethod: LoginMethod | undefined = undefined
|
loginMethod: LoginMethod | undefined = undefined
|
||||||
if (authCookie && !apiKey) {
|
if (authCookie && !apiKey) {
|
||||||
const sessionId = authCookie.sessionId
|
const sessionId = authCookie.sessionId
|
||||||
|
|
|
@ -20,7 +20,7 @@ export const ssoSaveUserNoOp: SaveSSOUserFunction = (user: SSOUser) =>
|
||||||
*/
|
*/
|
||||||
export async function authenticate(
|
export async function authenticate(
|
||||||
details: SSOAuthDetails,
|
details: SSOAuthDetails,
|
||||||
requireLocalAccount: boolean = true,
|
requireLocalAccount = true,
|
||||||
done: any,
|
done: any,
|
||||||
saveUserFn: SaveSSOUserFunction
|
saveUserFn: SaveSSOUserFunction
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -35,7 +35,7 @@ export const backPopulateMigrations = async (opts: MigrationNoOpOptions) => {
|
||||||
// filter migrations to the type and populate a no-op migration
|
// filter migrations to the type and populate a no-op migration
|
||||||
const migrations: Migration[] = DEFINITIONS.filter(
|
const migrations: Migration[] = DEFINITIONS.filter(
|
||||||
def => def.type === opts.type
|
def => def.type === opts.type
|
||||||
).map(d => ({ ...d, fn: () => {} }))
|
).map(d => ({ ...d, fn: async () => {} }))
|
||||||
await runMigrations(migrations, { noOp: opts })
|
await runMigrations(migrations, { noOp: opts })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -334,7 +334,7 @@ export async function listAllObjects(bucketName: string, path: string) {
|
||||||
export function getPresignedUrl(
|
export function getPresignedUrl(
|
||||||
bucketName: string,
|
bucketName: string,
|
||||||
key: string,
|
key: string,
|
||||||
durationSeconds: number = 3600
|
durationSeconds = 3600
|
||||||
) {
|
) {
|
||||||
const objectStore = ObjectStore(bucketName, { presigning: true })
|
const objectStore = ObjectStore(bucketName, { presigning: true })
|
||||||
const params = {
|
const params = {
|
||||||
|
|
|
@ -92,7 +92,7 @@ function getLockName(opts: LockOptions) {
|
||||||
// determine lock name
|
// determine lock name
|
||||||
// by default use the tenantId for uniqueness, unless using a system lock
|
// by default use the tenantId for uniqueness, unless using a system lock
|
||||||
const prefix = opts.systemLock ? "system" : context.getTenantId()
|
const prefix = opts.systemLock ? "system" : context.getTenantId()
|
||||||
let name: string = `lock:${prefix}_${opts.name}`
|
let name = `lock:${prefix}_${opts.name}`
|
||||||
// add additional unique name if required
|
// add additional unique name if required
|
||||||
if (opts.resource) {
|
if (opts.resource) {
|
||||||
name = name + `_${opts.resource}`
|
name = name + `_${opts.resource}`
|
||||||
|
|
|
@ -11,15 +11,15 @@ const { getCreatorCount } = require("../../../src/users/users")
|
||||||
|
|
||||||
describe("Users", () => {
|
describe("Users", () => {
|
||||||
let getGlobalDBMock
|
let getGlobalDBMock
|
||||||
let getGlobalUserParamsMock
|
|
||||||
let paginationMock
|
let paginationMock
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.resetAllMocks()
|
jest.resetAllMocks()
|
||||||
|
|
||||||
getGlobalDBMock = jest.spyOn(context, "getGlobalDB")
|
getGlobalDBMock = jest.spyOn(context, "getGlobalDB")
|
||||||
getGlobalUserParamsMock = jest.spyOn(db, "getGlobalUserParams")
|
|
||||||
paginationMock = jest.spyOn(db, "pagination")
|
paginationMock = jest.spyOn(db, "pagination")
|
||||||
|
|
||||||
|
jest.spyOn(db, "getGlobalUserParams")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Retrieves the number of creators", async () => {
|
it("Retrieves the number of creators", async () => {
|
||||||
|
|
|
@ -117,6 +117,7 @@ beforeAll(async () => {
|
||||||
jest.spyOn(events.view, "calculationCreated")
|
jest.spyOn(events.view, "calculationCreated")
|
||||||
jest.spyOn(events.view, "calculationUpdated")
|
jest.spyOn(events.view, "calculationUpdated")
|
||||||
jest.spyOn(events.view, "calculationDeleted")
|
jest.spyOn(events.view, "calculationDeleted")
|
||||||
|
jest.spyOn(events.view, "viewJoinCreated")
|
||||||
|
|
||||||
jest.spyOn(events.plugin, "init")
|
jest.spyOn(events.plugin, "init")
|
||||||
jest.spyOn(events.plugin, "imported")
|
jest.spyOn(events.plugin, "imported")
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { MonthlyQuotaName, QuotaUsage } from "@budibase/types"
|
import { MonthlyQuotaName, QuotaUsage } from "@budibase/types"
|
||||||
|
|
||||||
export const usage = (users: number = 0, creators: number = 0): QuotaUsage => {
|
export const usage = (users = 0, creators = 0): QuotaUsage => {
|
||||||
return {
|
return {
|
||||||
_id: "usage_quota",
|
_id: "usage_quota",
|
||||||
quotaReset: new Date().toISOString(),
|
quotaReset: new Date().toISOString(),
|
||||||
|
|
|
@ -85,7 +85,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPos = e => {
|
const getPos = e => {
|
||||||
var rect = canvasRef.getBoundingClientRect()
|
let rect = canvasRef.getBoundingClientRect()
|
||||||
const canvasX = e.offsetX || e.targetTouches?.[0].pageX - rect.left
|
const canvasX = e.offsetX || e.targetTouches?.[0].pageX - rect.left
|
||||||
const canvasY = e.offsetY || e.targetTouches?.[0].pageY - rect.top
|
const canvasY = e.offsetY || e.targetTouches?.[0].pageY - rect.top
|
||||||
|
|
||||||
|
|
|
@ -24,9 +24,9 @@
|
||||||
const j = 32 // Outer radius strength (higher is stronger)
|
const j = 32 // Outer radius strength (higher is stronger)
|
||||||
|
|
||||||
// Calculate unit vector
|
// Calculate unit vector
|
||||||
var dx = x1 - x2
|
let dx = x1 - x2
|
||||||
var dy = y1 - y2
|
let dy = y1 - y2
|
||||||
var len = Math.sqrt(dx * dx + dy * dy)
|
let len = Math.sqrt(dx * dx + dy * dy)
|
||||||
dx = dx / len
|
dx = dx / len
|
||||||
dy = dy / len
|
dy = dy / len
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ vi.mock("stores/selectors", () => ({
|
||||||
describe("datasource creation store", () => {
|
describe("datasource creation store", () => {
|
||||||
beforeEach(ctx => {
|
beforeEach(ctx => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
// eslint-disable-next-line no-import-assign
|
|
||||||
ctx.store = createDatasourceCreationStore()
|
ctx.store = createDatasourceCreationStore()
|
||||||
|
|
||||||
ctx.integration = { data: "integration" }
|
ctx.integration = { data: "integration" }
|
||||||
|
|
|
@ -30,7 +30,7 @@ const FullScreenControl = L.Control.extend({
|
||||||
fullScreenTitle: "Enter Fullscreen",
|
fullScreenTitle: "Enter Fullscreen",
|
||||||
},
|
},
|
||||||
onAdd: function () {
|
onAdd: function () {
|
||||||
var fullScreenClassName = "leaflet-control-fullscreen",
|
let fullScreenClassName = "leaflet-control-fullscreen",
|
||||||
container = L.DomUtil.create("div", fullScreenClassName + " leaflet-bar"),
|
container = L.DomUtil.create("div", fullScreenClassName + " leaflet-bar"),
|
||||||
options = this.options
|
options = this.options
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ const FullScreenControl = L.Control.extend({
|
||||||
return container
|
return container
|
||||||
},
|
},
|
||||||
_fullScreen: function () {
|
_fullScreen: function () {
|
||||||
var map = this._map
|
let map = this._map
|
||||||
if (screenfull.isEnabled) {
|
if (screenfull.isEnabled) {
|
||||||
screenfull.toggle(map.getContainer())
|
screenfull.toggle(map.getContainer())
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ const LocationControl = L.Control.extend({
|
||||||
locationTitle: "Show Your Location",
|
locationTitle: "Show Your Location",
|
||||||
},
|
},
|
||||||
onAdd: function () {
|
onAdd: function () {
|
||||||
var locationClassName = "leaflet-control-location",
|
let locationClassName = "leaflet-control-location",
|
||||||
container = L.DomUtil.create("div", locationClassName + " leaflet-bar"),
|
container = L.DomUtil.create("div", locationClassName + " leaflet-bar"),
|
||||||
options = this.options
|
options = this.options
|
||||||
|
|
||||||
|
@ -136,7 +136,7 @@ const LocationControl = L.Control.extend({
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
_getPosition: function () {
|
_getPosition: function () {
|
||||||
var options = {
|
let options = {
|
||||||
enableHighAccuracy: false,
|
enableHighAccuracy: false,
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
maximumAge: 30000,
|
maximumAge: 30000,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable no-useless-concat */
|
||||||
import { derived } from "svelte/store"
|
import { derived } from "svelte/store"
|
||||||
import { appStore } from "./app"
|
import { appStore } from "./app"
|
||||||
import { builderStore } from "./builder"
|
import { builderStore } from "./builder"
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
async function checkMigrationsFinished() {
|
async function checkMigrationsFinished() {
|
||||||
let totalWaitMs = 0
|
let totalWaitMs = 0
|
||||||
// eslint-disable-next-line no-constant-condition
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const waitForMs = 5000 + Math.random() * 5000
|
const waitForMs = 5000 + Math.random() * 5000
|
||||||
await new Promise(resolve => setTimeout(resolve, waitForMs))
|
await new Promise(resolve => setTimeout(resolve, waitForMs))
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit c22f82b908664a1624b43646547822221bf05bc1
|
Subproject commit 977baca179fef1192f8fe051122288a4128f7a63
|
|
@ -22,7 +22,7 @@ import {
|
||||||
async function redirect(
|
async function redirect(
|
||||||
ctx: any,
|
ctx: any,
|
||||||
method: "GET" | "POST" | "DELETE",
|
method: "GET" | "POST" | "DELETE",
|
||||||
path: string = "global"
|
path = "global"
|
||||||
) {
|
) {
|
||||||
const { devPath } = ctx.params
|
const { devPath } = ctx.params
|
||||||
const queryString = ctx.originalUrl.split("?")[1] || ""
|
const queryString = ctx.originalUrl.split("?")[1] || ""
|
||||||
|
|
|
@ -111,8 +111,8 @@ describe("Rest Importer", () => {
|
||||||
const importResult = await restImporter.importQueries(datasource._id)
|
const importResult = await restImporter.importQueries(datasource._id)
|
||||||
expect(importResult.errorQueries.length).toBe(0)
|
expect(importResult.errorQueries.length).toBe(0)
|
||||||
expect(importResult.queries.length).toBe(assertions[key].count)
|
expect(importResult.queries.length).toBe(assertions[key].count)
|
||||||
expect(events.query.imported).toBeCalledTimes(1)
|
expect(events.query.imported).toHaveBeenCalledTimes(1)
|
||||||
expect(events.query.imported).toBeCalledWith(
|
expect(events.query.imported).toHaveBeenCalledWith(
|
||||||
datasource,
|
datasource,
|
||||||
assertions[key].source,
|
assertions[key].source,
|
||||||
assertions[key].count
|
assertions[key].count
|
||||||
|
|
|
@ -378,7 +378,7 @@ export class ExternalRequest<T extends Operation> {
|
||||||
}
|
}
|
||||||
// many to one
|
// many to one
|
||||||
else {
|
else {
|
||||||
const thisKey: string = "id"
|
const thisKey = "id"
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const otherKey: string = field.fieldName
|
const otherKey: string = field.fieldName
|
||||||
for (const relationship of row[key]) {
|
for (const relationship of row[key]) {
|
||||||
|
|
|
@ -45,7 +45,7 @@ export function getInternalRowId(row: Row, table: Table): string {
|
||||||
export function generateIdForRow(
|
export function generateIdForRow(
|
||||||
row: Row | undefined,
|
row: Row | undefined,
|
||||||
table: Table,
|
table: Table,
|
||||||
isLinked: boolean = false
|
isLinked = false
|
||||||
): string {
|
): string {
|
||||||
const primary = table.primary
|
const primary = table.primary
|
||||||
if (!row || !primary) {
|
if (!row || !primary) {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
RowActionResponse,
|
RowActionResponse,
|
||||||
RowActionsResponse,
|
RowActionsResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
import { events } from "@budibase/backend-core"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
|
|
||||||
async function getTable(ctx: Ctx) {
|
async function getTable(ctx: Ctx) {
|
||||||
|
@ -59,6 +60,8 @@ export async function create(
|
||||||
name: ctx.request.body.name,
|
name: ctx.request.body.name,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await events.rowAction.created(createdAction)
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
tableId,
|
tableId,
|
||||||
id: createdAction.id,
|
id: createdAction.id,
|
||||||
|
|
|
@ -45,13 +45,13 @@ export async function updateTable(
|
||||||
inputs.created = true
|
inputs.created = true
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { datasource, table } = await sdk.tables.external.save(
|
const { datasource, oldTable, table } = await sdk.tables.external.save(
|
||||||
datasourceId!,
|
datasourceId!,
|
||||||
inputs,
|
inputs,
|
||||||
{ tableId, renaming }
|
{ tableId, renaming }
|
||||||
)
|
)
|
||||||
builderSocket?.emitDatasourceUpdate(ctx, datasource)
|
builderSocket?.emitDatasourceUpdate(ctx, datasource)
|
||||||
return table
|
return { table, oldTable }
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
ctx.throw(400, err.message)
|
ctx.throw(400, err.message)
|
||||||
|
|
|
@ -120,8 +120,15 @@ export async function save(ctx: UserCtx<SaveTableRequest, SaveTableResponse>) {
|
||||||
await events.table.created(savedTable)
|
await events.table.created(savedTable)
|
||||||
} else {
|
} else {
|
||||||
const api = pickApi({ table })
|
const api = pickApi({ table })
|
||||||
savedTable = await api.updateTable(ctx, renaming)
|
const { table: updatedTable, oldTable } = await api.updateTable(
|
||||||
await events.table.updated(savedTable)
|
ctx,
|
||||||
|
renaming
|
||||||
|
)
|
||||||
|
savedTable = updatedTable
|
||||||
|
|
||||||
|
if (oldTable) {
|
||||||
|
await events.table.updated(oldTable, savedTable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (renaming) {
|
if (renaming) {
|
||||||
await sdk.views.renameLinkedViews(savedTable, renaming)
|
await sdk.views.renameLinkedViews(savedTable, renaming)
|
||||||
|
|
|
@ -30,14 +30,14 @@ export async function updateTable(
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { table } = await sdk.tables.internal.save(tableToSave, {
|
const { table, oldTable } = await sdk.tables.internal.save(tableToSave, {
|
||||||
userId: ctx.user._id,
|
userId: ctx.user._id,
|
||||||
rowsToImport: rows,
|
rowsToImport: rows,
|
||||||
tableId: ctx.request.body._id,
|
tableId: ctx.request.body._id,
|
||||||
renaming,
|
renaming,
|
||||||
})
|
})
|
||||||
|
|
||||||
return table
|
return { table, oldTable }
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
ctx.throw(400, err.message)
|
ctx.throw(400, err.message)
|
||||||
|
|
|
@ -16,7 +16,7 @@ function escapeCsvString(str: string) {
|
||||||
export function csv(
|
export function csv(
|
||||||
headers: string[],
|
headers: string[],
|
||||||
rows: Row[],
|
rows: Row[],
|
||||||
delimiter: string = ",",
|
delimiter = ",",
|
||||||
customHeaders: { [key: string]: string } = {}
|
customHeaders: { [key: string]: string } = {}
|
||||||
) {
|
) {
|
||||||
let csvRows = [getHeaders(headers, customHeaders)]
|
let csvRows = [getHeaders(headers, customHeaders)]
|
||||||
|
|
|
@ -19,8 +19,6 @@ import { builderSocket } from "../../../websockets"
|
||||||
|
|
||||||
const cloneDeep = require("lodash/cloneDeep")
|
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()
|
||||||
}
|
}
|
||||||
|
@ -60,71 +58,11 @@ export async function save(ctx: Ctx) {
|
||||||
existingTable.views[viewName] = existingTable.views[originalName]
|
existingTable.views[viewName] = existingTable.views[originalName]
|
||||||
}
|
}
|
||||||
await db.put(table)
|
await db.put(table)
|
||||||
await handleViewEvents(
|
|
||||||
existingTable.views[viewName] as View,
|
|
||||||
table.views[viewName]
|
|
||||||
)
|
|
||||||
|
|
||||||
ctx.body = table.views[viewName]
|
ctx.body = table.views[viewName]
|
||||||
builderSocket?.emitTableUpdate(ctx, table)
|
builderSocket?.emitTableUpdate(ctx, table)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function calculationEvents(existingView: View, newView: View) {
|
|
||||||
const existingCalculation = existingView && existingView.calculation
|
|
||||||
const newCalculation = newView && newView.calculation
|
|
||||||
|
|
||||||
if (existingCalculation && !newCalculation) {
|
|
||||||
await events.view.calculationDeleted(existingView)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!existingCalculation && newCalculation) {
|
|
||||||
await events.view.calculationCreated(newView)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
existingCalculation &&
|
|
||||||
newCalculation &&
|
|
||||||
existingCalculation !== newCalculation
|
|
||||||
) {
|
|
||||||
await events.view.calculationUpdated(newView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function filterEvents(existingView: View, newView: View) {
|
|
||||||
const hasExistingFilters = !!(
|
|
||||||
existingView &&
|
|
||||||
existingView.filters &&
|
|
||||||
existingView.filters.length
|
|
||||||
)
|
|
||||||
const hasNewFilters = !!(newView && newView.filters && newView.filters.length)
|
|
||||||
|
|
||||||
if (hasExistingFilters && !hasNewFilters) {
|
|
||||||
await events.view.filterDeleted(newView)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasExistingFilters && hasNewFilters) {
|
|
||||||
await events.view.filterCreated(newView)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
hasExistingFilters &&
|
|
||||||
hasNewFilters &&
|
|
||||||
!isEqual(existingView.filters, newView.filters)
|
|
||||||
) {
|
|
||||||
await events.view.filterUpdated(newView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleViewEvents(existingView: View, newView: View) {
|
|
||||||
if (!existingView) {
|
|
||||||
await events.view.created(newView)
|
|
||||||
} else {
|
|
||||||
await events.view.updated(newView)
|
|
||||||
}
|
|
||||||
await calculationEvents(existingView, newView)
|
|
||||||
await filterEvents(existingView, newView)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function destroy(ctx: Ctx) {
|
export async function destroy(ctx: Ctx) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const viewName = decodeURIComponent(ctx.params.viewName)
|
const viewName = decodeURIComponent(ctx.params.viewName)
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
CreateViewResponse,
|
CreateViewResponse,
|
||||||
UpdateViewResponse,
|
UpdateViewResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
import { events } from "@budibase/backend-core"
|
||||||
import { builderSocket, gridSocket } from "../../../websockets"
|
import { builderSocket, gridSocket } from "../../../websockets"
|
||||||
import { helpers } from "@budibase/shared-core"
|
import { helpers } from "@budibase/shared-core"
|
||||||
|
|
||||||
|
@ -150,6 +151,9 @@ export async function create(ctx: Ctx<CreateViewRequest, CreateViewResponse>) {
|
||||||
primaryDisplay: view.primaryDisplay,
|
primaryDisplay: view.primaryDisplay,
|
||||||
}
|
}
|
||||||
const result = await sdk.views.create(tableId, parsedView)
|
const result = await sdk.views.create(tableId, parsedView)
|
||||||
|
|
||||||
|
await events.view.created(result)
|
||||||
|
|
||||||
ctx.status = 201
|
ctx.status = 201
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
data: result,
|
data: result,
|
||||||
|
@ -160,6 +164,46 @@ export async function create(ctx: Ctx<CreateViewRequest, CreateViewResponse>) {
|
||||||
gridSocket?.emitViewUpdate(ctx, result)
|
gridSocket?.emitViewUpdate(ctx, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleViewFilterEvents(existingView: ViewV2, view: ViewV2) {
|
||||||
|
const filterGroups = view.queryUI?.groups?.length || 0
|
||||||
|
const properties = { filterGroups, tableId: view.tableId }
|
||||||
|
if (
|
||||||
|
filterGroups >= 2 &&
|
||||||
|
filterGroups > (existingView?.queryUI?.groups?.length || 0)
|
||||||
|
) {
|
||||||
|
await events.view.filterUpdated(properties)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleViewEvents(existingView: ViewV2, view: ViewV2) {
|
||||||
|
// Grouped filters
|
||||||
|
if (view.queryUI?.groups) {
|
||||||
|
await handleViewFilterEvents(existingView, view)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if new columns in the view
|
||||||
|
for (const key in view.schema) {
|
||||||
|
if ("calculationType" in view.schema[key] && !existingView?.schema?.[key]) {
|
||||||
|
await events.view.calculationCreated({
|
||||||
|
calculationType: view.schema[key].calculationType,
|
||||||
|
tableId: view.tableId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// view joins
|
||||||
|
for (const column in view.schema[key]?.columns ?? []) {
|
||||||
|
// if the new column is visible and it wasn't before
|
||||||
|
if (
|
||||||
|
!existingView?.schema?.[key].columns?.[column].visible &&
|
||||||
|
view.schema?.[key].columns?.[column].visible
|
||||||
|
) {
|
||||||
|
// new view join exposing a column
|
||||||
|
await events.view.viewJoinCreated({ tableId: view.tableId })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function update(ctx: Ctx<UpdateViewRequest, UpdateViewResponse>) {
|
export async function update(ctx: Ctx<UpdateViewRequest, UpdateViewResponse>) {
|
||||||
const view = ctx.request.body
|
const view = ctx.request.body
|
||||||
|
|
||||||
|
@ -187,10 +231,15 @@ export async function update(ctx: Ctx<UpdateViewRequest, UpdateViewResponse>) {
|
||||||
primaryDisplay: view.primaryDisplay,
|
primaryDisplay: view.primaryDisplay,
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await sdk.views.update(tableId, parsedView)
|
const { view: result, existingView } = await sdk.views.update(
|
||||||
ctx.body = {
|
tableId,
|
||||||
data: result,
|
parsedView
|
||||||
}
|
)
|
||||||
|
|
||||||
|
await handleViewEvents(existingView, result)
|
||||||
|
await events.view.updated(result)
|
||||||
|
|
||||||
|
ctx.body = { data: result }
|
||||||
|
|
||||||
const table = await sdk.tables.getTable(tableId)
|
const table = await sdk.tables.getTable(tableId)
|
||||||
builderSocket?.emitTableUpdate(ctx, table)
|
builderSocket?.emitTableUpdate(ctx, table)
|
||||||
|
|
|
@ -4,14 +4,13 @@ const { events, constants } = require("@budibase/backend-core")
|
||||||
describe("/static", () => {
|
describe("/static", () => {
|
||||||
let request = setup.getRequest()
|
let request = setup.getRequest()
|
||||||
let config = setup.getConfig()
|
let config = setup.getConfig()
|
||||||
let app
|
|
||||||
|
|
||||||
const timezone = "Europe/London"
|
const timezone = "Europe/London"
|
||||||
|
|
||||||
afterAll(setup.afterAll)
|
afterAll(setup.afterAll)
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await config.init()
|
await config.init()
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -26,10 +25,10 @@ describe("/static", () => {
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(events.serve.servedBuilder).toBeCalledTimes(1)
|
expect(events.serve.servedBuilder).toHaveBeenCalledTimes(1)
|
||||||
expect(events.serve.servedBuilder).toBeCalledWith(timezone)
|
expect(events.serve.servedBuilder).toHaveBeenCalledWith(timezone)
|
||||||
expect(events.serve.servedApp).not.toBeCalled()
|
expect(events.serve.servedApp).not.toHaveBeenCalled()
|
||||||
expect(events.serve.servedAppPreview).not.toBeCalled()
|
expect(events.serve.servedAppPreview).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should ping from app preview", async () => {
|
it("should ping from app preview", async () => {
|
||||||
|
@ -39,12 +38,12 @@ describe("/static", () => {
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(events.serve.servedAppPreview).toBeCalledTimes(1)
|
expect(events.serve.servedAppPreview).toHaveBeenCalledTimes(1)
|
||||||
expect(events.serve.servedAppPreview).toBeCalledWith(
|
expect(events.serve.servedAppPreview).toHaveBeenCalledWith(
|
||||||
config.getApp(),
|
config.getApp(),
|
||||||
timezone
|
timezone
|
||||||
)
|
)
|
||||||
expect(events.serve.servedApp).not.toBeCalled()
|
expect(events.serve.servedApp).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should ping from app", async () => {
|
it("should ping from app", async () => {
|
||||||
|
@ -57,13 +56,13 @@ describe("/static", () => {
|
||||||
.set(headers)
|
.set(headers)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(events.serve.servedApp).toBeCalledTimes(1)
|
expect(events.serve.servedApp).toHaveBeenCalledTimes(1)
|
||||||
expect(events.serve.servedApp).toBeCalledWith(
|
expect(events.serve.servedApp).toHaveBeenCalledWith(
|
||||||
config.getProdApp(),
|
config.getProdApp(),
|
||||||
timezone,
|
timezone,
|
||||||
undefined
|
undefined
|
||||||
)
|
)
|
||||||
expect(events.serve.servedAppPreview).not.toBeCalled()
|
expect(events.serve.servedAppPreview).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should ping from an embedded app", async () => {
|
it("should ping from an embedded app", async () => {
|
||||||
|
@ -76,13 +75,13 @@ describe("/static", () => {
|
||||||
.set(headers)
|
.set(headers)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(events.serve.servedApp).toBeCalledTimes(1)
|
expect(events.serve.servedApp).toHaveBeenCalledTimes(1)
|
||||||
expect(events.serve.servedApp).toBeCalledWith(
|
expect(events.serve.servedApp).toHaveBeenCalledWith(
|
||||||
config.getProdApp(),
|
config.getProdApp(),
|
||||||
timezone,
|
timezone,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
expect(events.serve.servedAppPreview).not.toBeCalled()
|
expect(events.serve.servedAppPreview).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -208,7 +208,7 @@ describe("/applications", () => {
|
||||||
|
|
||||||
it("should reject with a known url", async () => {
|
it("should reject with a known url", async () => {
|
||||||
await config.api.application.create(
|
await config.api.application.create(
|
||||||
{ name: "made up", url: app?.url! },
|
{ name: "made up", url: app!.url! },
|
||||||
{ body: { message: "App URL is already in use." }, status: 400 }
|
{ body: { message: "App URL is already in use." }, status: 400 }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -19,7 +19,7 @@ describe("/dev", () => {
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
expect(events.app.reverted).toBeCalledTimes(1)
|
expect(events.app.reverted).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -32,8 +32,10 @@ describe("/dev", () => {
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.version).toBe("0.0.0+jest")
|
expect(res.body.version).toBe("0.0.0+jest")
|
||||||
expect(events.installation.versionChecked).toBeCalledTimes(1)
|
expect(events.installation.versionChecked).toHaveBeenCalledTimes(1)
|
||||||
expect(events.installation.versionChecked).toBeCalledWith("0.0.0+jest")
|
expect(events.installation.versionChecked).toHaveBeenCalledWith(
|
||||||
|
"0.0.0+jest"
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -25,7 +25,7 @@ describe("/layouts", () => {
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
expect(res.body._rev).toBeDefined()
|
expect(res.body._rev).toBeDefined()
|
||||||
expect(events.layout.created).toBeCalledTimes(1)
|
expect(events.layout.created).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should apply authorization to endpoint", async () => {
|
it("should apply authorization to endpoint", async () => {
|
||||||
|
@ -45,7 +45,7 @@ describe("/layouts", () => {
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
expect(res.body.message).toBeDefined()
|
expect(res.body.message).toBeDefined()
|
||||||
expect(events.layout.deleted).toBeCalledTimes(1)
|
expect(events.layout.deleted).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should apply authorization to endpoint", async () => {
|
it("should apply authorization to endpoint", async () => {
|
||||||
|
|
|
@ -314,9 +314,7 @@ if (descriptions.length) {
|
||||||
const cloned = cloneDeep(response)
|
const cloned = cloneDeep(response)
|
||||||
const foundRows = response.rows
|
const foundRows = response.rows
|
||||||
|
|
||||||
// eslint-disable-next-line jest/no-standalone-expect
|
|
||||||
expect(foundRows).toHaveLength(expectedRows.length)
|
expect(foundRows).toHaveLength(expectedRows.length)
|
||||||
// eslint-disable-next-line jest/no-standalone-expect
|
|
||||||
expect([...foundRows]).toEqual(
|
expect([...foundRows]).toEqual(
|
||||||
expectedRows.map((expectedRow: any) =>
|
expectedRows.map((expectedRow: any) =>
|
||||||
expect.objectContaining(this.popRow(expectedRow, foundRows))
|
expect.objectContaining(this.popRow(expectedRow, foundRows))
|
||||||
|
@ -333,9 +331,7 @@ if (descriptions.length) {
|
||||||
const cloned = cloneDeep(response)
|
const cloned = cloneDeep(response)
|
||||||
const foundRows = response.rows
|
const foundRows = response.rows
|
||||||
|
|
||||||
// eslint-disable-next-line jest/no-standalone-expect
|
|
||||||
expect(foundRows).toHaveLength(expectedRows.length)
|
expect(foundRows).toHaveLength(expectedRows.length)
|
||||||
// eslint-disable-next-line jest/no-standalone-expect
|
|
||||||
expect([...foundRows]).toEqual(
|
expect([...foundRows]).toEqual(
|
||||||
expect.arrayContaining(
|
expect.arrayContaining(
|
||||||
expectedRows.map((expectedRow: any) =>
|
expectedRows.map((expectedRow: any) =>
|
||||||
|
@ -358,10 +354,8 @@ if (descriptions.length) {
|
||||||
keyof SearchResponse<Row>
|
keyof SearchResponse<Row>
|
||||||
>
|
>
|
||||||
for (let key of keys) {
|
for (let key of keys) {
|
||||||
// eslint-disable-next-line jest/no-standalone-expect
|
|
||||||
expect(response[key]).toBeDefined()
|
expect(response[key]).toBeDefined()
|
||||||
if (properties[key]) {
|
if (properties[key]) {
|
||||||
// eslint-disable-next-line jest/no-standalone-expect
|
|
||||||
expect(response[key]).toEqual(properties[key])
|
expect(response[key]).toEqual(properties[key])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -375,7 +369,6 @@ if (descriptions.length) {
|
||||||
const response = await this.performSearch()
|
const response = await this.performSearch()
|
||||||
const cloned = cloneDeep(response)
|
const cloned = cloneDeep(response)
|
||||||
for (let property of properties) {
|
for (let property of properties) {
|
||||||
// eslint-disable-next-line jest/no-standalone-expect
|
|
||||||
expect(response[property]).toBeUndefined()
|
expect(response[property]).toBeUndefined()
|
||||||
}
|
}
|
||||||
return cloned
|
return cloned
|
||||||
|
@ -389,7 +382,6 @@ if (descriptions.length) {
|
||||||
const cloned = cloneDeep(response)
|
const cloned = cloneDeep(response)
|
||||||
const foundRows = response.rows
|
const foundRows = response.rows
|
||||||
|
|
||||||
// eslint-disable-next-line jest/no-standalone-expect
|
|
||||||
expect([...foundRows]).toEqual(
|
expect([...foundRows]).toEqual(
|
||||||
expect.arrayContaining(
|
expect.arrayContaining(
|
||||||
expectedRows.map((expectedRow: any) =>
|
expectedRows.map((expectedRow: any) =>
|
||||||
|
@ -409,7 +401,6 @@ if (descriptions.length) {
|
||||||
async toHaveLength(length: number) {
|
async toHaveLength(length: number) {
|
||||||
const { rows: foundRows } = await this.performSearch()
|
const { rows: foundRows } = await this.performSearch()
|
||||||
|
|
||||||
// eslint-disable-next-line jest/no-standalone-expect
|
|
||||||
expect(foundRows).toHaveLength(length)
|
expect(foundRows).toHaveLength(length)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2142,7 +2133,7 @@ if (descriptions.length) {
|
||||||
// repeat the search many times to check the first row is always the same
|
// repeat the search many times to check the first row is always the same
|
||||||
let bookmark: string | number | undefined,
|
let bookmark: string | number | undefined,
|
||||||
hasNextPage: boolean | undefined = true,
|
hasNextPage: boolean | undefined = true,
|
||||||
rowCount: number = 0
|
rowCount = 0
|
||||||
do {
|
do {
|
||||||
const response = await config.api.row.search(
|
const response = await config.api.row.search(
|
||||||
tableOrViewId,
|
tableOrViewId,
|
||||||
|
@ -2169,7 +2160,6 @@ if (descriptions.length) {
|
||||||
let bookmark: string | number = undefined
|
let bookmark: string | number = undefined
|
||||||
let rows: Row[] = []
|
let rows: Row[] = []
|
||||||
|
|
||||||
// eslint-disable-next-line no-constant-condition
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const response = await config.api.row.search(
|
const response = await config.api.row.search(
|
||||||
tableOrViewId,
|
tableOrViewId,
|
||||||
|
|
|
@ -247,6 +247,9 @@ if (descriptions.length) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
primary: ["_id"],
|
||||||
|
views: {},
|
||||||
|
sql: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -254,9 +257,8 @@ if (descriptions.length) {
|
||||||
...table,
|
...table,
|
||||||
name: generator.guid(),
|
name: generator.guid(),
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(events.table.updated).toHaveBeenCalledTimes(1)
|
expect(events.table.updated).toHaveBeenCalledTimes(1)
|
||||||
expect(events.table.updated).toHaveBeenCalledWith(updatedTable)
|
expect(events.table.updated).toHaveBeenCalledWith(table, updatedTable)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("updates all the row fields for a table when a schema key is renamed", async () => {
|
it("updates all the row fields for a table when a schema key is renamed", async () => {
|
||||||
|
|
|
@ -73,25 +73,12 @@ describe("/views", () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("create", () => {
|
describe("create", () => {
|
||||||
it("returns a success message when the view is successfully created", async () => {
|
|
||||||
await saveView()
|
|
||||||
expect(events.view.created).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("creates a view with a calculation", async () => {
|
it("creates a view with a calculation", async () => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
|
|
||||||
const view = await saveView({ calculation: ViewCalculation.COUNT })
|
const view = await saveView({ calculation: ViewCalculation.COUNT })
|
||||||
|
|
||||||
expect(view.tableId).toBe(table._id)
|
expect(view.tableId).toBe(table._id)
|
||||||
expect(events.view.created).toHaveBeenCalledTimes(1)
|
|
||||||
expect(events.view.updated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.calculationCreated).toHaveBeenCalledTimes(1)
|
|
||||||
expect(events.view.calculationUpdated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.calculationDeleted).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.filterCreated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.filterUpdated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.filterDeleted).not.toHaveBeenCalled()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("creates a view with a filter", async () => {
|
it("creates a view with a filter", async () => {
|
||||||
|
@ -109,14 +96,6 @@ describe("/views", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(view.tableId).toBe(table._id)
|
expect(view.tableId).toBe(table._id)
|
||||||
expect(events.view.created).toHaveBeenCalledTimes(1)
|
|
||||||
expect(events.view.updated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.calculationCreated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.calculationUpdated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.calculationDeleted).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.filterCreated).toHaveBeenCalledTimes(1)
|
|
||||||
expect(events.view.filterUpdated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.filterDeleted).not.toHaveBeenCalled()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("updates the table row with the new view metadata", async () => {
|
it("updates the table row with the new view metadata", async () => {
|
||||||
|
@ -166,13 +145,6 @@ describe("/views", () => {
|
||||||
await saveView()
|
await saveView()
|
||||||
|
|
||||||
expect(events.view.created).not.toHaveBeenCalled()
|
expect(events.view.created).not.toHaveBeenCalled()
|
||||||
expect(events.view.updated).toHaveBeenCalledTimes(1)
|
|
||||||
expect(events.view.calculationCreated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.calculationUpdated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.calculationDeleted).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.filterCreated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.filterUpdated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.filterDeleted).not.toHaveBeenCalled()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("updates a view calculation", async () => {
|
it("updates a view calculation", async () => {
|
||||||
|
@ -182,13 +154,6 @@ describe("/views", () => {
|
||||||
await saveView({ calculation: ViewCalculation.COUNT })
|
await saveView({ calculation: ViewCalculation.COUNT })
|
||||||
|
|
||||||
expect(events.view.created).not.toHaveBeenCalled()
|
expect(events.view.created).not.toHaveBeenCalled()
|
||||||
expect(events.view.updated).toHaveBeenCalledTimes(1)
|
|
||||||
expect(events.view.calculationCreated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.calculationUpdated).toHaveBeenCalledTimes(1)
|
|
||||||
expect(events.view.calculationDeleted).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.filterCreated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.filterUpdated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.filterDeleted).not.toHaveBeenCalled()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("deletes a view calculation", async () => {
|
it("deletes a view calculation", async () => {
|
||||||
|
@ -198,13 +163,6 @@ describe("/views", () => {
|
||||||
await saveView({ calculation: undefined })
|
await saveView({ calculation: undefined })
|
||||||
|
|
||||||
expect(events.view.created).not.toHaveBeenCalled()
|
expect(events.view.created).not.toHaveBeenCalled()
|
||||||
expect(events.view.updated).toHaveBeenCalledTimes(1)
|
|
||||||
expect(events.view.calculationCreated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.calculationUpdated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.calculationDeleted).toHaveBeenCalledTimes(1)
|
|
||||||
expect(events.view.filterCreated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.filterUpdated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.filterDeleted).not.toHaveBeenCalled()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("updates a view filter", async () => {
|
it("updates a view filter", async () => {
|
||||||
|
@ -230,13 +188,6 @@ describe("/views", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(events.view.created).not.toHaveBeenCalled()
|
expect(events.view.created).not.toHaveBeenCalled()
|
||||||
expect(events.view.updated).toHaveBeenCalledTimes(1)
|
|
||||||
expect(events.view.calculationCreated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.calculationUpdated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.calculationDeleted).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.filterCreated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.filterUpdated).toHaveBeenCalledTimes(1)
|
|
||||||
expect(events.view.filterDeleted).not.toHaveBeenCalled()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("deletes a view filter", async () => {
|
it("deletes a view filter", async () => {
|
||||||
|
@ -254,13 +205,6 @@ describe("/views", () => {
|
||||||
await saveView({ filters: [] })
|
await saveView({ filters: [] })
|
||||||
|
|
||||||
expect(events.view.created).not.toHaveBeenCalled()
|
expect(events.view.created).not.toHaveBeenCalled()
|
||||||
expect(events.view.updated).toHaveBeenCalledTimes(1)
|
|
||||||
expect(events.view.calculationCreated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.calculationUpdated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.calculationDeleted).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.filterCreated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.filterUpdated).not.toHaveBeenCalled()
|
|
||||||
expect(events.view.filterDeleted).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
import {
|
import {
|
||||||
|
ArrayOperator,
|
||||||
|
BasicOperator,
|
||||||
|
BBReferenceFieldSubType,
|
||||||
|
CalculationType,
|
||||||
CreateViewRequest,
|
CreateViewRequest,
|
||||||
Datasource,
|
Datasource,
|
||||||
|
EmptyFilterOption,
|
||||||
FieldSchema,
|
FieldSchema,
|
||||||
FieldType,
|
FieldType,
|
||||||
INTERNAL_TABLE_SOURCE_ID,
|
INTERNAL_TABLE_SOURCE_ID,
|
||||||
|
JsonFieldSubType,
|
||||||
|
JsonTypes,
|
||||||
|
LegacyFilter,
|
||||||
|
NumericCalculationFieldMetadata,
|
||||||
PermissionLevel,
|
PermissionLevel,
|
||||||
QuotaUsageType,
|
QuotaUsageType,
|
||||||
|
RelationshipType,
|
||||||
|
RenameColumn,
|
||||||
Row,
|
Row,
|
||||||
SaveTableRequest,
|
SaveTableRequest,
|
||||||
|
SearchFilters,
|
||||||
|
SearchResponse,
|
||||||
|
SearchViewRowRequest,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
SortType,
|
SortType,
|
||||||
StaticQuotaName,
|
StaticQuotaName,
|
||||||
Table,
|
Table,
|
||||||
|
TableSchema,
|
||||||
TableSourceType,
|
TableSourceType,
|
||||||
|
UILogicalOperator,
|
||||||
|
UISearchFilter,
|
||||||
UpdateViewRequest,
|
UpdateViewRequest,
|
||||||
ViewV2,
|
ViewV2,
|
||||||
SearchResponse,
|
|
||||||
BasicOperator,
|
|
||||||
CalculationType,
|
|
||||||
RelationshipType,
|
|
||||||
TableSchema,
|
|
||||||
RenameColumn,
|
|
||||||
BBReferenceFieldSubType,
|
|
||||||
NumericCalculationFieldMetadata,
|
|
||||||
ViewV2Schema,
|
ViewV2Schema,
|
||||||
ViewV2Type,
|
ViewV2Type,
|
||||||
JsonTypes,
|
|
||||||
EmptyFilterOption,
|
|
||||||
JsonFieldSubType,
|
|
||||||
UISearchFilter,
|
|
||||||
LegacyFilter,
|
|
||||||
SearchViewRowRequest,
|
|
||||||
ArrayOperator,
|
|
||||||
UILogicalOperator,
|
|
||||||
SearchFilters,
|
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { generator, mocks } from "@budibase/backend-core/tests"
|
import { generator, mocks } from "@budibase/backend-core/tests"
|
||||||
import {
|
import {
|
||||||
|
@ -42,7 +42,7 @@ import {
|
||||||
} from "../../../integrations/tests/utils"
|
} from "../../../integrations/tests/utils"
|
||||||
import merge from "lodash/merge"
|
import merge from "lodash/merge"
|
||||||
import { quotas } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
import { db, roles, context } from "@budibase/backend-core"
|
import { context, db, events, roles } from "@budibase/backend-core"
|
||||||
|
|
||||||
const descriptions = datasourceDescribe({ exclude: [DatabaseName.MONGODB] })
|
const descriptions = datasourceDescribe({ exclude: [DatabaseName.MONGODB] })
|
||||||
|
|
||||||
|
@ -129,6 +129,7 @@ if (descriptions.length) {
|
||||||
id: expect.stringMatching(new RegExp(`${table._id!}_`)),
|
id: expect.stringMatching(new RegExp(`${table._id!}_`)),
|
||||||
version: 2,
|
version: 2,
|
||||||
})
|
})
|
||||||
|
expect(events.view.created).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("can persist views with all fields", async () => {
|
it("can persist views with all fields", async () => {
|
||||||
|
@ -195,6 +196,7 @@ if (descriptions.length) {
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(res).toEqual(expected)
|
expect(res).toEqual(expected)
|
||||||
|
expect(events.view.created).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("can create a view with just a query field, no queryUI, for backwards compatibility", async () => {
|
it("can create a view with just a query field, no queryUI, for backwards compatibility", async () => {
|
||||||
|
@ -224,6 +226,7 @@ if (descriptions.length) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const res = await config.api.viewV2.create(newView)
|
const res = await config.api.viewV2.create(newView)
|
||||||
|
expect(events.view.created).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
const expected: ViewV2 = {
|
const expected: ViewV2 = {
|
||||||
...newView,
|
...newView,
|
||||||
|
@ -283,6 +286,7 @@ if (descriptions.length) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const createdView = await config.api.viewV2.create(newView)
|
const createdView = await config.api.viewV2.create(newView)
|
||||||
|
expect(events.view.created).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
expect(createdView).toEqual({
|
expect(createdView).toEqual({
|
||||||
...newView,
|
...newView,
|
||||||
|
@ -990,6 +994,46 @@ if (descriptions.length) {
|
||||||
expect((await config.api.table.get(tableId)).views).toEqual({
|
expect((await config.api.table.get(tableId)).views).toEqual({
|
||||||
[view.name]: expected,
|
[view.name]: expected,
|
||||||
})
|
})
|
||||||
|
expect(events.view.updated).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("handles view grouped filter events", async () => {
|
||||||
|
view.queryUI = {
|
||||||
|
logicalOperator: UILogicalOperator.ALL,
|
||||||
|
onEmptyFilter: EmptyFilterOption.RETURN_ALL,
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
logicalOperator: UILogicalOperator.ALL,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
operator: BasicOperator.EQUAL,
|
||||||
|
field: "newField",
|
||||||
|
value: "newValue",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
await config.api.viewV2.update(view)
|
||||||
|
expect(events.view.filterUpdated).not.toHaveBeenCalled()
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
view.queryUI.groups.push({
|
||||||
|
logicalOperator: UILogicalOperator.ALL,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
operator: BasicOperator.EQUAL,
|
||||||
|
field: "otherField",
|
||||||
|
value: "otherValue",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
await config.api.viewV2.update(view)
|
||||||
|
expect(events.view.filterUpdated).toHaveBeenCalledWith({
|
||||||
|
filterGroups: 2,
|
||||||
|
tableId: view.tableId,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("can update all fields", async () => {
|
it("can update all fields", async () => {
|
||||||
|
@ -1621,6 +1665,7 @@ if (descriptions.length) {
|
||||||
field: "age",
|
field: "age",
|
||||||
}
|
}
|
||||||
await config.api.viewV2.update(view)
|
await config.api.viewV2.update(view)
|
||||||
|
expect(events.view.calculationCreated).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
const { rows } = await config.api.row.search(view.id)
|
const { rows } = await config.api.row.search(view.id)
|
||||||
expect(rows).toHaveLength(2)
|
expect(rows).toHaveLength(2)
|
||||||
|
@ -2154,6 +2199,7 @@ if (descriptions.length) {
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
expect(events.view.viewJoinCreated).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("does not rename columns with the same name but from other tables", async () => {
|
it("does not rename columns with the same name but from other tables", async () => {
|
||||||
|
@ -2226,6 +2272,36 @@ if (descriptions.length) {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("handles events for changing column visibility from default false", async () => {
|
||||||
|
let auxTable = await createAuxTable()
|
||||||
|
let aux2Table = await createAuxTable()
|
||||||
|
|
||||||
|
const table = await createMainTable([
|
||||||
|
{ name: "aux", tableId: auxTable._id!, fk: "fk_aux" },
|
||||||
|
{ name: "aux2", tableId: aux2Table._id!, fk: "fk_aux2" },
|
||||||
|
])
|
||||||
|
|
||||||
|
const view = await createView(table._id!, {
|
||||||
|
aux: {
|
||||||
|
visible: true,
|
||||||
|
columns: {
|
||||||
|
name: { visible: false, readonly: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
aux2: {
|
||||||
|
visible: true,
|
||||||
|
columns: {
|
||||||
|
name: { visible: false, readonly: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// @ts-expect-error column exists above
|
||||||
|
view.schema.aux2.columns.name.visible = true
|
||||||
|
await config.api.viewV2.update(view)
|
||||||
|
expect(events.view.viewJoinCreated).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
it("updates all views references", async () => {
|
it("updates all views references", async () => {
|
||||||
let auxTable = await createAuxTable()
|
let auxTable = await createAuxTable()
|
||||||
|
|
||||||
|
|
|
@ -263,7 +263,7 @@ class AutomationBuilder extends BaseStepBuilder {
|
||||||
private automationConfig: Automation
|
private automationConfig: Automation
|
||||||
private config: TestConfiguration
|
private config: TestConfiguration
|
||||||
private triggerOutputs: any
|
private triggerOutputs: any
|
||||||
private triggerSet: boolean = false
|
private triggerSet = false
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
options: { name?: string; appId?: string; config?: TestConfiguration } = {}
|
options: { name?: string; appId?: string; config?: TestConfiguration } = {}
|
||||||
|
|
|
@ -107,7 +107,7 @@ if (mainDescriptions.length) {
|
||||||
const table = response.datasource.entities?.["binaryTable"]
|
const table = response.datasource.entities?.["binaryTable"]
|
||||||
expect(table).toBeDefined()
|
expect(table).toBeDefined()
|
||||||
expect(table?.schema.id.externalType).toBe("bytea")
|
expect(table?.schema.id.externalType).toBe("bytea")
|
||||||
const row = await config.api.row.save(table?._id!, {
|
const row = await config.api.row.save(table!._id!, {
|
||||||
id: "1111",
|
id: "1111",
|
||||||
column1: "hello",
|
column1: "hello",
|
||||||
column2: 222,
|
column2: 222,
|
||||||
|
|
|
@ -223,7 +223,7 @@ const COLUMN_DEFINITION_METADATA: Record<string, ColumnDefinitionMetadata> = {
|
||||||
|
|
||||||
class SqlServerIntegration extends Sql implements DatasourcePlus {
|
class SqlServerIntegration extends Sql implements DatasourcePlus {
|
||||||
private readonly config: MSSQLConfig
|
private readonly config: MSSQLConfig
|
||||||
private index: number = 0
|
private index = 0
|
||||||
private client?: sqlServer.ConnectionPool
|
private client?: sqlServer.ConnectionPool
|
||||||
|
|
||||||
MASTER_TABLES = [
|
MASTER_TABLES = [
|
||||||
|
|
|
@ -116,7 +116,7 @@ const OracleContraintTypes = {
|
||||||
|
|
||||||
class OracleIntegration extends Sql implements DatasourcePlus {
|
class OracleIntegration extends Sql implements DatasourcePlus {
|
||||||
private readonly config: OracleConfig
|
private readonly config: OracleConfig
|
||||||
private index: number = 1
|
private index = 1
|
||||||
|
|
||||||
private static readonly COLUMNS_SQL = `
|
private static readonly COLUMNS_SQL = `
|
||||||
SELECT
|
SELECT
|
||||||
|
|
|
@ -149,7 +149,7 @@ const SCHEMA: Integration = {
|
||||||
class PostgresIntegration extends Sql implements DatasourcePlus {
|
class PostgresIntegration extends Sql implements DatasourcePlus {
|
||||||
private readonly client: Client
|
private readonly client: Client
|
||||||
private readonly config: PostgresConfig
|
private readonly config: PostgresConfig
|
||||||
private index: number = 1
|
private index = 1
|
||||||
private open: boolean
|
private open: boolean
|
||||||
|
|
||||||
PRIMARY_KEYS_SQL = () => `
|
PRIMARY_KEYS_SQL = () => `
|
||||||
|
@ -252,7 +252,7 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async internalQuery(query: SqlQuery, close: boolean = true) {
|
async internalQuery(query: SqlQuery, close = true) {
|
||||||
if (!this.open) {
|
if (!this.open) {
|
||||||
await this.openConnection()
|
await this.openConnection()
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,7 +129,7 @@ class RedisIntegration {
|
||||||
return this.client.quit()
|
return this.client.quit()
|
||||||
}
|
}
|
||||||
|
|
||||||
async redisContext(query: Function) {
|
async redisContext<T>(query: () => Promise<T>) {
|
||||||
try {
|
try {
|
||||||
return await query()
|
return await query()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -171,7 +171,7 @@ export class RestIntegration implements IntegrationBase {
|
||||||
path.basename(parse(contentDisposition).parameters?.filename) || ""
|
path.basename(parse(contentDisposition).parameters?.filename) || ""
|
||||||
}
|
}
|
||||||
|
|
||||||
let triedParsing: boolean = false,
|
let triedParsing = false,
|
||||||
responseTxt: string | undefined
|
responseTxt: string | undefined
|
||||||
try {
|
try {
|
||||||
if (filename) {
|
if (filename) {
|
||||||
|
@ -313,7 +313,9 @@ export class RestIntegration implements IntegrationBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Util to add pagination values to a certain body type
|
// Util to add pagination values to a certain body type
|
||||||
const addPaginationToBody = (insertFn: Function) => {
|
const addPaginationToBody = (
|
||||||
|
insertFn: (pageParam: string, page?: string | number) => void
|
||||||
|
) => {
|
||||||
if (pagination?.location === "body") {
|
if (pagination?.location === "body") {
|
||||||
if (pagination?.pageParam && paginationValues?.page != null) {
|
if (pagination?.pageParam && paginationValues?.page != null) {
|
||||||
insertFn(pagination.pageParam, paginationValues.page)
|
insertFn(pagination.pageParam, paginationValues.page)
|
||||||
|
|
|
@ -389,25 +389,24 @@ describe("Google Sheets Integration", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: this gets the error "Sheet is not large enough to fit 27 columns. Resize the sheet first."
|
// TODO: this gets the error "Sheet is not large enough to fit 27 columns. Resize the sheet first."
|
||||||
// eslint-disable-next-line jest/no-commented-out-tests
|
it.skip("should be able to add a new column", async () => {
|
||||||
// it("should be able to add a new column", async () => {
|
const updatedTable = await config.api.table.save({
|
||||||
// const updatedTable = await config.api.table.save({
|
...table,
|
||||||
// ...table,
|
schema: {
|
||||||
// schema: {
|
...table.schema,
|
||||||
// ...table.schema,
|
newColumn: {
|
||||||
// newColumn: {
|
name: "newColumn",
|
||||||
// name: "newColumn",
|
type: FieldType.STRING,
|
||||||
// type: FieldType.STRING,
|
},
|
||||||
// },
|
},
|
||||||
// },
|
})
|
||||||
// })
|
|
||||||
|
|
||||||
// expect(updatedTable.schema.newColumn).toBeDefined()
|
expect(updatedTable.schema.newColumn).toBeDefined()
|
||||||
|
|
||||||
// expect(mock.cell("A1")).toEqual("name")
|
expect(mock.cell("A1")).toEqual("name")
|
||||||
// expect(mock.cell("B1")).toEqual("description")
|
expect(mock.cell("B1")).toEqual("description")
|
||||||
// expect(mock.cell("C1")).toEqual("newColumn")
|
expect(mock.cell("C1")).toEqual("newColumn")
|
||||||
// })
|
})
|
||||||
|
|
||||||
it("should be able to delete a column", async () => {
|
it("should be able to delete a column", async () => {
|
||||||
const row = await config.api.row.save(table._id!, {
|
const row = await config.api.row.save(table._id!, {
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { RestIntegration } from "../rest"
|
||||||
import { BodyType, RestAuthType } from "@budibase/types"
|
import { BodyType, RestAuthType } from "@budibase/types"
|
||||||
import { Response } from "node-fetch"
|
import { Response } from "node-fetch"
|
||||||
import TestConfiguration from "../../../src/tests/utilities/TestConfiguration"
|
import TestConfiguration from "../../../src/tests/utilities/TestConfiguration"
|
||||||
|
import { createServer } from "http"
|
||||||
|
import { AddressInfo } from "net"
|
||||||
|
|
||||||
const UUID_REGEX =
|
const UUID_REGEX =
|
||||||
"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"
|
"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"
|
||||||
|
@ -455,29 +457,27 @@ describe("REST Integration", () => {
|
||||||
// NOTE(samwho): it seems like this code doesn't actually work because it requires
|
// NOTE(samwho): it seems like this code doesn't actually work because it requires
|
||||||
// node-fetch >=3, and we're not on that because upgrading to it produces errors to
|
// node-fetch >=3, and we're not on that because upgrading to it produces errors to
|
||||||
// do with ESM that are above my pay grade.
|
// do with ESM that are above my pay grade.
|
||||||
|
it.skip("doesn't fail when legacyHttpParser is set", async () => {
|
||||||
|
const server = createServer((req, res) => {
|
||||||
|
res.writeHead(200, {
|
||||||
|
"Transfer-Encoding": "chunked",
|
||||||
|
"Content-Length": "10",
|
||||||
|
})
|
||||||
|
res.end(JSON.stringify({ foo: "bar" }))
|
||||||
|
})
|
||||||
|
|
||||||
// eslint-disable-next-line jest/no-commented-out-tests
|
server.listen()
|
||||||
// it("doesn't fail when legacyHttpParser is set", async () => {
|
await new Promise(resolve => server.once("listening", resolve))
|
||||||
// const server = createServer((req, res) => {
|
|
||||||
// res.writeHead(200, {
|
|
||||||
// "Transfer-Encoding": "chunked",
|
|
||||||
// "Content-Length": "10",
|
|
||||||
// })
|
|
||||||
// res.end(JSON.stringify({ foo: "bar" }))
|
|
||||||
// })
|
|
||||||
|
|
||||||
// server.listen()
|
const address = server.address() as AddressInfo
|
||||||
// await new Promise(resolve => server.once("listening", resolve))
|
|
||||||
|
|
||||||
// const address = server.address() as AddressInfo
|
const integration = new RestIntegration({
|
||||||
|
url: `http://localhost:${address.port}`,
|
||||||
// const integration = new RestIntegration({
|
legacyHttpParser: true,
|
||||||
// url: `http://localhost:${address.port}`,
|
})
|
||||||
// legacyHttpParser: true,
|
const { data } = await integration.read({})
|
||||||
// })
|
expect(data).toEqual({ foo: "bar" })
|
||||||
// const { data } = await integration.read({})
|
})
|
||||||
// expect(data).toEqual({ foo: "bar" })
|
|
||||||
// })
|
|
||||||
|
|
||||||
it("doesn't fail when legacyHttpParser is true", async () => {
|
it("doesn't fail when legacyHttpParser is true", async () => {
|
||||||
nock("https://example.com").get("/").reply(200, { foo: "bar" })
|
nock("https://example.com").get("/").reply(200, { foo: "bar" })
|
||||||
|
|
|
@ -144,10 +144,10 @@ describe("Captures of real examples", () => {
|
||||||
queryJson
|
queryJson
|
||||||
)
|
)
|
||||||
const filters = queryJson.filters
|
const filters = queryJson.filters
|
||||||
const notEqualsValue = Object.values(filters?.notEqual!)[0]
|
const notEqualsValue = Object.values(filters!.notEqual!)[0]
|
||||||
const rangeValue: { high?: string | number; low?: string | number } =
|
const rangeValue: { high?: string | number; low?: string | number } =
|
||||||
Object.values(filters?.range!)[0]
|
Object.values(filters!.range!)[0]
|
||||||
const equalValue = Object.values(filters?.equal!)[0]
|
const equalValue = Object.values(filters!.equal!)[0]
|
||||||
|
|
||||||
expect(query).toEqual({
|
expect(query).toEqual({
|
||||||
bindings: [
|
bindings: [
|
||||||
|
@ -245,7 +245,7 @@ describe("Captures of real examples", () => {
|
||||||
tableNames.push(generator.guid())
|
tableNames.push(generator.guid())
|
||||||
}
|
}
|
||||||
const aliasing = new AliasTables(tableNames)
|
const aliasing = new AliasTables(tableNames)
|
||||||
let alias: string = ""
|
let alias = ""
|
||||||
for (let table of tableNames) {
|
for (let table of tableNames) {
|
||||||
alias = aliasing.getAlias(table)
|
alias = aliasing.getAlias(table)
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ export class IsolatedVM implements VM {
|
||||||
|
|
||||||
this.addToContext({
|
this.addToContext({
|
||||||
helpersStripProtocol: new ivm.Callback((str: string) => {
|
helpersStripProtocol: new ivm.Callback((str: string) => {
|
||||||
var parsed = url.parse(str) as any
|
let parsed = url.parse(str) as any
|
||||||
parsed.protocol = ""
|
parsed.protocol = ""
|
||||||
return parsed.format()
|
return parsed.format()
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -50,7 +50,7 @@ async function updateAppUpdatedAt(ctx: UserCtx) {
|
||||||
const metadata = await db.get<any>(DocumentType.APP_METADATA)
|
const metadata = await db.get<any>(DocumentType.APP_METADATA)
|
||||||
metadata.updatedAt = new Date().toISOString()
|
metadata.updatedAt = new Date().toISOString()
|
||||||
|
|
||||||
metadata.updatedBy = getGlobalIDFromUserMetadataID(ctx.user?.userId!)
|
metadata.updatedBy = getGlobalIDFromUserMetadataID(ctx.user!.userId!)
|
||||||
|
|
||||||
const response = await db.put(metadata)
|
const response = await db.put(metadata)
|
||||||
metadata._rev = response.rev
|
metadata._rev = response.rev
|
||||||
|
@ -59,9 +59,7 @@ async function updateAppUpdatedAt(ctx: UserCtx) {
|
||||||
await setDebounce(appId, DEBOUNCE_TIME_SEC)
|
await setDebounce(appId, DEBOUNCE_TIME_SEC)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
// if a 409 occurs, then multiple clients connected at the same time - ignore
|
// if a 409 occurs, then multiple clients connected at the same time - ignore
|
||||||
if (err?.status === 409) {
|
if (err && err.status !== 409) {
|
||||||
return
|
|
||||||
} else {
|
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,24 +7,6 @@ export const backfill = async (appDb: Database, timestamp: string | number) => {
|
||||||
|
|
||||||
for (const table of tables) {
|
for (const table of tables) {
|
||||||
await events.table.created(table, timestamp)
|
await events.table.created(table, timestamp)
|
||||||
|
|
||||||
if (table.views) {
|
|
||||||
for (const view of Object.values(table.views)) {
|
|
||||||
if (sdk.views.isV2(view)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
await events.view.created(view, timestamp)
|
|
||||||
|
|
||||||
if (view.calculation) {
|
|
||||||
await events.view.calculationCreated(view, timestamp)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (view.filters?.length) {
|
|
||||||
await events.view.filterCreated(view, timestamp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tables.length
|
return tables.length
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export const runQuotaMigration = async (migration: Function) => {
|
export const runQuotaMigration = async (migration: () => Promise<void>) => {
|
||||||
await migration()
|
await migration()
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,16 +73,12 @@ describe("migrations", () => {
|
||||||
expect(events.query.created).toHaveBeenCalledTimes(2)
|
expect(events.query.created).toHaveBeenCalledTimes(2)
|
||||||
expect(events.role.created).toHaveBeenCalledTimes(3) // created roles + admin (created on table creation)
|
expect(events.role.created).toHaveBeenCalledTimes(3) // created roles + admin (created on table creation)
|
||||||
expect(events.table.created).toHaveBeenCalledTimes(3)
|
expect(events.table.created).toHaveBeenCalledTimes(3)
|
||||||
expect(events.view.created).toHaveBeenCalledTimes(2)
|
|
||||||
expect(events.view.calculationCreated).toHaveBeenCalledTimes(1)
|
|
||||||
expect(events.view.filterCreated).toHaveBeenCalledTimes(1)
|
|
||||||
expect(events.screen.created).toHaveBeenCalledTimes(2)
|
|
||||||
expect(events.backfill.appSucceeded).toHaveBeenCalledTimes(2)
|
expect(events.backfill.appSucceeded).toHaveBeenCalledTimes(2)
|
||||||
|
|
||||||
// to make sure caching is working as expected
|
// to make sure caching is working as expected
|
||||||
expect(
|
expect(
|
||||||
events.processors.analyticsProcessor.processEvent
|
events.processors.analyticsProcessor.processEvent
|
||||||
).toHaveBeenCalledTimes(24) // Addtion of of the events above
|
).toHaveBeenCalledTimes(20) // Addition of of the events above
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -104,8 +104,6 @@ export async function getDependantResources(
|
||||||
return p
|
return p
|
||||||
}, {} as Record<string, number>)
|
}, {} as Record<string, number>)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updatePermissionOnRole(
|
export async function updatePermissionOnRole(
|
||||||
|
|
|
@ -437,7 +437,7 @@ export async function search(
|
||||||
)
|
)
|
||||||
|
|
||||||
// check for pagination final row
|
// check for pagination final row
|
||||||
let nextRow: boolean = false
|
let nextRow = false
|
||||||
if (paginate && params.limit && rows.length > params.limit) {
|
if (paginate && params.limit && rows.length > params.limit) {
|
||||||
// remove the extra row that confirmed if there is another row to move to
|
// remove the extra row that confirmed if there is another row to move to
|
||||||
nextRow = true
|
nextRow = true
|
||||||
|
|
|
@ -281,7 +281,7 @@ export async function save(
|
||||||
tableToSave.sql = true
|
tableToSave.sql = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return { datasource: updatedDatasource, table: tableToSave }
|
return { datasource: updatedDatasource, table: tableToSave, oldTable }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function destroy(datasourceId: string, table: Table) {
|
export async function destroy(datasourceId: string, table: Table) {
|
||||||
|
|
|
@ -171,7 +171,7 @@ export async function save(
|
||||||
}
|
}
|
||||||
// has to run after, make sure it has _id
|
// has to run after, make sure it has _id
|
||||||
await runStaticFormulaChecks(table, { oldTable, deletion: false })
|
await runStaticFormulaChecks(table, { oldTable, deletion: false })
|
||||||
return { table }
|
return { table, oldTable }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function destroy(table: Table) {
|
export async function destroy(table: Table) {
|
||||||
|
|
|
@ -63,7 +63,7 @@ export async function create(
|
||||||
export async function update(
|
export async function update(
|
||||||
tableId: string,
|
tableId: string,
|
||||||
view: Readonly<ViewV2>
|
view: Readonly<ViewV2>
|
||||||
): Promise<ViewV2> {
|
): Promise<{ view: Readonly<ViewV2>; existingView: ViewV2 }> {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
|
|
||||||
const { datasourceId, tableName } = breakExternalTableId(tableId)
|
const { datasourceId, tableName } = breakExternalTableId(tableId)
|
||||||
|
@ -87,7 +87,7 @@ export async function update(
|
||||||
delete views[existingView.name]
|
delete views[existingView.name]
|
||||||
views[view.name] = view
|
views[view.name] = view
|
||||||
await db.put(ds)
|
await db.put(ds)
|
||||||
return view
|
return { view, existingView } as { view: ViewV2; existingView: ViewV2 }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(viewId: string): Promise<ViewV2> {
|
export async function remove(viewId: string): Promise<ViewV2> {
|
||||||
|
|
|
@ -315,7 +315,10 @@ export async function create(
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function update(tableId: string, view: ViewV2): Promise<ViewV2> {
|
export async function update(
|
||||||
|
tableId: string,
|
||||||
|
view: ViewV2
|
||||||
|
): Promise<{ view: ViewV2; existingView: ViewV2 }> {
|
||||||
await guardViewSchema(tableId, view)
|
await guardViewSchema(tableId, view)
|
||||||
|
|
||||||
return pickApi(tableId).update(tableId, view)
|
return pickApi(tableId).update(tableId, view)
|
||||||
|
|
|
@ -54,7 +54,7 @@ export async function create(
|
||||||
export async function update(
|
export async function update(
|
||||||
tableId: string,
|
tableId: string,
|
||||||
view: Readonly<ViewV2>
|
view: Readonly<ViewV2>
|
||||||
): Promise<ViewV2> {
|
): Promise<{ view: ViewV2; existingView: ViewV2 }> {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const table = await sdk.tables.getTable(tableId)
|
const table = await sdk.tables.getTable(tableId)
|
||||||
table.views ??= {}
|
table.views ??= {}
|
||||||
|
@ -76,7 +76,7 @@ export async function update(
|
||||||
delete table.views[existingView.name]
|
delete table.views[existingView.name]
|
||||||
table.views[view.name] = view
|
table.views[view.name] = view
|
||||||
await db.put(table)
|
await db.put(table)
|
||||||
return view
|
return { view, existingView } as { view: ViewV2; existingView: ViewV2 }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(viewId: string): Promise<ViewV2> {
|
export async function remove(viewId: string): Promise<ViewV2> {
|
||||||
|
|
|
@ -47,7 +47,7 @@ describe("check BB_ADMIN environment variables", () => {
|
||||||
})
|
})
|
||||||
expect(user).toBeDefined()
|
expect(user).toBeDefined()
|
||||||
expect(user?.password).toBeDefined()
|
expect(user?.password).toBeDefined()
|
||||||
expect(await utils.compare(PASSWORD, user?.password!)).toEqual(
|
expect(await utils.compare(PASSWORD, user!.password!)).toEqual(
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -535,7 +535,7 @@ export function basicRow(tableId: string) {
|
||||||
export function basicLinkedRow(
|
export function basicLinkedRow(
|
||||||
tableId: string,
|
tableId: string,
|
||||||
linkedRowId: string,
|
linkedRowId: string,
|
||||||
linkField: string = "link"
|
linkField = "link"
|
||||||
) {
|
) {
|
||||||
// this is based on the basic linked tables you get from the test configuration
|
// this is based on the basic linked tables you get from the test configuration
|
||||||
return {
|
return {
|
||||||
|
@ -586,14 +586,14 @@ export function basicUser(role: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function basicScreen(route: string = "/") {
|
export function basicScreen(route = "/") {
|
||||||
return createHomeScreen({
|
return createHomeScreen({
|
||||||
roleId: BUILTIN_ROLE_IDS.BASIC,
|
roleId: BUILTIN_ROLE_IDS.BASIC,
|
||||||
route,
|
route,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function powerScreen(route: string = "/") {
|
export function powerScreen(route = "/") {
|
||||||
return createHomeScreen({
|
return createHomeScreen({
|
||||||
roleId: BUILTIN_ROLE_IDS.POWER,
|
roleId: BUILTIN_ROLE_IDS.POWER,
|
||||||
route,
|
route,
|
||||||
|
|
|
@ -78,7 +78,7 @@ export async function getCachedSelf(
|
||||||
// this has to be tenant aware, can't depend on the context to find it out
|
// this has to be tenant aware, can't depend on the context to find it out
|
||||||
// running some middlewares before the tenancy causes context to break
|
// running some middlewares before the tenancy causes context to break
|
||||||
const user = await cache.user.getUser({
|
const user = await cache.user.getUser({
|
||||||
userId: ctx.user?._id!,
|
userId: ctx.user!._id!,
|
||||||
})
|
})
|
||||||
return processUser(user, { appId })
|
return processUser(user, { appId })
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
export async function retry<T extends (...arg0: any[]) => any>(
|
export async function retry<T extends (...arg0: any[]) => any>(
|
||||||
fn: T,
|
fn: T,
|
||||||
maxTry: number = 5,
|
maxTry = 5,
|
||||||
retryCount = 1
|
retryCount = 1
|
||||||
): Promise<Awaited<ReturnType<T>>> {
|
): Promise<Awaited<ReturnType<T>>> {
|
||||||
const currRetry = typeof retryCount === "number" ? retryCount : 1
|
const currRetry = typeof retryCount === "number" ? retryCount : 1
|
||||||
|
|
|
@ -92,7 +92,7 @@ export default class BuilderSocket extends BaseSocket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateUser(socket: Socket, patch: Object) {
|
async updateUser(socket: Socket, patch: object) {
|
||||||
await super.updateUser(socket, {
|
await super.updateUser(socket, {
|
||||||
builderMetadata: {
|
builderMetadata: {
|
||||||
...socket.data.builderMetadata,
|
...socket.data.builderMetadata,
|
||||||
|
|
|
@ -70,7 +70,7 @@ export default class GridSocket extends BaseSocket {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateUser(socket: Socket, patch: Object) {
|
async updateUser(socket: Socket, patch: object) {
|
||||||
await super.updateUser(socket, {
|
await super.updateUser(socket, {
|
||||||
gridMetadata: {
|
gridMetadata: {
|
||||||
...socket.data.gridMetadata,
|
...socket.data.gridMetadata,
|
||||||
|
|
|
@ -58,7 +58,7 @@ export const createContext = (
|
||||||
export const runMiddlewares = async (
|
export const runMiddlewares = async (
|
||||||
ctx: any,
|
ctx: any,
|
||||||
middlewares: any[],
|
middlewares: any[],
|
||||||
callback: Function
|
callback: () => Promise<void>
|
||||||
) => {
|
) => {
|
||||||
if (!middlewares[0]) {
|
if (!middlewares[0]) {
|
||||||
await callback()
|
await callback()
|
||||||
|
|
|
@ -33,7 +33,7 @@ export class BaseSocket {
|
||||||
constructor(
|
constructor(
|
||||||
app: Koa,
|
app: Koa,
|
||||||
server: http.Server,
|
server: http.Server,
|
||||||
path: string = "/",
|
path = "/",
|
||||||
additionalMiddlewares?: any[]
|
additionalMiddlewares?: any[]
|
||||||
) {
|
) {
|
||||||
this.app = app
|
this.app = app
|
||||||
|
@ -57,7 +57,7 @@ export class BaseSocket {
|
||||||
const ctx = createContext(this.app, socket)
|
const ctx = createContext(this.app, socket)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await runMiddlewares(ctx, middlewares, () => {
|
await runMiddlewares(ctx, middlewares, async () => {
|
||||||
// Middlewares are finished
|
// Middlewares are finished
|
||||||
// Extract some data from our enriched koa context to persist
|
// Extract some data from our enriched koa context to persist
|
||||||
// as metadata for the socket
|
// as metadata for the socket
|
||||||
|
@ -250,7 +250,7 @@ export class BaseSocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates a connected user's metadata, assuming a room change is not required.
|
// Updates a connected user's metadata, assuming a room change is not required.
|
||||||
async updateUser(socket: Socket, patch: Object) {
|
async updateUser(socket: Socket, patch: object) {
|
||||||
socket.data = {
|
socket.data = {
|
||||||
...socket.data,
|
...socket.data,
|
||||||
...patch,
|
...patch,
|
||||||
|
|
|
@ -33,7 +33,7 @@ function improveErrors(errors: string[]): string[] {
|
||||||
|
|
||||||
export function getNextExecutionDates(
|
export function getNextExecutionDates(
|
||||||
cronExpression: string,
|
cronExpression: string,
|
||||||
limit: number = 4
|
limit = 4
|
||||||
): string[] {
|
): string[] {
|
||||||
const parsed = cronParser.parseExpression(cronExpression)
|
const parsed = cronParser.parseExpression(cronExpression)
|
||||||
const nextRuns = []
|
const nextRuns = []
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable no-template-curly-in-string */
|
||||||
import { convertToJS } from "../src/index"
|
import { convertToJS } from "../src/index"
|
||||||
|
|
||||||
function checkLines(response: string, lines: string[]) {
|
function checkLines(response: string, lines: string[]) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ function tryParseJson(str: string) {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(str.replace(/'/g, '"'))
|
return JSON.parse(str.replace(/'/g, '"'))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return
|
// do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -128,13 +128,13 @@ export interface Database {
|
||||||
name: string
|
name: string
|
||||||
|
|
||||||
exists(): Promise<boolean>
|
exists(): Promise<boolean>
|
||||||
|
exists(docId: string): Promise<boolean>
|
||||||
/**
|
/**
|
||||||
* @deprecated the plan is to get everything using `tryGet` instead, then rename
|
* @deprecated the plan is to get everything using `tryGet` instead, then rename
|
||||||
* `tryGet` to `get`.
|
* `tryGet` to `get`.
|
||||||
*/
|
*/
|
||||||
get<T extends Document>(id?: string): Promise<T>
|
get<T extends Document>(id?: string): Promise<T>
|
||||||
tryGet<T extends Document>(id?: string): Promise<T | undefined>
|
tryGet<T extends Document>(id?: string): Promise<T | undefined>
|
||||||
exists(docId: string): Promise<boolean>
|
|
||||||
getMultiple<T extends Document>(
|
getMultiple<T extends Document>(
|
||||||
ids: string[],
|
ids: string[],
|
||||||
opts?: { allowMissing?: boolean; excludeDocs?: boolean }
|
opts?: { allowMissing?: boolean; excludeDocs?: boolean }
|
||||||
|
|
|
@ -118,6 +118,7 @@ export enum Event {
|
||||||
VIEW_CALCULATION_CREATED = "view:calculation:created",
|
VIEW_CALCULATION_CREATED = "view:calculation:created",
|
||||||
VIEW_CALCULATION_UPDATED = "view:calculation:updated",
|
VIEW_CALCULATION_UPDATED = "view:calculation:updated",
|
||||||
VIEW_CALCULATION_DELETED = "view:calculation:deleted",
|
VIEW_CALCULATION_DELETED = "view:calculation:deleted",
|
||||||
|
VIEW_JOIN_CREATED = "view:join:created",
|
||||||
|
|
||||||
// ROWS
|
// ROWS
|
||||||
ROWS_CREATED = "rows:created",
|
ROWS_CREATED = "rows:created",
|
||||||
|
@ -192,6 +193,9 @@ export enum Event {
|
||||||
// AUDIT LOG
|
// AUDIT LOG
|
||||||
AUDIT_LOGS_FILTERED = "audit_log:filtered",
|
AUDIT_LOGS_FILTERED = "audit_log:filtered",
|
||||||
AUDIT_LOGS_DOWNLOADED = "audit_log:downloaded",
|
AUDIT_LOGS_DOWNLOADED = "audit_log:downloaded",
|
||||||
|
|
||||||
|
// ROW ACTION
|
||||||
|
ROW_ACTION_CREATED = "row_action:created",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserGroupSyncEvents: Event[] = [
|
export const UserGroupSyncEvents: Event[] = [
|
||||||
|
@ -376,6 +380,7 @@ export const AuditedEventFriendlyName: Record<Event, string | undefined> = {
|
||||||
[Event.VIEW_CALCULATION_CREATED]: undefined,
|
[Event.VIEW_CALCULATION_CREATED]: undefined,
|
||||||
[Event.VIEW_CALCULATION_UPDATED]: undefined,
|
[Event.VIEW_CALCULATION_UPDATED]: undefined,
|
||||||
[Event.VIEW_CALCULATION_DELETED]: undefined,
|
[Event.VIEW_CALCULATION_DELETED]: undefined,
|
||||||
|
[Event.VIEW_JOIN_CREATED]: undefined,
|
||||||
|
|
||||||
// SERVED - NOT AUDITED
|
// SERVED - NOT AUDITED
|
||||||
[Event.SERVED_BUILDER]: undefined,
|
[Event.SERVED_BUILDER]: undefined,
|
||||||
|
@ -395,6 +400,9 @@ export const AuditedEventFriendlyName: Record<Event, string | undefined> = {
|
||||||
// AUDIT LOG - NOT AUDITED
|
// AUDIT LOG - NOT AUDITED
|
||||||
[Event.AUDIT_LOGS_FILTERED]: undefined,
|
[Event.AUDIT_LOGS_FILTERED]: undefined,
|
||||||
[Event.AUDIT_LOGS_DOWNLOADED]: undefined,
|
[Event.AUDIT_LOGS_DOWNLOADED]: undefined,
|
||||||
|
|
||||||
|
// ROW ACTIONS - NOT AUDITED
|
||||||
|
[Event.ROW_ACTION_CREATED]: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
// properties added at the final stage of the event pipeline
|
// properties added at the final stage of the event pipeline
|
||||||
|
|
|
@ -24,3 +24,4 @@ export * from "./plugin"
|
||||||
export * from "./backup"
|
export * from "./backup"
|
||||||
export * from "./environmentVariable"
|
export * from "./environmentVariable"
|
||||||
export * from "./auditLog"
|
export * from "./auditLog"
|
||||||
|
export * from "./rowAction"
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { BaseEvent } from "./event"
|
||||||
|
|
||||||
|
export interface RowActionCreatedEvent extends BaseEvent {
|
||||||
|
name: string
|
||||||
|
automationId: string
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { BaseEvent, TableExportFormat } from "./event"
|
import { BaseEvent, TableExportFormat } from "./event"
|
||||||
|
import { AIOperationEnum } from "../ai"
|
||||||
|
|
||||||
export interface TableCreatedEvent extends BaseEvent {
|
export interface TableCreatedEvent extends BaseEvent {
|
||||||
tableId: string
|
tableId: string
|
||||||
|
@ -9,6 +10,8 @@ export interface TableCreatedEvent extends BaseEvent {
|
||||||
|
|
||||||
export interface TableUpdatedEvent extends BaseEvent {
|
export interface TableUpdatedEvent extends BaseEvent {
|
||||||
tableId: string
|
tableId: string
|
||||||
|
defaultValues: boolean | undefined
|
||||||
|
aiColumn: AIOperationEnum | undefined
|
||||||
audited: {
|
audited: {
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { ViewCalculation } from "../../documents"
|
import { CalculationType, ViewCalculation, ViewV2Type } from "../../documents"
|
||||||
import { BaseEvent, TableExportFormat } from "./event"
|
import { BaseEvent, TableExportFormat } from "./event"
|
||||||
|
|
||||||
export interface ViewCreatedEvent extends BaseEvent {
|
export interface ViewCreatedEvent extends BaseEvent {
|
||||||
|
name: string
|
||||||
|
type?: ViewV2Type
|
||||||
tableId: string
|
tableId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,10 +22,12 @@ export interface ViewExportedEvent extends BaseEvent {
|
||||||
|
|
||||||
export interface ViewFilterCreatedEvent extends BaseEvent {
|
export interface ViewFilterCreatedEvent extends BaseEvent {
|
||||||
tableId: string
|
tableId: string
|
||||||
|
filterGroups: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ViewFilterUpdatedEvent extends BaseEvent {
|
export interface ViewFilterUpdatedEvent extends BaseEvent {
|
||||||
tableId: string
|
tableId: string
|
||||||
|
filterGroups: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ViewFilterDeletedEvent extends BaseEvent {
|
export interface ViewFilterDeletedEvent extends BaseEvent {
|
||||||
|
@ -32,7 +36,7 @@ export interface ViewFilterDeletedEvent extends BaseEvent {
|
||||||
|
|
||||||
export interface ViewCalculationCreatedEvent extends BaseEvent {
|
export interface ViewCalculationCreatedEvent extends BaseEvent {
|
||||||
tableId: string
|
tableId: string
|
||||||
calculation: ViewCalculation
|
calculation: CalculationType
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ViewCalculationUpdatedEvent extends BaseEvent {
|
export interface ViewCalculationUpdatedEvent extends BaseEvent {
|
||||||
|
@ -44,3 +48,7 @@ export interface ViewCalculationDeletedEvent extends BaseEvent {
|
||||||
tableId: string
|
tableId: string
|
||||||
calculation: ViewCalculation
|
calculation: ViewCalculation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ViewJoinCreatedEvent extends BaseEvent {
|
||||||
|
tableId: string
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
import { Database } from "./db"
|
||||||
|
|
||||||
export interface Migration extends MigrationDefinition {
|
export interface Migration extends MigrationDefinition {
|
||||||
appOpts?: object
|
appOpts?: object
|
||||||
fn: Function
|
fn: (db: Database) => Promise<void>
|
||||||
silent?: boolean
|
silent?: boolean
|
||||||
preventRetry?: boolean
|
preventRetry?: boolean
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ describe("/api/global/auth", () => {
|
||||||
describe("POST /api/global/auth/:tenantId/login", () => {
|
describe("POST /api/global/auth/:tenantId/login", () => {
|
||||||
it("logs in with correct credentials", async () => {
|
it("logs in with correct credentials", async () => {
|
||||||
const tenantId = config.tenantId!
|
const tenantId = config.tenantId!
|
||||||
const email = config.user?.email!
|
const email = config.user!.email!
|
||||||
const password = config.userPassword
|
const password = config.userPassword
|
||||||
|
|
||||||
const response = await config.api.auth.login(tenantId, email, password)
|
const response = await config.api.auth.login(tenantId, email, password)
|
||||||
|
@ -65,7 +65,7 @@ describe("/api/global/auth", () => {
|
||||||
|
|
||||||
it("should return 403 with incorrect credentials", async () => {
|
it("should return 403 with incorrect credentials", async () => {
|
||||||
const tenantId = config.tenantId!
|
const tenantId = config.tenantId!
|
||||||
const email = config.user?.email!
|
const email = config.user!.email!
|
||||||
const password = "incorrect123"
|
const password = "incorrect123"
|
||||||
|
|
||||||
const response = await config.api.auth.login(
|
const response = await config.api.auth.login(
|
||||||
|
|
|
@ -343,7 +343,7 @@ describe("/api/global/users", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should not allow a user to update their own admin/builder status", async () => {
|
it("should not allow a user to update their own admin/builder status", async () => {
|
||||||
const user = (await config.api.users.getUser(config.user?._id!))
|
const user = (await config.api.users.getUser(config.user!._id!))
|
||||||
.body as User
|
.body as User
|
||||||
await config.api.users.saveUser({
|
await config.api.users.saveUser({
|
||||||
...user,
|
...user,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Ctx } from "@budibase/types"
|
import { Ctx } from "@budibase/types"
|
||||||
|
|
||||||
export const handleScimBody = (ctx: Ctx, next: any) => {
|
export const handleScimBody = (ctx: Ctx, next: any) => {
|
||||||
var type = ctx.req.headers["content-type"] || ""
|
let type = ctx.req.headers["content-type"] || ""
|
||||||
type = type.split(";")[0]
|
type = type.split(";")[0]
|
||||||
|
|
||||||
if (type === "application/scim+json") {
|
if (type === "application/scim+json") {
|
||||||
|
|
|
@ -40,10 +40,7 @@ export const getMetadata = async (
|
||||||
try {
|
try {
|
||||||
return await db.get(accountId)
|
return await db.get(accountId)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.status === 404) {
|
if (e.status !== 404) {
|
||||||
// do nothing
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
import { Account, AccountMetadata } from "@budibase/types"
|
import { Account, AccountMetadata } from "@budibase/types"
|
||||||
import TestConfiguration from "../TestConfiguration"
|
|
||||||
import { TestAPI } from "./base"
|
import { TestAPI } from "./base"
|
||||||
|
|
||||||
export class AccountAPI extends TestAPI {
|
export class AccountAPI extends TestAPI {
|
||||||
constructor(config: TestConfiguration) {
|
|
||||||
super(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
saveMetadata = async (account: Account) => {
|
saveMetadata = async (account: Account) => {
|
||||||
const res = await this.request
|
const res = await this.request
|
||||||
.put(`/api/system/accounts/${account.accountId}/metadata`)
|
.put(`/api/system/accounts/${account.accountId}/metadata`)
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
import { AuditLogSearchParams, SearchAuditLogsResponse } from "@budibase/types"
|
import { AuditLogSearchParams, SearchAuditLogsResponse } from "@budibase/types"
|
||||||
import TestConfiguration from "../TestConfiguration"
|
|
||||||
import { TestAPI } from "./base"
|
import { TestAPI } from "./base"
|
||||||
|
|
||||||
export class AuditLogAPI extends TestAPI {
|
export class AuditLogAPI extends TestAPI {
|
||||||
constructor(config: TestConfiguration) {
|
|
||||||
super(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
search = async (search: AuditLogSearchParams) => {
|
search = async (search: AuditLogSearchParams) => {
|
||||||
const res = await this.request
|
const res = await this.request
|
||||||
.post("/api/global/auditlogs/search")
|
.post("/api/global/auditlogs/search")
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
import TestConfiguration from "../TestConfiguration"
|
|
||||||
import { TestAPI, TestAPIOpts } from "./base"
|
import { TestAPI, TestAPIOpts } from "./base"
|
||||||
|
|
||||||
export class AuthAPI extends TestAPI {
|
export class AuthAPI extends TestAPI {
|
||||||
constructor(config: TestConfiguration) {
|
|
||||||
super(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePassword = (
|
updatePassword = (
|
||||||
resetCode: string,
|
resetCode: string,
|
||||||
password: string,
|
password: string,
|
||||||
|
|
|
@ -10,7 +10,7 @@ export abstract class TestAPI {
|
||||||
config: TestConfiguration
|
config: TestConfiguration
|
||||||
request: SuperTest<Test>
|
request: SuperTest<Test>
|
||||||
|
|
||||||
protected constructor(config: TestConfiguration) {
|
constructor(config: TestConfiguration) {
|
||||||
this.config = config
|
this.config = config
|
||||||
this.request = config.request
|
this.request = config.request
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
import TestConfiguration from "../TestConfiguration"
|
|
||||||
import { TestAPI } from "./base"
|
import { TestAPI } from "./base"
|
||||||
|
|
||||||
export class ConfigAPI extends TestAPI {
|
export class ConfigAPI extends TestAPI {
|
||||||
constructor(config: TestConfiguration) {
|
|
||||||
super(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
getConfigChecklist = () => {
|
getConfigChecklist = () => {
|
||||||
return this.request
|
return this.request
|
||||||
.get(`/api/global/configs/checklist`)
|
.get(`/api/global/configs/checklist`)
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
import { EmailAttachment } from "@budibase/types"
|
import { EmailAttachment } from "@budibase/types"
|
||||||
import TestConfiguration from "../TestConfiguration"
|
|
||||||
import { TestAPI } from "./base"
|
import { TestAPI } from "./base"
|
||||||
|
|
||||||
export class EmailAPI extends TestAPI {
|
export class EmailAPI extends TestAPI {
|
||||||
constructor(config: TestConfiguration) {
|
|
||||||
super(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
sendEmail = (purpose: string, attachments?: EmailAttachment[]) => {
|
sendEmail = (purpose: string, attachments?: EmailAttachment[]) => {
|
||||||
return this.request
|
return this.request
|
||||||
.post(`/api/global/email/send`)
|
.post(`/api/global/email/send`)
|
||||||
|
@ -15,7 +10,7 @@ export class EmailAPI extends TestAPI {
|
||||||
attachments,
|
attachments,
|
||||||
purpose,
|
purpose,
|
||||||
tenantId: this.config.getTenantId(),
|
tenantId: this.config.getTenantId(),
|
||||||
userId: this.config.user?._id!,
|
userId: this.config.user!._id!,
|
||||||
})
|
})
|
||||||
.set(this.config.defaultHeaders())
|
.set(this.config.defaultHeaders())
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue