Merge branch 'ts-portal-apps-store' of github.com:Budibase/budibase into ts-portal-auditlogs-store

This commit is contained in:
Andrew Kingston 2024-12-13 09:15:49 +00:00
commit d0b8ab79e3
No known key found for this signature in database
11 changed files with 105 additions and 52 deletions

View File

@ -4,8 +4,8 @@ import {
getContainerRuntimeClient, getContainerRuntimeClient,
} from "testcontainers" } from "testcontainers"
import { ContainerInfo } from "dockerode" import { ContainerInfo } from "dockerode"
import path from "path" import * as path from "path"
import lockfile from "proper-lockfile" import * as lockfile from "proper-lockfile"
import { execSync } from "child_process" import { execSync } from "child_process"
interface DockerContext { interface DockerContext {
@ -29,8 +29,8 @@ function getCurrentDockerContext(): DockerContext {
async function getBudibaseContainers() { async function getBudibaseContainers() {
const client = await getContainerRuntimeClient() const client = await getContainerRuntimeClient()
const conatiners = await client.container.list() const containers = await client.container.list()
return conatiners.filter( return containers.filter(
container => container =>
container.Labels["com.budibase"] === "true" && container.Labels["com.budibase"] === "true" &&
container.Labels["org.testcontainers"] === "true" container.Labels["org.testcontainers"] === "true"

View File

@ -59,11 +59,15 @@ export function isExternalTable(table: Table) {
} }
export function buildExternalTableId(datasourceId: string, tableName: string) { export function buildExternalTableId(datasourceId: string, tableName: string) {
// encode spaces return `${datasourceId}${DOUBLE_SEPARATOR}${encodeURIComponent(tableName)}`
if (tableName.includes(" ")) { }
tableName = encodeURIComponent(tableName)
export function encodeTableId(tableId: string) {
if (isExternalTableID(tableId)) {
return encodeURIComponent(tableId)
} else {
return tableId
} }
return `${datasourceId}${DOUBLE_SEPARATOR}${tableName}`
} }
export function breakExternalTableId(tableId: string) { export function breakExternalTableId(tableId: string) {

View File

@ -49,7 +49,7 @@
const disabled = () => { const disabled = () => {
return { return {
SEND_EMAIL_SMTP: { SEND_EMAIL_SMTP: {
disabled: !$admin.checklist.smtp.checked, disabled: !$admin.checklist?.smtp?.checked,
message: "Please configure SMTP", message: "Please configure SMTP",
}, },
COLLECT: { COLLECT: {

View File

@ -9,7 +9,7 @@
$: useAccountPortal = cloud && !$admin.disableAccountPortal $: useAccountPortal = cloud && !$admin.disableAccountPortal
onMount(() => { onMount(() => {
if ($admin?.checklist?.adminUser.checked || useAccountPortal) { if ($admin?.checklist?.adminUser?.checked || useAccountPortal) {
$redirect("../") $redirect("../")
} else { } else {
loaded = true loaded = true

View File

@ -177,7 +177,7 @@
<Button <Button
secondary secondary
on:click={deleteSmtp} on:click={deleteSmtp}
disabled={!$admin.checklist.smtp.checked} disabled={!$admin.checklist?.smtp?.checked}
> >
Reset Reset
</Button> </Button>

View File

@ -1,5 +1,5 @@
import { it, expect, describe, beforeEach, vi } from "vitest" import { it, expect, describe, beforeEach, vi } from "vitest"
import { DEFAULT_CONFIG, createAdminStore } from "./admin" import { createAdminStore } from "./admin"
import { writable, get } from "svelte/store" import { writable, get } from "svelte/store"
import { API } from "api" import { API } from "api"
@ -45,11 +45,6 @@ describe("admin store", () => {
ctx.returnedStore = createAdminStore() ctx.returnedStore = createAdminStore()
}) })
it("inits the writable store with the default config", () => {
expect(writable).toHaveBeenCalledTimes(1)
expect(writable).toHaveBeenCalledWith(DEFAULT_CONFIG)
})
it("returns the created store", ctx => { it("returns the created store", ctx => {
expect(ctx.returnedStore).toEqual({ expect(ctx.returnedStore).toEqual({
subscribe: expect.toBe(ctx.writableReturn.subscribe), subscribe: expect.toBe(ctx.writableReturn.subscribe),

View File

@ -2,27 +2,28 @@ import { writable, get } from "svelte/store"
import { API } from "api" import { API } from "api"
import { auth } from "stores/portal" import { auth } from "stores/portal"
import { banner } from "@budibase/bbui" import { banner } from "@budibase/bbui"
import {
ConfigChecklistResponse,
GetEnvironmentResponse,
SystemStatusResponse,
} from "@budibase/types"
export const DEFAULT_CONFIG = { interface PortalAdminStore extends GetEnvironmentResponse {
loaded: false, loaded: boolean
multiTenancy: false, checklist?: ConfigChecklistResponse
cloud: false, status?: SystemStatusResponse
isDev: false,
disableAccountPortal: false,
accountPortalUrl: "",
importComplete: false,
checklist: {
apps: { checked: false },
smtp: { checked: false },
adminUser: { checked: false },
sso: { checked: false },
},
maintenance: [],
offlineMode: false,
} }
export function createAdminStore() { export function createAdminStore() {
const admin = writable(DEFAULT_CONFIG) const admin = writable<PortalAdminStore>({
loaded: false,
multiTenancy: false,
cloud: false,
isDev: false,
disableAccountPortal: false,
offlineMode: false,
maintenance: [],
})
async function init() { async function init() {
await getChecklist() await getChecklist()

View File

@ -288,19 +288,21 @@ function replaceTableNamesInFilters(
for (const key of Object.keys(filter)) { for (const key of Object.keys(filter)) {
const matches = key.match(`^(?<relation>.+)\\.(?<field>.+)`) const matches = key.match(`^(?<relation>.+)\\.(?<field>.+)`)
const relation = matches?.groups?.["relation"] // this is the possible table name which we need to check if it needs to be converted
const relatedTableName = matches?.groups?.["relation"]
const field = matches?.groups?.["field"] const field = matches?.groups?.["field"]
if (!relation || !field) { if (!relatedTableName || !field) {
continue continue
} }
const table = allTables.find(r => r._id === tableId)! const table = allTables.find(r => r._id === tableId)
if (Object.values(table.schema).some(f => f.name === relation)) { const isColumnName = !!table?.schema[relatedTableName]
if (!table || isColumnName) {
continue continue
} }
const matchedTable = allTables.find(t => t.name === relation) const matchedTable = allTables.find(t => t.name === relatedTableName)
const relationship = Object.values(table.schema).find( const relationship = Object.values(table.schema).find(
f => isRelationshipField(f) && f.tableId === matchedTable?._id f => isRelationshipField(f) && f.tableId === matchedTable?._id
) )

View File

@ -1,6 +1,6 @@
import * as utils from "../../../../db/utils" import * as utils from "../../../../db/utils"
import { docIds } from "@budibase/backend-core" import { docIds, sql } from "@budibase/backend-core"
import { import {
Ctx, Ctx,
DatasourcePlusQueryResponse, DatasourcePlusQueryResponse,
@ -69,15 +69,15 @@ export function getSourceId(ctx: Ctx): { tableId: string; viewId?: string } {
viewId: sourceId, viewId: sourceId,
} }
} }
return { tableId: ctx.params.sourceId } return { tableId: sql.utils.encodeTableId(ctx.params.sourceId) }
} }
// now check for old way of specifying table ID // now check for old way of specifying table ID
if (ctx.params?.tableId) { if (ctx.params?.tableId) {
return { tableId: ctx.params.tableId } return { tableId: sql.utils.encodeTableId(ctx.params.tableId) }
} }
// check body for a table ID // check body for a table ID
if (ctx.request.body?.tableId) { if (ctx.request.body?.tableId) {
return { tableId: ctx.request.body.tableId } return { tableId: sql.utils.encodeTableId(ctx.request.body.tableId) }
} }
throw new Error("Unable to find table ID in request") throw new Error("Unable to find table ID in request")
} }

View File

@ -71,18 +71,27 @@ if (descriptions.length) {
let tableOrViewId: string let tableOrViewId: string
let rows: Row[] let rows: Row[]
async function basicRelationshipTables(type: RelationshipType) { async function basicRelationshipTables(
type: RelationshipType,
opts?: {
tableName?: string
primaryColumn?: string
otherColumn?: string
}
) {
const relatedTable = await createTable({ const relatedTable = await createTable({
name: { name: "name", type: FieldType.STRING }, name: { name: opts?.tableName || "name", type: FieldType.STRING },
}) })
const columnName = opts?.primaryColumn || "productCat"
//@ts-ignore - API accepts this structure, will build out rest of definition
const tableId = await createTable({ const tableId = await createTable({
name: { name: "name", type: FieldType.STRING }, name: { name: opts?.tableName || "name", type: FieldType.STRING },
//@ts-ignore - API accepts this structure, will build out rest of definition [columnName]: {
productCat: {
type: FieldType.LINK, type: FieldType.LINK,
relationshipType: type, relationshipType: type,
name: "productCat", name: columnName,
fieldName: "product", fieldName: opts?.otherColumn || "product",
tableId: relatedTable, tableId: relatedTable,
constraints: { constraints: {
type: "array", type: "array",
@ -2776,6 +2785,42 @@ if (descriptions.length) {
}) })
}) })
isSql &&
describe("relationship - table with spaces", () => {
let primaryTable: Table, row: Row
beforeAll(async () => {
const { relatedTable, tableId } =
await basicRelationshipTables(
RelationshipType.ONE_TO_MANY,
{
tableName: "table with spaces",
primaryColumn: "related",
otherColumn: "related",
}
)
tableOrViewId = tableId
primaryTable = relatedTable
row = await config.api.row.save(primaryTable._id!, {
name: "foo",
})
await config.api.row.save(tableOrViewId, {
name: "foo",
related: [row._id],
})
})
it("should be able to search by table name with spaces", async () => {
await expectQuery({
equal: {
["table with spaces.name"]: "foo",
},
}).toContain([{ name: "foo" }])
})
})
isSql && isSql &&
describe.each([ describe.each([
RelationshipType.MANY_TO_ONE, RelationshipType.MANY_TO_ONE,

View File

@ -1,4 +1,10 @@
import { context, db as dbCore, docIds, utils } from "@budibase/backend-core" import {
context,
db as dbCore,
docIds,
utils,
sql,
} from "@budibase/backend-core"
import { import {
DatabaseQueryOpts, DatabaseQueryOpts,
Datasource, Datasource,
@ -328,7 +334,7 @@ export function extractViewInfoFromID(viewId: string) {
const regex = new RegExp(`^(?<tableId>.+)${SEPARATOR}([^${SEPARATOR}]+)$`) const regex = new RegExp(`^(?<tableId>.+)${SEPARATOR}([^${SEPARATOR}]+)$`)
const res = regex.exec(viewId) const res = regex.exec(viewId)
return { return {
tableId: res!.groups!["tableId"], tableId: sql.utils.encodeTableId(res!.groups!["tableId"]),
} }
} }