Merge branch 'master' into BUDI-8084/single-attachment-column-setting
This commit is contained in:
commit
a276c71ad9
|
@ -85,7 +85,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const automationErrorMessage = appId => {
|
const automationErrorMessage = appId => {
|
||||||
const app = enrichedApps.find(app => app.devId === appId)
|
const app = $enrichedApps.find(app => app.devId === appId)
|
||||||
const errors = automationErrors[appId]
|
const errors = automationErrors[appId]
|
||||||
return `${app.name} - Automation error (${errorCount(errors)})`
|
return `${app.name} - Automation error (${errorCount(errors)})`
|
||||||
}
|
}
|
||||||
|
|
|
@ -852,6 +852,42 @@ describe.each([
|
||||||
expect(Object.keys(row).length).toEqual(1)
|
expect(Object.keys(row).length).toEqual(1)
|
||||||
expect(row._id).toEqual(existing._id)
|
expect(row._id).toEqual(existing._id)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should handle single quotes in row filtering", async () => {
|
||||||
|
const existing = await config.api.row.save(table._id!, {})
|
||||||
|
const res = await config.api.row.exportRows(table._id!, {
|
||||||
|
rows: [`['${existing._id!}']`],
|
||||||
|
})
|
||||||
|
const results = JSON.parse(res)
|
||||||
|
expect(results.length).toEqual(1)
|
||||||
|
const row = results[0]
|
||||||
|
expect(row._id).toEqual(existing._id)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return an error on composite keys", async () => {
|
||||||
|
const existing = await config.api.row.save(table._id!, {})
|
||||||
|
await config.api.row.exportRows(
|
||||||
|
table._id!,
|
||||||
|
{
|
||||||
|
rows: [`['${existing._id!}']`, "['d001', '10111']"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
message: "Export data does not support composite keys.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return an error if no table is found", async () => {
|
||||||
|
const existing = await config.api.row.save(table._id!, {})
|
||||||
|
await config.api.row.exportRows(
|
||||||
|
"1234567",
|
||||||
|
{ rows: [existing._id!] },
|
||||||
|
{ status: 404 }
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("view 2.0", () => {
|
describe("view 2.0", () => {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
context,
|
context,
|
||||||
db,
|
db,
|
||||||
|
HTTPError,
|
||||||
SearchParams as InternalSearchParams,
|
SearchParams as InternalSearchParams,
|
||||||
} from "@budibase/backend-core"
|
} from "@budibase/backend-core"
|
||||||
import env from "../../../../environment"
|
import env from "../../../../environment"
|
||||||
|
@ -31,6 +32,7 @@ import sdk from "../../../../sdk"
|
||||||
import { ExportRowsParams, ExportRowsResult } from "../search"
|
import { ExportRowsParams, ExportRowsResult } from "../search"
|
||||||
import { searchInputMapping } from "./utils"
|
import { searchInputMapping } from "./utils"
|
||||||
import pick from "lodash/pick"
|
import pick from "lodash/pick"
|
||||||
|
import { breakRowIdField } from "../../../../integrations/utils"
|
||||||
|
|
||||||
export async function search(options: SearchParams) {
|
export async function search(options: SearchParams) {
|
||||||
const { tableId } = options
|
const { tableId } = options
|
||||||
|
@ -103,7 +105,16 @@ export async function exportRows(
|
||||||
let response = (
|
let response = (
|
||||||
await db.allDocs({
|
await db.allDocs({
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
keys: rowIds,
|
keys: rowIds.map((row: string) => {
|
||||||
|
const ids = breakRowIdField(row)
|
||||||
|
if (ids.length > 1) {
|
||||||
|
throw new HTTPError(
|
||||||
|
"Export data does not support composite keys.",
|
||||||
|
400
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return ids[0]
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
).rows.map(row => row.doc)
|
).rows.map(row => row.doc)
|
||||||
|
|
||||||
|
|
|
@ -1,132 +0,0 @@
|
||||||
import { exportRows } from "../../app/rows/search/external"
|
|
||||||
import sdk from "../.."
|
|
||||||
import { ExternalRequest } from "../../../api/controllers/row/ExternalRequest"
|
|
||||||
import { ExportRowsParams } from "../../app/rows/search"
|
|
||||||
import { Format } from "../../../api/controllers/view/exporters"
|
|
||||||
import { HTTPError } from "@budibase/backend-core"
|
|
||||||
import { Operation } from "@budibase/types"
|
|
||||||
|
|
||||||
const mockDatasourcesGet = jest.fn()
|
|
||||||
const mockTableGet = jest.fn()
|
|
||||||
sdk.datasources.get = mockDatasourcesGet
|
|
||||||
sdk.tables.getTable = mockTableGet
|
|
||||||
|
|
||||||
jest.mock("../../../api/controllers/row/ExternalRequest")
|
|
||||||
jest.mock("../../../utilities/rowProcessor", () => ({
|
|
||||||
outputProcessing: jest.fn((_, rows) => rows),
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock("../../../api/controllers/view/exporters", () => ({
|
|
||||||
...jest.requireActual("../../../api/controllers/view/exporters"),
|
|
||||||
Format: {
|
|
||||||
CSV: "csv",
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
jest.mock("../../../utilities/fileSystem")
|
|
||||||
|
|
||||||
describe("external row sdk", () => {
|
|
||||||
describe("exportRows", () => {
|
|
||||||
function getExportOptions(): ExportRowsParams {
|
|
||||||
return {
|
|
||||||
tableId: "datasource__tablename",
|
|
||||||
format: Format.CSV,
|
|
||||||
query: {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const externalRequestCall = jest.fn()
|
|
||||||
beforeAll(() => {
|
|
||||||
jest
|
|
||||||
.spyOn(ExternalRequest.prototype, "run")
|
|
||||||
.mockImplementation(externalRequestCall.mockResolvedValue([]))
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should throw a 400 if no datasource entities are present", async () => {
|
|
||||||
const exportOptions = getExportOptions()
|
|
||||||
await expect(exportRows(exportOptions)).rejects.toThrowError(
|
|
||||||
new HTTPError("Datasource has not been configured for plus API.", 400)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should handle single quotes from a row ID", async () => {
|
|
||||||
mockDatasourcesGet.mockImplementation(async () => ({
|
|
||||||
entities: {
|
|
||||||
tablename: {
|
|
||||||
schema: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
const exportOptions = getExportOptions()
|
|
||||||
exportOptions.rowIds = ["['d001']"]
|
|
||||||
|
|
||||||
await exportRows(exportOptions)
|
|
||||||
|
|
||||||
expect(ExternalRequest).toBeCalledTimes(1)
|
|
||||||
expect(ExternalRequest).toBeCalledWith(
|
|
||||||
Operation.READ,
|
|
||||||
exportOptions.tableId,
|
|
||||||
undefined
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(externalRequestCall).toBeCalledTimes(1)
|
|
||||||
expect(externalRequestCall).toBeCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
filters: {
|
|
||||||
oneOf: {
|
|
||||||
_id: ["d001"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should throw a 400 if any composite keys are present", async () => {
|
|
||||||
const exportOptions = getExportOptions()
|
|
||||||
exportOptions.rowIds = ["[123]", "['d001'%2C'10111']"]
|
|
||||||
await expect(exportRows(exportOptions)).rejects.toThrowError(
|
|
||||||
new HTTPError("Export data does not support composite keys.", 400)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should throw a 400 if no table name was found", async () => {
|
|
||||||
const exportOptions = getExportOptions()
|
|
||||||
exportOptions.tableId = "datasource__"
|
|
||||||
exportOptions.rowIds = ["[123]"]
|
|
||||||
|
|
||||||
await expect(exportRows(exportOptions)).rejects.toThrowError(
|
|
||||||
new HTTPError("Could not find table name.", 400)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should only export specified columns", async () => {
|
|
||||||
mockDatasourcesGet.mockImplementation(async () => ({
|
|
||||||
entities: {
|
|
||||||
tablename: {
|
|
||||||
schema: {
|
|
||||||
name: {},
|
|
||||||
age: {},
|
|
||||||
dob: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
const headers = ["name", "dob"]
|
|
||||||
|
|
||||||
const result = await exportRows({
|
|
||||||
tableId: "datasource__tablename",
|
|
||||||
format: Format.CSV,
|
|
||||||
query: {},
|
|
||||||
columns: headers,
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(result).toEqual({
|
|
||||||
fileName: "export.csv",
|
|
||||||
content: `"name","dob"`,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -143,16 +143,12 @@ export abstract class TestAPI {
|
||||||
return await request
|
return await request
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _request = async <T>(
|
protected _checkResponse = (
|
||||||
method: Method,
|
response: Response,
|
||||||
url: string,
|
expectations?: Expectations
|
||||||
opts?: RequestOpts
|
) => {
|
||||||
): Promise<T> => {
|
|
||||||
const { expectations } = opts || {}
|
|
||||||
const { status = 200 } = expectations || {}
|
const { status = 200 } = expectations || {}
|
||||||
|
|
||||||
const response = await this._requestRaw(method, url, opts)
|
|
||||||
|
|
||||||
if (response.status !== status) {
|
if (response.status !== status) {
|
||||||
let message = `Expected status ${status} but got ${response.status}`
|
let message = `Expected status ${status} but got ${response.status}`
|
||||||
|
|
||||||
|
@ -191,6 +187,17 @@ export abstract class TestAPI {
|
||||||
expect(response.body).toMatchObject(expectations.body)
|
expect(response.body).toMatchObject(expectations.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.body
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _request = async <T>(
|
||||||
|
method: Method,
|
||||||
|
url: string,
|
||||||
|
opts?: RequestOpts
|
||||||
|
): Promise<T> => {
|
||||||
|
return this._checkResponse(
|
||||||
|
await this._requestRaw(method, url, opts),
|
||||||
|
opts?.expectations
|
||||||
|
).body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,6 @@ export class LegacyViewAPI extends TestAPI {
|
||||||
query: { view: viewName, format },
|
query: { view: viewName, format },
|
||||||
expectations,
|
expectations,
|
||||||
})
|
})
|
||||||
return response.text
|
return this._checkResponse(response, expectations).text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,8 @@ import {
|
||||||
BulkImportResponse,
|
BulkImportResponse,
|
||||||
SearchRowResponse,
|
SearchRowResponse,
|
||||||
SearchParams,
|
SearchParams,
|
||||||
DeleteRowRequest,
|
|
||||||
DeleteRows,
|
DeleteRows,
|
||||||
DeleteRow,
|
DeleteRow,
|
||||||
ExportRowsResponse,
|
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { Expectations, TestAPI } from "./base"
|
import { Expectations, TestAPI } from "./base"
|
||||||
|
|
||||||
|
@ -117,6 +115,7 @@ export class RowAPI extends TestAPI {
|
||||||
expectations,
|
expectations,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
this._checkResponse(response, expectations)
|
||||||
return response.text
|
return response.text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2062,9 +2062,9 @@ find-up@^4.0.0, find-up@^4.1.0:
|
||||||
path-exists "^4.0.0"
|
path-exists "^4.0.0"
|
||||||
|
|
||||||
follow-redirects@^1.14.0, follow-redirects@^1.14.4, follow-redirects@^1.15.0:
|
follow-redirects@^1.14.0, follow-redirects@^1.14.4, follow-redirects@^1.15.0:
|
||||||
version "1.15.2"
|
version "1.15.6"
|
||||||
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz"
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
|
||||||
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
|
integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
|
||||||
|
|
||||||
forever-agent@~0.6.1:
|
forever-agent@~0.6.1:
|
||||||
version "0.6.1"
|
version "0.6.1"
|
||||||
|
|
Loading…
Reference in New Issue