Merge branch 'develop' of github.com:Budibase/budibase into qa/rename-jobs

This commit is contained in:
Michael Drury 2023-03-31 23:29:56 +01:00
commit 77d2fbc4be
42 changed files with 520 additions and 168 deletions

View File

@ -56,7 +56,6 @@ jobs:
run: yarn install:pro $BRANCH $BASE_BRANCH
- run: yarn
- run: yarn bootstrap
- run: yarn build:client
- run: yarn test
- uses: codecov/codecov-action@v3
with:
@ -78,28 +77,28 @@ jobs:
- run: yarn bootstrap
- run: yarn test:pro
integration-test:
runs-on: ubuntu-latest
services:
couchdb:
image: ibmcom/couchdb3
env:
COUCHDB_PASSWORD: budibase
COUCHDB_USER: budibase
ports:
- 4567:5984
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- name: Install Pro
run: yarn install:pro $BRANCH $BASE_BRANCH
- run: yarn
- run: yarn bootstrap
- run: yarn build
- run: |
cd qa-core
yarn
yarn api:test:ci
# integration-test:
# runs-on: ubuntu-latest
# services:
# couchdb:
# image: ibmcom/couchdb3
# env:
# COUCHDB_PASSWORD: budibase
# COUCHDB_USER: budibase
# ports:
# - 4567:5984
# steps:
# - uses: actions/checkout@v2
# - name: Use Node.js 14.x
# uses: actions/setup-node@v1
# with:
# node-version: 14.x
# - name: Install Pro
# run: yarn install:pro $BRANCH $BASE_BRANCH
# - run: yarn
# - run: yarn bootstrap
# - run: yarn build
# - run: |
# cd qa-core
# yarn
# yarn api:test:ci

View File

