Merge branch 'ts-portal-apps-store' of github.com:Budibase/budibase into ts-portal-auditlogs-store
This commit is contained in:
commit
d0b8ab79e3
|
@ -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"
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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: boolean
|
||||||
|
checklist?: ConfigChecklistResponse
|
||||||
|
status?: SystemStatusResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createAdminStore() {
|
||||||
|
const admin = writable<PortalAdminStore>({
|
||||||
loaded: false,
|
loaded: false,
|
||||||
multiTenancy: false,
|
multiTenancy: false,
|
||||||
cloud: false,
|
cloud: false,
|
||||||
isDev: false,
|
isDev: false,
|
||||||
disableAccountPortal: false,
|
disableAccountPortal: false,
|
||||||
accountPortalUrl: "",
|
|
||||||
importComplete: false,
|
|
||||||
checklist: {
|
|
||||||
apps: { checked: false },
|
|
||||||
smtp: { checked: false },
|
|
||||||
adminUser: { checked: false },
|
|
||||||
sso: { checked: false },
|
|
||||||
},
|
|
||||||
maintenance: [],
|
|
||||||
offlineMode: false,
|
offlineMode: false,
|
||||||
}
|
maintenance: [],
|
||||||
|
})
|
||||||
export function createAdminStore() {
|
|
||||||
const admin = writable(DEFAULT_CONFIG)
|
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
await getChecklist()
|
await getChecklist()
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 tableId = await createTable({
|
|
||||||
name: { name: "name", type: FieldType.STRING },
|
const columnName = opts?.primaryColumn || "productCat"
|
||||||
//@ts-ignore - API accepts this structure, will build out rest of definition
|
//@ts-ignore - API accepts this structure, will build out rest of definition
|
||||||
productCat: {
|
const tableId = await createTable({
|
||||||
|
name: { name: opts?.tableName || "name", type: FieldType.STRING },
|
||||||
|
[columnName]: {
|
||||||
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,
|
||||||
|
|
|
@ -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"]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue