1
0
Fork 0
mirror of synced 2024-07-13 18:26:06 +12:00

Merge pull request #11615 from Budibase/BUDI-7189/views_on_plus_ds

Support views on plus datasources
This commit is contained in:
Adria Navarro 2023-08-30 00:51:45 +02:00 committed by GitHub
commit f47a2c3016
5 changed files with 242 additions and 102 deletions

View file

@ -17,7 +17,6 @@ import {
FetchDatasourceInfoRequest, FetchDatasourceInfoRequest,
FetchDatasourceInfoResponse, FetchDatasourceInfoResponse,
IntegrationBase, IntegrationBase,
RestConfig,
SourceName, SourceName,
UpdateDatasourceResponse, UpdateDatasourceResponse,
UserCtx, UserCtx,
@ -27,7 +26,6 @@ import {
import sdk from "../../sdk" import sdk from "../../sdk"
import { builderSocket } from "../../websockets" import { builderSocket } from "../../websockets"
import { setupCreationAuth as googleSetupCreationAuth } from "../../integrations/googlesheets" import { setupCreationAuth as googleSetupCreationAuth } from "../../integrations/googlesheets"
import { areRESTVariablesValid } from "../../sdk/app/datasources/datasources"
function getErrorTables(errors: any, errorType: string) { function getErrorTables(errors: any, errorType: string) {
return Object.entries(errors) return Object.entries(errors)

View file

@ -10,6 +10,7 @@ import {
ViewV2, ViewV2,
} from "@budibase/types" } from "@budibase/types"
import { generator } from "@budibase/backend-core/tests" import { generator } from "@budibase/backend-core/tests"
import { generateDatasourceID } from "../../../db/utils"
function priceTable(): Table { function priceTable(): Table {
return { return {
@ -32,27 +33,52 @@ function priceTable(): Table {
} }
} }
describe("/v2/views", () => { const config = setup.getConfig()
const config = setup.getConfig()
afterAll(setup.afterAll) beforeAll(async () => {
await config.init()
})
describe.each([
["internal ds", () => config.createTable(priceTable())],
[
"external ds",
async () => {
const datasource = await config.createDatasource({
datasource: {
...setup.structures.basicDatasource().datasource,
plus: true,
_id: generateDatasourceID({ plus: true }),
},
})
return config.createTable({
...priceTable(),
sourceId: datasource._id,
type: "external",
})
},
],
])("/v2/views (%s)", (_, tableBuilder) => {
let table: Table
beforeAll(async () => { beforeAll(async () => {
await config.init() table = await tableBuilder()
await config.createTable(priceTable())
}) })
afterAll(setup.afterAll)
describe("create", () => { describe("create", () => {
it("persist the view when the view is successfully created", async () => { it("persist the view when the view is successfully created", async () => {
const newView: CreateViewRequest = { const newView: CreateViewRequest = {
name: generator.name(), name: generator.name(),
tableId: config.table!._id!, tableId: table._id!,
} }
const res = await config.api.viewV2.create(newView) const res = await config.api.viewV2.create(newView)
expect(res).toEqual({ expect(res).toEqual({
...newView, ...newView,
id: expect.stringMatching(new RegExp(`${config.table?._id!}_`)), id: expect.stringMatching(new RegExp(`${table._id!}_`)),
version: 2, version: 2,
}) })
}) })
@ -60,7 +86,7 @@ describe("/v2/views", () => {
it("can persist views with all fields", async () => { it("can persist views with all fields", async () => {
const newView: Required<CreateViewRequest> = { const newView: Required<CreateViewRequest> = {
name: generator.name(), name: generator.name(),
tableId: config.table!._id!, tableId: table._id!,
primaryDisplay: generator.word(), primaryDisplay: generator.word(),
query: [{ operator: "equal", field: "field", value: "value" }], query: [{ operator: "equal", field: "field", value: "value" }],
sort: { sort: {
@ -87,7 +113,7 @@ describe("/v2/views", () => {
it("persist only UI schema overrides", async () => { it("persist only UI schema overrides", async () => {
const newView: CreateViewRequest = { const newView: CreateViewRequest = {
name: generator.name(), name: generator.name(),
tableId: config.table!._id!, tableId: table._id!,
schema: { schema: {
Price: { Price: {
name: "Price", name: "Price",
@ -124,7 +150,7 @@ describe("/v2/views", () => {
it("will not throw an exception if the schema is 'deleting' non UI fields", async () => { it("will not throw an exception if the schema is 'deleting' non UI fields", async () => {
const newView: CreateViewRequest = { const newView: CreateViewRequest = {
name: generator.name(), name: generator.name(),
tableId: config.table!._id!, tableId: table._id!,
schema: { schema: {
Price: { Price: {
name: "Price", name: "Price",
@ -148,35 +174,29 @@ describe("/v2/views", () => {
let view: ViewV2 let view: ViewV2
beforeEach(async () => { beforeEach(async () => {
await config.createTable(priceTable()) table = await tableBuilder()
view = await config.api.viewV2.create({ name: "View A" }) view = await config.api.viewV2.create({ name: "View A" })
}) })
it("can update an existing view data", async () => { it("can update an existing view data", async () => {
const tableId = config.table!._id! const tableId = table._id!
await config.api.viewV2.update({ await config.api.viewV2.update({
...view, ...view,
query: [{ operator: "equal", field: "newField", value: "thatValue" }], query: [{ operator: "equal", field: "newField", value: "thatValue" }],
}) })
expect(await config.api.table.get(tableId)).toEqual({ expect((await config.api.table.get(tableId)).views).toEqual({
...config.table, [view.name]: {
views: { ...view,
[view.name]: { query: [{ operator: "equal", field: "newField", value: "thatValue" }],
...view, schema: expect.anything(),
query: [
{ operator: "equal", field: "newField", value: "thatValue" },
],
schema: expect.anything(),
},
}, },
_rev: expect.any(String),
updatedAt: expect.any(String),
}) })
}) })
it("can update all fields", async () => { it("can update all fields", async () => {
const tableId = config.table!._id! const tableId = table._id!
const updatedData: Required<UpdateViewRequest> = { const updatedData: Required<UpdateViewRequest> = {
version: view.version, version: view.version,
@ -204,29 +224,24 @@ describe("/v2/views", () => {
} }
await config.api.viewV2.update(updatedData) await config.api.viewV2.update(updatedData)
expect(await config.api.table.get(tableId)).toEqual({ expect((await config.api.table.get(tableId)).views).toEqual({
...config.table, [view.name]: {
views: { ...updatedData,
[view.name]: { schema: {
...updatedData, ...table.schema,
schema: { Category: expect.objectContaining({
...config.table!.schema, visible: false,
Category: expect.objectContaining({ }),
visible: false, Price: expect.objectContaining({
}), visible: false,
Price: expect.objectContaining({ }),
visible: false,
}),
},
}, },
}, },
_rev: expect.any(String),
updatedAt: expect.any(String),
}) })
}) })
it("can update an existing view name", async () => { it("can update an existing view name", async () => {
const tableId = config.table!._id! const tableId = table._id!
await config.api.viewV2.update({ ...view, name: "View B" }) await config.api.viewV2.update({ ...view, name: "View B" })
expect(await config.api.table.get(tableId)).toEqual( expect(await config.api.table.get(tableId)).toEqual(
@ -239,7 +254,7 @@ describe("/v2/views", () => {
}) })
it("cannot update an unexisting views nor edit ids", async () => { it("cannot update an unexisting views nor edit ids", async () => {
const tableId = config.table!._id! const tableId = table._id!
await config.api.viewV2.update( await config.api.viewV2.update(
{ ...view, id: generator.guid() }, { ...view, id: generator.guid() },
{ expectStatus: 404 } { expectStatus: 404 }
@ -258,7 +273,7 @@ describe("/v2/views", () => {
}) })
it("cannot update views with the wrong tableId", async () => { it("cannot update views with the wrong tableId", async () => {
const tableId = config.table!._id! const tableId = table._id!
await config.api.viewV2.update( await config.api.viewV2.update(
{ {
...view, ...view,
@ -374,12 +389,11 @@ describe("/v2/views", () => {
let view: ViewV2 let view: ViewV2
beforeAll(async () => { beforeAll(async () => {
await config.createTable(priceTable())
view = await config.api.viewV2.create() view = await config.api.viewV2.create()
}) })
it("can delete an existing view", async () => { it("can delete an existing view", async () => {
const tableId = config.table!._id! const tableId = table._id!
const getPersistedView = async () => const getPersistedView = async () =>
(await config.api.table.get(tableId)).views![view.name] (await config.api.table.get(tableId)).views![view.name]
@ -393,8 +407,6 @@ describe("/v2/views", () => {
describe("fetch view (through table)", () => { describe("fetch view (through table)", () => {
it("should be able to fetch a view V2", async () => { it("should be able to fetch a view V2", async () => {
const table = await config.createTable(priceTable())
const newView: CreateViewRequest = { const newView: CreateViewRequest = {
name: generator.name(), name: generator.name(),
tableId: table._id!, tableId: table._id!,

View file

@ -0,0 +1,88 @@
import { ViewV2 } from "@budibase/types"
import { context, HTTPError } from "@budibase/backend-core"
import sdk from "../../../sdk"
import * as utils from "../../../db/utils"
import { enrichSchema, isV2 } from "."
import { breakExternalTableId } from "../../../integrations/utils"
export async function get(
viewId: string,
opts?: { enriched: boolean }
): Promise<ViewV2> {
const { tableId } = utils.extractViewInfoFromID(viewId)
const { datasourceId, tableName } = breakExternalTableId(tableId)
const ds = await sdk.datasources.get(datasourceId!)
const table = ds.entities![tableName!]
const views = Object.values(table.views!)
const found = views.find(v => isV2(v) && v.id === viewId)
if (!found) {
throw new Error("No view found")
}
if (opts?.enriched) {
return enrichSchema(found, table.schema) as ViewV2
} else {
return found as ViewV2
}
}
export async function create(
tableId: string,
viewRequest: Omit<ViewV2, "id" | "version">
): Promise<ViewV2> {
const view: ViewV2 = {
...viewRequest,
id: utils.generateViewID(tableId),
version: 2,
}
const db = context.getAppDB()
const { datasourceId, tableName } = breakExternalTableId(tableId)
const ds = await sdk.datasources.get(datasourceId!)
ds.entities![tableName!].views ??= {}
ds.entities![tableName!].views![view.name] = view
await db.put(ds)
return view
}
export async function update(tableId: string, view: ViewV2): Promise<ViewV2> {
const db = context.getAppDB()
const { datasourceId, tableName } = breakExternalTableId(tableId)
const ds = await sdk.datasources.get(datasourceId!)
ds.entities![tableName!].views ??= {}
const views = ds.entities![tableName!].views!
const existingView = Object.values(views).find(
v => isV2(v) && v.id === view.id
)
if (!existingView) {
throw new HTTPError(`View ${view.id} not found in table ${tableId}`, 404)
}
console.log("set to", view)
delete views[existingView.name]
views[view.name] = view
await db.put(ds)
return view
}
export async function remove(viewId: string): Promise<ViewV2> {
const db = context.getAppDB()
const view = await get(viewId)
if (!view) {
throw new HTTPError(`View ${viewId} not found`, 404)
}
const { datasourceId, tableName } = breakExternalTableId(view.tableId)
const ds = await sdk.datasources.get(datasourceId!)
delete ds.entities![tableName!].views![view?.name]
await db.put(ds)
return view
}

View file

@ -1,64 +1,38 @@
import { RenameColumn, TableSchema, View, ViewV2 } from "@budibase/types" import { RenameColumn, TableSchema, View, ViewV2 } from "@budibase/types"
import { context, db as dbCore, HTTPError } from "@budibase/backend-core" import { db as dbCore } from "@budibase/backend-core"
import { cloneDeep } from "lodash" import { cloneDeep } from "lodash"
import sdk from "../../../sdk" import sdk from "../../../sdk"
import * as utils from "../../../db/utils" import * as utils from "../../../db/utils"
import { isExternalTable } from "../../../integrations/utils"
import * as internal from "./internal"
import * as external from "./external"
function pickApi(tableId: any) {
if (isExternalTable(tableId)) {
return external
}
return internal
}
export async function get( export async function get(
viewId: string, viewId: string,
opts?: { enriched: boolean } opts?: { enriched: boolean }
): Promise<ViewV2> { ): Promise<ViewV2> {
const { tableId } = utils.extractViewInfoFromID(viewId) const { tableId } = utils.extractViewInfoFromID(viewId)
const table = await sdk.tables.getTable(tableId) return pickApi(tableId).get(viewId, opts)
const views = Object.values(table.views!)
const found = views.find(v => isV2(v) && v.id === viewId)
if (!found) {
throw new Error("No view found")
}
if (opts?.enriched) {
return enrichSchema(found, table.schema) as ViewV2
} else {
return found as ViewV2
}
} }
export async function create( export async function create(
tableId: string, tableId: string,
viewRequest: Omit<ViewV2, "id" | "version"> viewRequest: Omit<ViewV2, "id" | "version">
): Promise<ViewV2> { ): Promise<ViewV2> {
const view: ViewV2 = { return pickApi(tableId).create(tableId, viewRequest)
...viewRequest,
id: utils.generateViewID(tableId),
version: 2,
}
const db = context.getAppDB()
const table = await sdk.tables.getTable(tableId)
table.views ??= {}
table.views[view.name] = view
await db.put(table)
return view
} }
export async function update(tableId: string, view: ViewV2): Promise<ViewV2> { export async function update(tableId: string, view: ViewV2): Promise<ViewV2> {
const db = context.getAppDB() return pickApi(tableId).update(tableId, view)
const table = await sdk.tables.getTable(tableId)
table.views ??= {}
const existingView = Object.values(table.views).find(
v => isV2(v) && v.id === view.id
)
if (!existingView) {
throw new HTTPError(`View ${view.id} not found in table ${tableId}`, 404)
}
console.log("set to", view)
delete table.views[existingView.name]
table.views[view.name] = view
await db.put(table)
return view
} }
export function isV2(view: View | ViewV2): view is ViewV2 { export function isV2(view: View | ViewV2): view is ViewV2 {
@ -66,17 +40,8 @@ export function isV2(view: View | ViewV2): view is ViewV2 {
} }
export async function remove(viewId: string): Promise<ViewV2> { export async function remove(viewId: string): Promise<ViewV2> {
const db = context.getAppDB() const { tableId } = utils.extractViewInfoFromID(viewId)
return pickApi(tableId).remove(viewId)
const view = await get(viewId)
const table = await sdk.tables.getTable(view?.tableId)
if (!view) {
throw new HTTPError(`View ${viewId} not found`, 404)
}
delete table.views![view?.name]
await db.put(table)
return view
} }
export function allowedFields(view: View | ViewV2) { export function allowedFields(view: View | ViewV2) {

View file

@ -0,0 +1,77 @@
import { ViewV2 } from "@budibase/types"
import { context, HTTPError } from "@budibase/backend-core"
import sdk from "../../../sdk"
import * as utils from "../../../db/utils"
import { enrichSchema, isV2 } from "."
export async function get(
viewId: string,
opts?: { enriched: boolean }
): Promise<ViewV2> {
const { tableId } = utils.extractViewInfoFromID(viewId)
const table = await sdk.tables.getTable(tableId)
const views = Object.values(table.views!)
const found = views.find(v => isV2(v) && v.id === viewId)
if (!found) {
throw new Error("No view found")
}
if (opts?.enriched) {
return enrichSchema(found, table.schema) as ViewV2
} else {
return found as ViewV2
}
}
export async function create(
tableId: string,
viewRequest: Omit<ViewV2, "id" | "version">
): Promise<ViewV2> {
const view: ViewV2 = {
...viewRequest,
id: utils.generateViewID(tableId),
version: 2,
}
const db = context.getAppDB()
const table = await sdk.tables.getTable(tableId)
table.views ??= {}
table.views[view.name] = view
await db.put(table)
return view
}
export async function update(tableId: string, view: ViewV2): Promise<ViewV2> {
const db = context.getAppDB()
const table = await sdk.tables.getTable(tableId)
table.views ??= {}
const existingView = Object.values(table.views).find(
v => isV2(v) && v.id === view.id
)
if (!existingView) {
throw new HTTPError(`View ${view.id} not found in table ${tableId}`, 404)
}
console.log("set to", view)
delete table.views[existingView.name]
table.views[view.name] = view
await db.put(table)
return view
}
export async function remove(viewId: string): Promise<ViewV2> {
const db = context.getAppDB()
const view = await get(viewId)
const table = await sdk.tables.getTable(view?.tableId)
if (!view) {
throw new HTTPError(`View ${viewId} not found`, 404)
}
delete table.views![view?.name]
await db.put(table)
return view
}