@ -1,5 +1,5 @@
{
"version": "2.4.42-alpha.5",
"version": "2.4.43",
"npmClient": "yarn",
"packages": [
"packages/*"

View File

@ -25,7 +25,6 @@
"setup": "node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev",
"bootstrap": "lerna bootstrap && lerna link && ./scripts/link-dependencies.sh",
"build": "lerna run build",
"build:client": "lerna run build --ignore @budibase/backend-core --ignore @budibase/worker --ignore @budibase/server --ignore @budibase/builder --ignore @budibase/cli --ignore @budibase/sdk",
"build:dev": "lerna run prebuild && tsc --build --watch --preserveWatchOutput",
"build:backend": "lerna run build --ignore @budibase/client --ignore @budibase/bbui --ignore @budibase/builder --ignore @budibase/cli",
"build:sdk": "lerna run build:sdk",
@ -45,7 +44,7 @@
"dev": "yarn run kill-all && lerna link && lerna run --parallel dev:builder --concurrency 1",
"dev:noserver": "yarn run kill-builder && lerna link && lerna run dev:stack:up && lerna run --parallel dev:builder --concurrency 1 --ignore @budibase/backend-core --ignore @budibase/server --ignore @budibase/worker",
"dev:server": "yarn run kill-server && lerna run --parallel dev:builder --concurrency 1 --scope @budibase/backend-core --scope @budibase/worker --scope @budibase/server",
"test": "lerna run test",
"test": "lerna run test --stream",
"test:pro": "bash scripts/pro/test.sh",
"lint:eslint": "eslint packages && eslint qa-core",
"lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --check \"qa-core/**/*.{js,ts,svelte}\"",

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/backend-core",
"version": "2.4.42-alpha.5",
"version": "2.4.43",
"description": "Budibase backend core libraries used in server and worker",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
@ -24,7 +24,7 @@
"dependencies": {
"@budibase/nano": "10.1.2",
"@budibase/pouchdb-replication-stream": "1.2.10",
"@budibase/types": "2.4.42-alpha.5",
"@budibase/types": "^2.4.43",
"@shopify/jest-koa-mocks": "5.0.1",
"@techpass/passport-openidconnect": "0.3.2",
"aws-cloudfront-sign": "2.2.0",

View File

@ -1,4 +1,5 @@
#!/bin/bash
set -e
if [[ -n $CI ]]
then
@ -7,6 +8,6 @@ then
jest --coverage --runInBand --forceExit
else
# --maxWorkers performs better in development
echo "jest --coverage"
jest --coverage
echo "jest --coverage --forceExit"
jest --coverage --forceExit
fi

View File

@ -32,8 +32,7 @@ export async function getConfig<T extends Config>(
const db = context.getGlobalDB()
try {
// await to catch error
const config = (await db.get(generateConfigID(type))) as T
return config
return (await db.get(generateConfigID(type))) as T
} catch (e: any) {
if (e.status === 404) {
return

View File

@ -78,17 +78,23 @@ export async function postAuth(
),
{ successRedirect: "/", failureRedirect: "/error" },
async (err: any, tokens: string[]) => {
const baseUrl = `/builder/app/${authStateCookie.appId}/data`
// update the DB for the datasource with all the user info
await doWithDB(authStateCookie.appId, async (db: Database) => {
const datasource = await db.get(authStateCookie.datasourceId)
let datasource
try {
datasource = await db.get(authStateCookie.datasourceId)
} catch (err: any) {
if (err.status === 404) {
ctx.redirect(baseUrl)
}
}
if (!datasource.config) {
datasource.config = {}
}
datasource.config.auth = { type: "google", ...tokens }
await db.put(datasource)
ctx.redirect(
`/builder/app/${authStateCookie.appId}/data/datasource/${authStateCookie.datasourceId}`
)
ctx.redirect(`${baseUrl}/datasource/${authStateCookie.datasourceId}`)
})
}
)(ctx, next)

View File

@ -8,4 +8,5 @@ export * as plugins from "./plugins"
export * as sso from "./sso"
export * as tenant from "./tenants"
export * as users from "./users"
export * as userGroups from "./userGroups"
export { generator } from "./generator"

View File

@ -0,0 +1,10 @@
import { UserGroup } from "@budibase/types"
import { generator } from "./generator"
export function userGroup(): UserGroup {
return {
name: generator.word(),
icon: generator.word(),
color: generator.word(),
}
}

View File

@ -1,7 +1,7 @@
{
"name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.",
"version": "2.4.42-alpha.5",
"version": "2.4.43",
"license": "MPL-2.0",
"svelte": "src/index.js",
"module": "dist/bbui.es.js",
@ -38,8 +38,8 @@
],
"dependencies": {
"@adobe/spectrum-css-workflow-icons": "1.2.1",
"@budibase/shared-core": "2.4.42-alpha.5",
"@budibase/string-templates": "2.4.42-alpha.5",
"@budibase/shared-core": "^2.4.43",
"@budibase/string-templates": "^2.4.43",
"@spectrum-css/accordion": "3.0.24",
"@spectrum-css/actionbutton": "1.0.1",
"@spectrum-css/actiongroup": "1.0.1",

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/builder",
"version": "2.4.42-alpha.5",
"version": "2.4.43",
"license": "GPL-3.0",
"private": true,
"scripts": {
@ -58,11 +58,11 @@
}
},
"dependencies": {
"@budibase/bbui": "2.4.42-alpha.5",
"@budibase/client": "2.4.42-alpha.5",
"@budibase/frontend-core": "2.4.42-alpha.5",
"@budibase/shared-core": "2.4.42-alpha.5",
"@budibase/string-templates": "2.4.42-alpha.5",
"@budibase/bbui": "^2.4.43",
"@budibase/client": "^2.4.43",
"@budibase/frontend-core": "^2.4.43",
"@budibase/shared-core": "^2.4.43",
"@budibase/string-templates": "^2.4.43",
"@fortawesome/fontawesome-svg-core": "^6.2.1",
"@fortawesome/free-brands-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1",

View File

@ -5,18 +5,28 @@
export let preAuthStep
export let datasource
export let disabled
$: tenantId = $auth.tenantId
</script>
<button
class:disabled
{disabled}
on:click={async () => {
let ds = datasource
let appId = $store.appId
if (!ds) {
ds = await preAuthStep()
const resp = await preAuthStep()
if (resp.datasource && resp.appId) {
ds = resp.datasource
appId = resp.appId
} else {
ds = resp
}
}
window.open(
`/api/global/auth/${tenantId}/datasource/google?datasourceId=${ds._id}&appId=${$store.appId}`,
`/api/global/auth/${tenantId}/datasource/google?datasourceId=${ds._id}&appId=${appId}`,
"_blank"
)
}}
@ -26,6 +36,10 @@
</button>
<style>
.disabled {
opacity: 0.5;
}
button {
width: 195px;
height: 40px;

View File

@ -1,12 +1,15 @@
<script>
import { Button, FancyForm, FancyInput, FancyCheckbox } from "@budibase/bbui"
import GoogleButton from "components/backend/DatasourceNavigator/_components/GoogleButton.svelte"
import { capitalise } from "helpers/helpers"
import PanelHeader from "./PanelHeader.svelte"
import { helpers } from "@budibase/shared-core"
export let title = ""
export let onBack = null
export let onNext = () => {}
export let fields = {}
export let type = ""
let errors = {}
@ -57,8 +60,9 @@
}
$: isValid = getIsValid(fields, errors, values)
$: isGoogle = helpers.isGoogleSheets(type)
const handleNext = () => {
const handleNext = async () => {
const parsedValues = {}
Object.entries(values).forEach(([name, value]) => {
@ -69,7 +73,10 @@
}
})
return onNext(parsedValues)
if (isGoogle) {
parsedValues.isGoogle = isGoogle
}
return await onNext(parsedValues)
}
</script>
@ -99,7 +106,11 @@
{/each}
</FancyForm>
</div>
{#if isGoogle}
<GoogleButton disabled={!isValid} preAuthStep={handleNext} />
{:else}
<Button cta disabled={!isValid} on:click={handleNext}>Connect</Button>
{/if}
</div>
<style>

View File

@ -4,19 +4,20 @@
import DataPanel from "./_components/DataPanel.svelte"
import DatasourceConfigPanel from "./_components/DatasourceConfigPanel.svelte"
import ExampleApp from "./_components/ExampleApp.svelte"
import { FancyButton, notifications, Modal } from "@budibase/bbui"
import { FancyButton, notifications, Modal, Body } from "@budibase/bbui"
import IntegrationIcon from "components/backend/DatasourceNavigator/IntegrationIcon.svelte"
import { SplitPage } from "@budibase/frontend-core"
import { API } from "api"
import { store, automationStore } from "builderStore"
import { saveDatasource } from "builderStore/datasource"
import { integrations } from "stores/backend"
import { auth, admin } from "stores/portal"
import { auth, admin, organisation } from "stores/portal"
import FontAwesomeIcon from "components/common/FontAwesomeIcon.svelte"
import CreateTableModal from "components/backend/TableNavigator/modals/CreateTableModal.svelte"
import createFromScratchScreen from "builderStore/store/screenTemplates/createFromScratchScreen"
import { Roles } from "constants/backend"
import Spinner from "components/common/Spinner.svelte"
import { helpers } from "@budibase/shared-core"
let name = "My first app"
let url = "my-first-app"
@ -25,10 +26,11 @@
let plusIntegrations = {}
let integrationsLoading = true
$: getIntegrations()
let creationLoading = false
let uploadModal
let googleComplete = false
$: getIntegrations()
const createApp = async useSampleData => {
creationLoading = true
@ -62,6 +64,7 @@
await store.actions.screens.save(defaultScreenTemplate)
appId = createdApp.instance._id
return createdApp
} catch (e) {
creationLoading = false
throw e
@ -74,6 +77,13 @@
const newPlusIntegrations = {}
Object.entries($integrations).forEach(([integrationType, schema]) => {
// google sheets not available in self-host
if (
helpers.isGoogleSheets(integrationType) &&
!$organisation.googleDatasourceConfigured
) {
return
}
if (schema?.plus) {
newPlusIntegrations[integrationType] = schema
}
@ -92,12 +102,17 @@
notifications.success(`App created successfully`)
}
const handleCreateApp = async ({ datasourceConfig, useSampleData }) => {
const handleCreateApp = async ({
datasourceConfig,
useSampleData,
isGoogle,
}) => {
try {
await createApp(useSampleData)
const app = await createApp(useSampleData)
let datasource
if (datasourceConfig) {
await saveDatasource({
datasource = await saveDatasource({
plus: true,
auth: undefined,
name: plusIntegrations[stage].friendlyName,
@ -107,7 +122,14 @@
})
}
store.set()
if (isGoogle) {
googleComplete = true
return { datasource, appId: app.appId }
} else {
goToApp()
}
} catch (e) {
console.log(e)
creationLoading = false
@ -127,8 +149,15 @@
<SplitPage>
{#if stage === "name"}
<NamePanel bind:name bind:url onNext={() => (stage = "data")} />
{:else if googleComplete}
<div class="centered">
<Body
>Please login to your Google account in the new tab which as opened to
continue.</Body
>
</div>
{:else if integrationsLoading || creationLoading}
<div class="spinner">
<div class="centered">
<Spinner />
</div>
{:else if stage === "data"}
@ -174,8 +203,13 @@
<DatasourceConfigPanel
title={plusIntegrations[stage].friendlyName}
fields={plusIntegrations[stage].datasource}
type={stage}
onBack={() => (stage = "data")}
onNext={data => handleCreateApp({ datasourceConfig: data })}
onNext={data => {
const isGoogle = data.isGoogle
delete data.isGoogle
return handleCreateApp({ datasourceConfig: data, isGoogle })
}}
/>
{:else}
<p>There was an problem. Please refresh the page and try again.</p>
@ -186,7 +220,7 @@
</SplitPage>
<style>
.spinner {
.centered {
display: flex;
justify-content: center;
align-items: center;

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/cli",
"version": "2.4.42-alpha.5",
"version": "2.4.43",
"description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "dist/index.js",
"bin": {
@ -29,9 +29,9 @@
"outputPath": "build"
},
"dependencies": {
"@budibase/backend-core": "2.4.42-alpha.5",
"@budibase/string-templates": "2.4.42-alpha.5",
"@budibase/types": "2.4.42-alpha.5",
"@budibase/backend-core": "^2.4.43",
"@budibase/string-templates": "^2.4.43",
"@budibase/types": "^2.4.43",
"axios": "0.21.2",
"chalk": "4.1.0",
"cli-progress": "3.11.2",

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/client",
"version": "2.4.42-alpha.5",
"version": "2.4.43",
"license": "MPL-2.0",
"module": "dist/budibase-client.js",
"main": "dist/budibase-client.js",
@ -19,11 +19,11 @@
"dev:builder": "rollup -cw"
},
"dependencies": {
"@budibase/bbui": "2.4.42-alpha.5",
"@budibase/frontend-core": "2.4.42-alpha.5",
"@budibase/shared-core": "2.4.42-alpha.5",
"@budibase/string-templates": "2.4.42-alpha.5",
"@budibase/types": "2.4.42-alpha.5",
"@budibase/bbui": "^2.4.43",
"@budibase/frontend-core": "^2.4.43",
"@budibase/shared-core": "^2.4.43",
"@budibase/string-templates": "^2.4.43",
"@budibase/types": "^2.4.43",
"@spectrum-css/button": "^3.0.3",
"@spectrum-css/card": "^3.0.3",
"@spectrum-css/divider": "^1.0.3",

View File

@ -1,13 +1,13 @@
{
"name": "@budibase/frontend-core",
"version": "2.4.42-alpha.5",
"version": "2.4.43",
"description": "Budibase frontend core libraries used in builder and client",
"author": "Budibase",
"license": "MPL-2.0",
"svelte": "src/index.js",
"dependencies": {
"@budibase/bbui": "2.4.42-alpha.5",
"@budibase/shared-core": "2.4.42-alpha.5",
"@budibase/bbui": "^2.4.43",
"@budibase/shared-core": "^2.4.43",
"lodash": "^4.17.21",
"svelte": "^3.46.2"
}

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/sdk",
"version": "2.4.42-alpha.5",
"version": "2.4.43",
"description": "Budibase Public API SDK",
"author": "Budibase",
"license": "MPL-2.0",

View File

@ -44,6 +44,7 @@ const config: Config.InitialOptions = {
// The use of coverage with couchdb view functions breaks tests
"!src/db/views/staticViews.*",
"!src/**/*.spec.{js,ts}",
"!src/tests/**/*.{js,ts}",
],
coverageReporters: ["lcov", "json", "clover"],
}

View File

@ -1,7 +1,7 @@
{
"name": "@budibase/server",
"email": "hi@budibase.com",
"version": "2.4.42-alpha.5",
"version": "2.4.43",
"description": "Budibase Web Server",
"main": "src/index.ts",
"repository": {
@ -14,7 +14,7 @@
"build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
"debug": "yarn build && node --expose-gc --inspect=9222 dist/index.js",
"postbuild": "copyfiles -u 1 src/**/*.svelte dist/ && copyfiles -u 1 src/**/*.hbs dist/ && copyfiles -u 1 src/**/*.json dist/",
"test": "NODE_OPTIONS=\"--max-old-space-size=4096\" bash scripts/test.sh",
"test": "bash scripts/test.sh",
"test:memory": "jest --maxWorkers=2 --logHeapUsage --forceExit",
"test:watch": "jest --watch",
"predocker": "copyfiles -f ../client/dist/budibase-client.js ../client/manifest.json client",
@ -44,12 +44,12 @@
"license": "GPL-3.0",
"dependencies": {
"@apidevtools/swagger-parser": "10.0.3",
"@budibase/backend-core": "2.4.42-alpha.5",
"@budibase/client": "2.4.42-alpha.5",
"@budibase/pro": "2.4.42-alpha.5",
"@budibase/shared-core": "2.4.42-alpha.5",
"@budibase/string-templates": "2.4.42-alpha.5",
"@budibase/types": "2.4.42-alpha.5",
"@budibase/backend-core": "^2.4.43",
"@budibase/client": "^2.4.43",
"@budibase/pro": "2.4.43",
"@budibase/shared-core": "^2.4.43",
"@budibase/string-templates": "^2.4.43",
"@budibase/types": "^2.4.43",
"@bull-board/api": "3.7.0",
"@bull-board/koa": "3.9.4",
"@elastic/elasticsearch": "7.10.0",

View File

@ -1,12 +1,14 @@
#!/bin/bash
set -e
if [[ -n $CI ]]
then
# --runInBand performs better in ci where resources are limited
export NODE_OPTIONS="--max-old-space-size=4096"
echo "jest --coverage --runInBand --forceExit"
jest --coverage --runInBand --forceExit
else
# --maxWorkers performs better in development
echo "jest --coverage --maxWorkers=2"
jest --coverage --maxWorkers=2
echo "jest --coverage --maxWorkers=2 --forceExit"
jest --coverage --maxWorkers=2 --forceExit
fi

View File

@ -2,9 +2,9 @@ import { DocumentType } from "../../db/utils"
import { Plugin } from "@budibase/types"
import { db as dbCore, context, tenancy } from "@budibase/backend-core"
import { getComponentLibraryManifest } from "../../utilities/fileSystem"
import { BBContext } from "@budibase/types"
import { UserCtx } from "@budibase/types"
export async function fetchAppComponentDefinitions(ctx: BBContext) {
export async function fetchAppComponentDefinitions(ctx: UserCtx) {
try {
const db = context.getAppDB()
const app = await db.get(DocumentType.APP_METADATA)

View File

@ -0,0 +1,97 @@
import { FieldType } from "@budibase/types"
import { AutoFieldSubTypes } from "../../../../constants"
import TestConfiguration from "../../../../tests/utilities/TestConfiguration"
import { importToRows } from "../utils"
describe("utils", () => {
const config = new TestConfiguration()
beforeEach(async () => {
await config.init()
})
afterAll(config.end)
describe("importToRows", () => {
it("consecutive row have consecutive auto ids", async () => {
await config.doInContext(config.appId, async () => {
const table = await config.createTable({
name: "table",
type: "table",
schema: {
autoId: {
name: "autoId",
type: FieldType.NUMBER,
subtype: AutoFieldSubTypes.AUTO_ID,
autocolumn: true,
constraints: {
type: FieldType.NUMBER,
presence: true,
},
},
name: {
name: "name",
type: FieldType.STRING,
constraints: {
type: FieldType.STRING,
presence: true,
},
},
},
})
const data = [{ name: "Alice" }, { name: "Bob" }, { name: "Claire" }]
const result = importToRows(data, table, config.user)
expect(result).toEqual([
expect.objectContaining({
autoId: 1,
name: "Alice",
}),
expect.objectContaining({
autoId: 2,
name: "Bob",
}),
expect.objectContaining({
autoId: 3,
name: "Claire",
}),
])
})
})
it("can import data without a specific user performing the action", async () => {
await config.doInContext(config.appId, async () => {
const table = await config.createTable({
name: "table",
type: "table",
schema: {
autoId: {
name: "autoId",
type: FieldType.NUMBER,
subtype: AutoFieldSubTypes.AUTO_ID,
autocolumn: true,
constraints: {
type: FieldType.NUMBER,
presence: true,
},
},
name: {
name: "name",
type: FieldType.STRING,
constraints: {
type: FieldType.STRING,
presence: true,
},
},
},
})
const data = [{ name: "Alice" }, { name: "Bob" }, { name: "Claire" }]
const result = importToRows(data, table)
expect(result).toHaveLength(3)
})
})
})
})

View File

@ -20,7 +20,13 @@ import viewTemplate from "../view/viewBuilder"
import { cloneDeep } from "lodash/fp"
import { quotas } from "@budibase/pro"
import { events, context } from "@budibase/backend-core"
import { Database, Datasource, SourceName, Table } from "@budibase/types"
import {
ContextUser,
Database,
Datasource,
SourceName,
Table,
} from "@budibase/types"
export async function clearColumns(table: any, columnNames: any) {
const db: Database = context.getAppDB()
@ -99,32 +105,35 @@ export function makeSureTableUpToDate(table: any, tableToSave: any) {
return tableToSave
}
export function importToRows(data: any, table: any, user: any = {}) {
export function importToRows(
data: any[],
table: Table,
user: ContextUser | null = null
) {
let finalData: any = []
for (let i = 0; i < data.length; i++) {
let row = data[i]
row._id = generateRowID(table._id)
row._id = generateRowID(table._id!)
row.tableId = table._id
const processed: any = inputProcessing(user, table, row, {
const processed = inputProcessing(user, table, row, {
noAutoRelationships: true,
})
row = processed.row
table = processed.table
let fieldName: any
let schema: any
for ([fieldName, schema] of Object.entries(table.schema)) {
for (const [fieldName, schema] of Object.entries(table.schema)) {
// check whether the options need to be updated for inclusion as part of the data import
if (
schema.type === FieldTypes.OPTIONS &&
row[fieldName] &&
(!schema.constraints.inclusion ||
schema.constraints.inclusion.indexOf(row[fieldName]) === -1)
(!schema.constraints!.inclusion ||
schema.constraints!.inclusion.indexOf(row[fieldName]) === -1)
) {
schema.constraints.inclusion = [
...schema.constraints.inclusion,
schema.constraints!.inclusion = [
...schema.constraints!.inclusion!,
row[fieldName],
]
schema.constraints.inclusion.sort()
schema.constraints!.inclusion.sort()
}
}

View File

@ -34,7 +34,7 @@ function syncLastIds(table: Table, rowCount: number) {
})
}
function tableImport(table: Table, data: Row) {
function tableImport(table: Table, data: Row[]) {
const cloneTable = cloneDeep(table)
const rowDocs = importToRows(data, cloneTable)
syncLastIds(cloneTable, rowDocs.length)

View File

@ -245,6 +245,10 @@ class GoogleSheetsIntegration implements DatasourcePlus {
}
async buildSchema(datasourceId: string, entities: Record<string, Table>) {
// not fully configured yet
if (!this.config.auth) {
return
}
await this.connect()
const sheets = this.client.sheetsByIndex
const tables: Record<string, Table> = {}

View File

@ -0,0 +1,159 @@
import { db, roles } from "@budibase/backend-core"
import { structures } from "@budibase/backend-core/tests"
import { sdk as proSdk } from "@budibase/pro"
import TestConfiguration from "../../../tests/utilities/TestConfiguration"
import { rawUserMetadata, syncGlobalUsers } from "../utils"
describe("syncGlobalUsers", () => {
const config = new TestConfiguration()
beforeEach(async () => {
await config.init()
})
afterAll(config.end)
it("the default user is synced", async () => {
await config.doInContext(config.appId, async () => {
await syncGlobalUsers()
const metadata = await rawUserMetadata()
expect(metadata).toHaveLength(1)
expect(metadata).toEqual([
expect.objectContaining({
_id: db.generateUserMetadataID(config.user._id),
}),
])
})
})
it("admin and builders users are synced", async () => {
const user1 = await config.createUser({ admin: true })
const user2 = await config.createUser({ admin: false, builder: true })
await config.doInContext(config.appId, async () => {
expect(await rawUserMetadata()).toHaveLength(1)
await syncGlobalUsers()
const metadata = await rawUserMetadata()
expect(metadata).toHaveLength(3)
expect(metadata).toContainEqual(
expect.objectContaining({
_id: db.generateUserMetadataID(user1._id),
})
)
expect(metadata).toContainEqual(
expect.objectContaining({
_id: db.generateUserMetadataID(user2._id),
})
)
})
})
it("app users are not synced if not specified", async () => {
const user = await config.createUser({ admin: false, builder: false })
await config.doInContext(config.appId, async () => {
await syncGlobalUsers()
const metadata = await rawUserMetadata()
expect(metadata).toHaveLength(1)
expect(metadata).not.toContainEqual(
expect.objectContaining({
_id: db.generateUserMetadataID(user._id),
})
)
})
})
it("app users are added when group is assigned to app", async () => {
await config.doInTenant(async () => {
const group = await proSdk.groups.save(structures.userGroups.userGroup())
const user1 = await config.createUser({ admin: false, builder: false })
const user2 = await config.createUser({ admin: false, builder: false })
await proSdk.groups.addUsers(group.id, [user1._id, user2._id])
await config.doInContext(config.appId, async () => {
await syncGlobalUsers()
expect(await rawUserMetadata()).toHaveLength(1)
await proSdk.groups.updateGroupApps(group.id, {
appsToAdd: [
{ appId: config.prodAppId!, roleId: roles.BUILTIN_ROLE_IDS.BASIC },
],
})
await syncGlobalUsers()
const metadata = await rawUserMetadata()
expect(metadata).toHaveLength(3)
expect(metadata).toContainEqual(
expect.objectContaining({
_id: db.generateUserMetadataID(user1._id),
})
)
expect(metadata).toContainEqual(
expect.objectContaining({
_id: db.generateUserMetadataID(user2._id),
})
)
})
})
})
it("app users are removed when app is removed from user group", async () => {
await config.doInTenant(async () => {
const group = await proSdk.groups.save(structures.userGroups.userGroup())
const user1 = await config.createUser({ admin: false, builder: false })
const user2 = await config.createUser({ admin: false, builder: false })
await proSdk.groups.updateGroupApps(group.id, {
appsToAdd: [
{ appId: config.prodAppId!, roleId: roles.BUILTIN_ROLE_IDS.BASIC },
],
})
await proSdk.groups.addUsers(group.id, [user1._id, user2._id])
await config.doInContext(config.appId, async () => {
await syncGlobalUsers()
expect(await rawUserMetadata()).toHaveLength(3)
await proSdk.groups.updateGroupApps(group.id, {
appsToRemove: [{ appId: config.prodAppId! }],
})
await syncGlobalUsers()
const metadata = await rawUserMetadata()
expect(metadata).toHaveLength(1)
})
})
})
it("app users are removed when app is removed from user group", async () => {
await config.doInTenant(async () => {
const group = await proSdk.groups.save(structures.userGroups.userGroup())
const user1 = await config.createUser({ admin: false, builder: false })
const user2 = await config.createUser({ admin: false, builder: false })
await proSdk.groups.updateGroupApps(group.id, {
appsToAdd: [
{ appId: config.prodAppId!, roleId: roles.BUILTIN_ROLE_IDS.BASIC },
],
})
await proSdk.groups.addUsers(group.id, [user1._id, user2._id])
await config.doInContext(config.appId, async () => {
await syncGlobalUsers()
expect(await rawUserMetadata()).toHaveLength(3)
await proSdk.groups.removeUsers(group.id, [user1._id])
await syncGlobalUsers()
const metadata = await rawUserMetadata()
expect(metadata).toHaveLength(2)
expect(metadata).not.toContainEqual(
expect.objectContaining({
_id: db.generateUserMetadataID(user1._id),
})
)
})
})
})
})

View File

@ -6,25 +6,33 @@ import {
InternalTables,
} from "../../db/utils"
import { isEqual } from "lodash"
import { ContextUser, UserMetadata } from "@budibase/types"
export function combineMetadataAndUser(user: any, metadata: any) {
export function combineMetadataAndUser(
user: ContextUser,
metadata: UserMetadata | UserMetadata[]
) {
const metadataId = generateUserMetadataID(user._id!)
const found = Array.isArray(metadata)
? metadata.find(doc => doc._id === metadataId)
: metadata
// skip users with no access
if (
user.roleId == null ||
user.roleId === rolesCore.BUILTIN_ROLE_IDS.PUBLIC
) {
// If it exists and it should not, we must remove it
if (found?._id) {
return { ...found, _deleted: true }
}
return null
}
delete user._rev
const metadataId = generateUserMetadataID(user._id)
const newDoc = {
...user,
_id: metadataId,
tableId: InternalTables.USER_METADATA,
}
const found = Array.isArray(metadata)
? metadata.find(doc => doc._id === metadataId)
: metadata
// copy rev over for the purposes of equality check
if (found) {
newDoc._rev = found._rev
@ -58,7 +66,7 @@ export async function syncGlobalUsers() {
])
const toWrite = []
for (let user of users) {
const combined = await combineMetadataAndUser(user, metadata)
const combined = combineMetadataAndUser(user, metadata)
if (combined) {
toWrite.push(combined)
}

View File

@ -47,6 +47,7 @@ import {
SourceName,
Table,
SearchFilters,
UserRoles,
} from "@budibase/types"
type DefaultUserValues = {
@ -277,7 +278,7 @@ class TestConfiguration {
email?: string
builder?: boolean
admin?: boolean
roles?: any
roles?: UserRoles
} = {}
) {
let { id, firstName, lastName, email, builder, admin, roles } = user

View File

@ -33,15 +33,8 @@ export const deleteApp = async (appId: string) => {
export const getComponentLibraryManifest = async (library: string) => {
const appId = context.getAppId()
const filename = "manifest.json"
/* istanbul ignore next */
// when testing in cypress and so on we need to get the package
// as the environment may not be fully fleshed out for dev or prod
if (env.isTest()) {
library = library.replace("standard-components", "client")
const lib = library.split("/")[1]
const path = require.resolve(library).split(lib)[0]
return require(join(path, lib, filename))
} else if (env.isDev()) {
if (env.isDev() || env.isTest()) {
const path = join(NODE_MODULES_PATH, "@budibase", "client", filename)
// always load from new so that updates are refreshed
delete require.cache[require.resolve(path)]

View File

@ -24,10 +24,6 @@ export const init = () => {
}
}
}
const clientLibPath = join(budibaseTempDir(), "budibase-client.js")
if (env.isTest() && !fs.existsSync(clientLibPath)) {
fs.copyFileSync(require.resolve("@budibase/client"), clientLibPath)
}
}
/**

View File

@ -131,7 +131,7 @@ export function coerce(row: any, type: string) {
* @returns {object} the row which has been prepared to be written to the DB.
*/
export function inputProcessing(
user: ContextUser,
user: ContextUser | null,
table: Table,
row: Row,
opts?: AutoColumnProcessingOpts

View File

@ -1290,14 +1290,14 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@2.4.42-alpha.5":
version "2.4.42-alpha.5"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.4.42-alpha.5.tgz#3e4b44c6e1b7f9c0652527a48dfa46dbe022e93b"
integrity sha512-vclysH06NASv9XZ55W9yonPIodVTCWklo6alfKPQTbvjINGBopfTglV9new+OVJoqlaYPyt4AyBVp0si29vuKg==
"@budibase/backend-core@2.4.43":
version "2.4.43"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.4.43.tgz#801cdad706a90f6718e9323dd66e7e664cc9b287"
integrity sha512-pxMjFsCugkR/lR6XsA4bAbBEnUta92jjeUIY4C//hjczHF0lwEUWqhhR8QRCMaNTrWoJfZBFGnJyNIqfguyDVw==
dependencies:
"@budibase/nano" "10.1.2"
"@budibase/pouchdb-replication-stream" "1.2.10"
"@budibase/types" "2.4.42-alpha.5"
"@budibase/types" "^2.4.43"
"@shopify/jest-koa-mocks" "5.0.1"
"@techpass/passport-openidconnect" "0.3.2"
aws-cloudfront-sign "2.2.0"
@ -1429,14 +1429,14 @@
pouchdb-promise "^6.0.4"
through2 "^2.0.0"
"@budibase/pro@2.4.42-alpha.5":
version "2.4.42-alpha.5"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.4.42-alpha.5.tgz#99774e2e06fc22892c545b7208d78144395859db"
integrity sha512-Axxx4zqsBsu2fl5UKetNXCJWoMi2pcA/kVY88Z9BYNPmGp7k3s2pKU/kShy751I2fDF0TGgxSCGzpVzWqM8ZEg==
"@budibase/pro@2.4.43":
version "2.4.43"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.4.43.tgz#e717000a26fd4c183a984a141965945a26ac83d9"
integrity sha512-J/Up28bSV5E2UTQBpaTOXvNG1jQqRj8fcZH5TSqS7ymJ2H4OyLOQvEGFL3roCGnN3Dgrzkfv9LBO0JyRORVtSw==
dependencies:
"@budibase/backend-core" "2.4.42-alpha.5"
"@budibase/backend-core" "2.4.43"
"@budibase/string-templates" "2.3.20"
"@budibase/types" "2.4.42-alpha.5"
"@budibase/types" "2.4.43"
"@koa/router" "8.0.8"
bull "4.10.1"
joi "17.6.0"
@ -1475,10 +1475,10 @@
lodash "^4.17.20"
vm2 "^3.9.4"
"@budibase/types@2.4.42-alpha.5":
version "2.4.42-alpha.5"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.4.42-alpha.5.tgz#21408ab88b74833f176003f9e54dc109252d8080"
integrity sha512-3uZmKryzMcAxIrR0hnBwoNfuqR8yvCWjK880h4Mdi2ik/uYfndUAhdVi/Bh/oU3ZGu9KiJzqh/IE29IuRboiNg==
"@budibase/types@2.4.43", "@budibase/types@^2.4.43":
version "2.4.43"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.4.43.tgz#2f6e609bada8855c83dfb26deb3b2d64dbb2d401"
integrity sha512-OWr8dNjk3VjjAJ4Rni/+AMGDtCDhz7LrZqxgedL0wKiRwgwkSiBNExh1eIZ/NKlt1vZrzH/GG5jhzlPkYqDTQg==
"@bull-board/api@3.7.0":
version "3.7.0"

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/shared-core",
"version": "2.4.42-alpha.5",
"version": "2.4.43",
"description": "Shared data utils",
"main": "dist/cjs/src/index.js",
"types": "dist/mjs/src/index.d.ts",
@ -20,7 +20,7 @@
"dev:builder": "yarn prebuild && concurrently \"tsc -p tsconfig.build.json --watch\" \"tsc -p tsconfig-cjs.build.json --watch\""
},
"dependencies": {
"@budibase/types": "2.4.42-alpha.5"
"@budibase/types": "^2.4.43"
},
"devDependencies": {
"concurrently": "^7.6.0",

View File

@ -0,0 +1,2 @@
export * from "./helpers"
export * from "./integrations"

View File

@ -0,0 +1,5 @@
import { SourceName } from "@budibase/types"
export function isGoogleSheets(type: SourceName) {
return type === SourceName.GOOGLE_SHEETS
}

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/string-templates",
"version": "2.4.42-alpha.5",
"version": "2.4.43",
"description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs",
"module": "dist/bundle.mjs",

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/types",
"version": "2.4.42-alpha.5",
"version": "2.4.43",
"description": "Budibase types",
"main": "dist/cjs/index.js",
"types": "dist/mjs/index.d.ts",

View File

@ -1,7 +1,7 @@
{
"name": "@budibase/worker",
"email": "hi@budibase.com",
"version": "2.4.42-alpha.5",
"version": "2.4.43",
"description": "Budibase background service",
"main": "src/index.ts",
"repository": {
@ -36,10 +36,10 @@
"author": "Budibase",
"license": "GPL-3.0",
"dependencies": {
"@budibase/backend-core": "2.4.42-alpha.5",
"@budibase/pro": "2.4.42-alpha.5",
"@budibase/string-templates": "2.4.42-alpha.5",
"@budibase/types": "2.4.42-alpha.5",
"@budibase/backend-core": "^2.4.43",
"@budibase/pro": "2.4.43",
"@budibase/string-templates": "^2.4.43",
"@budibase/types": "^2.4.43",
"@koa/router": "8.0.8",
"@sentry/node": "6.17.7",
"@techpass/passport-openidconnect": "0.3.2",

View File

@ -1,4 +1,5 @@
#!/bin/bash
set -e
if [[ -n $CI ]]
then
@ -7,6 +8,6 @@ then
jest --coverage --runInBand --forceExit
else
# --maxWorkers performs better in development
echo "jest --coverage --maxWorkers=2"
jest --coverage --maxWorkers=2
echo "jest --coverage --maxWorkers=2 --forceExit"
jest --coverage --maxWorkers=2 --forceExit
fi

View File

@ -475,14 +475,14 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@2.4.42-alpha.5":
version "2.4.42-alpha.5"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.4.42-alpha.5.tgz#3e4b44c6e1b7f9c0652527a48dfa46dbe022e93b"
integrity sha512-vclysH06NASv9XZ55W9yonPIodVTCWklo6alfKPQTbvjINGBopfTglV9new+OVJoqlaYPyt4AyBVp0si29vuKg==
"@budibase/backend-core@2.4.43":
version "2.4.43"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.4.43.tgz#801cdad706a90f6718e9323dd66e7e664cc9b287"
integrity sha512-pxMjFsCugkR/lR6XsA4bAbBEnUta92jjeUIY4C//hjczHF0lwEUWqhhR8QRCMaNTrWoJfZBFGnJyNIqfguyDVw==
dependencies:
"@budibase/nano" "10.1.2"
"@budibase/pouchdb-replication-stream" "1.2.10"
"@budibase/types" "2.4.42-alpha.5"
"@budibase/types" "^2.4.43"
"@shopify/jest-koa-mocks" "5.0.1"
"@techpass/passport-openidconnect" "0.3.2"
aws-cloudfront-sign "2.2.0"
@ -564,14 +564,14 @@
pouchdb-promise "^6.0.4"
through2 "^2.0.0"
"@budibase/pro@2.4.42-alpha.5":
version "2.4.42-alpha.5"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.4.42-alpha.5.tgz#99774e2e06fc22892c545b7208d78144395859db"
integrity sha512-Axxx4zqsBsu2fl5UKetNXCJWoMi2pcA/kVY88Z9BYNPmGp7k3s2pKU/kShy751I2fDF0TGgxSCGzpVzWqM8ZEg==
"@budibase/pro@2.4.43":
version "2.4.43"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.4.43.tgz#e717000a26fd4c183a984a141965945a26ac83d9"
integrity sha512-J/Up28bSV5E2UTQBpaTOXvNG1jQqRj8fcZH5TSqS7ymJ2H4OyLOQvEGFL3roCGnN3Dgrzkfv9LBO0JyRORVtSw==
dependencies:
"@budibase/backend-core" "2.4.42-alpha.5"
"@budibase/backend-core" "2.4.43"
"@budibase/string-templates" "2.3.20"
"@budibase/types" "2.4.42-alpha.5"
"@budibase/types" "2.4.43"
"@koa/router" "8.0.8"
bull "4.10.1"
joi "17.6.0"
@ -592,10 +592,10 @@
lodash "^4.17.20"
vm2 "^3.9.4"
"@budibase/types@2.4.42-alpha.5":
version "2.4.42-alpha.5"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.4.42-alpha.5.tgz#21408ab88b74833f176003f9e54dc109252d8080"
integrity sha512-3uZmKryzMcAxIrR0hnBwoNfuqR8yvCWjK880h4Mdi2ik/uYfndUAhdVi/Bh/oU3ZGu9KiJzqh/IE29IuRboiNg==
"@budibase/types@2.4.43", "@budibase/types@^2.4.43":
version "2.4.43"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.4.43.tgz#2f6e609bada8855c83dfb26deb3b2d64dbb2d401"
integrity sha512-OWr8dNjk3VjjAJ4Rni/+AMGDtCDhz7LrZqxgedL0wKiRwgwkSiBNExh1eIZ/NKlt1vZrzH/GG5jhzlPkYqDTQg==
"@cspotcode/source-map-support@^0.8.0":
version "0.8.1"