1
0
Fork 0
mirror of synced 2024-06-12 07:24:43 +12:00
budibase/packages/server/src/sdk/app/rows/search/external.ts

245 lines
6 KiB
TypeScript

import {
SortJson,
SortDirection,
Operation,
PaginationJson,
IncludeRelationship,
Row,
SearchFilters,
RowSearchParams,
SearchResponse,
Table,
} from "@budibase/types"
import * as exporters from "../../../../api/controllers/view/exporters"
import { handleRequest } from "../../../../api/controllers/row/external"
import {
breakExternalTableId,
breakRowIdField,
} from "../../../../integrations/utils"
import { utils } from "@budibase/shared-core"
import { ExportRowsParams, ExportRowsResult } from "./types"
import { HTTPError, db } from "@budibase/backend-core"
import pick from "lodash/pick"
import { outputProcessing } from "../../../../utilities/rowProcessor"
import sdk from "../../../"
export async function search(
options: RowSearchParams,
table: Table
): Promise<SearchResponse<Row>> {
const { tableId } = options
const { paginate, query, ...params } = options
const { limit } = params
let bookmark =
(params.bookmark && parseInt(params.bookmark as string)) || undefined
if (paginate && !bookmark) {
bookmark = 1
}
let paginateObj = {}
if (paginate) {
paginateObj = {
// add one so we can track if there is another page
limit: limit,
page: bookmark,
}
} else if (params && limit) {
paginateObj = {
limit: limit,
}
}
let sort: SortJson | undefined
if (params.sort) {
const direction =
params.sortOrder === "descending"
? SortDirection.DESCENDING
: SortDirection.ASCENDING
sort = {
[params.sort]: { direction },
}
}
// Make sure oneOf _id queries decode the Row IDs
if (query?.oneOf?._id) {
const rowIds = query.oneOf._id
query.oneOf._id = rowIds.map((row: string) => {
const ids = breakRowIdField(row)
return ids[0]
})
}
try {
let rows = await handleRequest(Operation.READ, tableId, {
filters: query,
sort,
paginate: paginateObj as PaginationJson,
includeSqlRelationships: IncludeRelationship.INCLUDE,
})
let hasNextPage = false
if (paginate && rows.length === limit) {
const nextRows = await handleRequest(Operation.READ, tableId, {
filters: query,
sort,
paginate: {
limit: 1,
page: bookmark! * limit + 1,
},
includeSqlRelationships: IncludeRelationship.INCLUDE,
})
hasNextPage = nextRows.length > 0
}
if (options.fields) {
const fields = [...options.fields, ...db.CONSTANT_EXTERNAL_ROW_COLS]
rows = rows.map((r: any) => pick(r, fields))
}
rows = await outputProcessing<Row[]>(table, rows, {
preserveLinks: true,
squash: true,
})
// need wrapper object for bookmarks etc when paginating
return { rows, hasNextPage, bookmark: bookmark && bookmark + 1 }
} catch (err: any) {
if (err.message && err.message.includes("does not exist")) {
throw new Error(
`Table updated externally, please re-fetch - ${err.message}`
)
} else {
throw err
}
}
}
export async function exportRows(
options: ExportRowsParams
): Promise<ExportRowsResult> {
const {
tableId,
format,
columns,
rowIds,
query,
sort,
sortOrder,
delimiter,
customHeaders,
} = options
const { datasourceId, tableName } = breakExternalTableId(tableId)
let requestQuery: SearchFilters = {}
if (rowIds?.length) {
requestQuery = {
oneOf: {
_id: 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]
}),
},
}
} else {
requestQuery = query || {}
}
const datasource = await sdk.datasources.get(datasourceId!)
const table = await sdk.tables.getTable(tableId)
if (!datasource || !datasource.entities) {
throw new HTTPError("Datasource has not been configured for plus API.", 400)
}
let result = await search(
{ tableId, query: requestQuery, sort, sortOrder },
table
)
let rows: Row[] = []
let headers
if (!tableName) {
throw new HTTPError("Could not find table name.", 400)
}
// Filter data to only specified columns if required
if (columns && columns.length) {
for (let i = 0; i < result.rows.length; i++) {
rows[i] = {}
for (let column of columns) {
rows[i][column] = result.rows[i][column]
}
}
headers = columns
} else {
rows = result.rows
}
const schema = datasource.entities[tableName].schema
let exportRows = sdk.rows.utils.cleanExportRows(
rows,
schema,
format,
columns,
customHeaders
)
let content: string
switch (format) {
case exporters.Format.CSV:
content = exporters.csv(
headers ?? Object.keys(schema),
exportRows,
delimiter,
customHeaders
)
break
case exporters.Format.JSON:
content = exporters.json(exportRows)
break
case exporters.Format.JSON_WITH_SCHEMA:
content = exporters.jsonWithSchema(schema, exportRows)
break
default:
throw utils.unreachable(format)
}
const fileName = `export.${format}`
return {
fileName,
content,
}
}
export async function fetch(tableId: string): Promise<Row[]> {
const response = await handleRequest<Operation.READ>(
Operation.READ,
tableId,
{
includeSqlRelationships: IncludeRelationship.INCLUDE,
}
)
const table = await sdk.tables.getTable(tableId)
return await outputProcessing<Row[]>(table, response, {
preserveLinks: true,
squash: true,
})
}
export async function fetchRaw(tableId: string): Promise<Row[]> {
return await handleRequest<Operation.READ>(Operation.READ, tableId, {
includeSqlRelationships: IncludeRelationship.INCLUDE,
})
}
export async function fetchView(viewName: string) {
// there are no views in external datasources, shouldn't ever be called
// for now just fetch
const split = viewName.split("all_")
const tableId = split[1] ? split[1] : split[0]
return fetch(tableId)
}