Merge branch 'develop' of github.com:Budibase/budibase into develop
This commit is contained in:
commit
c280e44b26
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.2.12-alpha.46",
|
"version": "2.2.12-alpha.49",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"name": "@budibase/backend-core",
|
||||||
"version": "2.2.12-alpha.46",
|
"version": "2.2.12-alpha.49",
|
||||||
"description": "Budibase backend core libraries used in server and worker",
|
"description": "Budibase backend core libraries used in server and worker",
|
||||||
"main": "dist/src/index.js",
|
"main": "dist/src/index.js",
|
||||||
"types": "dist/src/index.d.ts",
|
"types": "dist/src/index.d.ts",
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/nano": "10.1.1",
|
"@budibase/nano": "10.1.1",
|
||||||
"@budibase/types": "2.2.12-alpha.46",
|
"@budibase/types": "2.2.12-alpha.49",
|
||||||
"@shopify/jest-koa-mocks": "5.0.1",
|
"@shopify/jest-koa-mocks": "5.0.1",
|
||||||
"@techpass/passport-openidconnect": "0.3.2",
|
"@techpass/passport-openidconnect": "0.3.2",
|
||||||
"aws-cloudfront-sign": "2.2.0",
|
"aws-cloudfront-sign": "2.2.0",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/bbui",
|
"name": "@budibase/bbui",
|
||||||
"description": "A UI solution used in the different Budibase projects.",
|
"description": "A UI solution used in the different Budibase projects.",
|
||||||
"version": "2.2.12-alpha.46",
|
"version": "2.2.12-alpha.49",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@adobe/spectrum-css-workflow-icons": "1.2.1",
|
"@adobe/spectrum-css-workflow-icons": "1.2.1",
|
||||||
"@budibase/string-templates": "2.2.12-alpha.46",
|
"@budibase/string-templates": "2.2.12-alpha.49",
|
||||||
"@spectrum-css/accordion": "3.0.24",
|
"@spectrum-css/accordion": "3.0.24",
|
||||||
"@spectrum-css/actionbutton": "1.0.1",
|
"@spectrum-css/actionbutton": "1.0.1",
|
||||||
"@spectrum-css/actiongroup": "1.0.1",
|
"@spectrum-css/actiongroup": "1.0.1",
|
||||||
|
|
|
@ -134,7 +134,7 @@
|
||||||
on:blur={onBlur}
|
on:blur={onBlur}
|
||||||
on:focus={onFocus}
|
on:focus={onFocus}
|
||||||
on:input={onInput}
|
on:input={onInput}
|
||||||
{type}
|
type={hbsValue.length ? "text" : type}
|
||||||
style={align ? `text-align: ${align};` : ""}
|
style={align ? `text-align: ${align};` : ""}
|
||||||
class="spectrum-Textfield-input"
|
class="spectrum-Textfield-input"
|
||||||
inputmode={type === "number" ? "decimal" : "text"}
|
inputmode={type === "number" ? "decimal" : "text"}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "2.2.12-alpha.46",
|
"version": "2.2.12-alpha.49",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -71,10 +71,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.2.12-alpha.46",
|
"@budibase/bbui": "2.2.12-alpha.49",
|
||||||
"@budibase/client": "2.2.12-alpha.46",
|
"@budibase/client": "2.2.12-alpha.49",
|
||||||
"@budibase/frontend-core": "2.2.12-alpha.46",
|
"@budibase/frontend-core": "2.2.12-alpha.49",
|
||||||
"@budibase/string-templates": "2.2.12-alpha.46",
|
"@budibase/string-templates": "2.2.12-alpha.49",
|
||||||
"@sentry/browser": "5.19.1",
|
"@sentry/browser": "5.19.1",
|
||||||
"@spectrum-css/accordion": "^3.0.24",
|
"@spectrum-css/accordion": "^3.0.24",
|
||||||
"@spectrum-css/page": "^3.0.1",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
|
|
|
@ -39,6 +39,23 @@
|
||||||
$: showError($fetch.error)
|
$: showError($fetch.error)
|
||||||
$: id, (filters = null)
|
$: id, (filters = null)
|
||||||
|
|
||||||
|
let appliedFilter
|
||||||
|
let rawFilter
|
||||||
|
let appliedSort
|
||||||
|
let selectedRows = []
|
||||||
|
|
||||||
|
$: enrichedSchema,
|
||||||
|
() => {
|
||||||
|
appliedFilter = null
|
||||||
|
rawFilter = null
|
||||||
|
appliedSort = null
|
||||||
|
selectedRows = []
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if (Number.isInteger($fetch.pageNumber)) {
|
||||||
|
selectedRows = []
|
||||||
|
}
|
||||||
|
|
||||||
const showError = error => {
|
const showError = error => {
|
||||||
if (error) {
|
if (error) {
|
||||||
notifications.error(error?.message || "Unable to fetch data.")
|
notifications.error(error?.message || "Unable to fetch data.")
|
||||||
|
@ -95,11 +112,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch data whenever sorting option changes
|
// Fetch data whenever sorting option changes
|
||||||
const onSort = e => {
|
const onSort = async e => {
|
||||||
fetch.update({
|
const sort = {
|
||||||
sortColumn: e.detail.column,
|
sortColumn: e.detail.column,
|
||||||
sortOrder: e.detail.order,
|
sortOrder: e.detail.order,
|
||||||
})
|
}
|
||||||
|
await fetch.update(sort)
|
||||||
|
appliedSort = { ...sort }
|
||||||
|
appliedSort.sortOrder = appliedSort.sortOrder.toLowerCase()
|
||||||
|
selectedRows = []
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch data whenever filters change
|
// Fetch data whenever filters change
|
||||||
|
@ -108,16 +129,19 @@
|
||||||
fetch.update({
|
fetch.update({
|
||||||
filter: filters,
|
filter: filters,
|
||||||
})
|
})
|
||||||
|
appliedFilter = e.detail
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch data whenever schema changes
|
// Fetch data whenever schema changes
|
||||||
const onUpdateColumns = () => {
|
const onUpdateColumns = () => {
|
||||||
|
selectedRows = []
|
||||||
fetch.refresh()
|
fetch.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch data whenever rows are modified. Unfortunately we have to lose
|
// Fetch data whenever rows are modified. Unfortunately we have to lose
|
||||||
// our pagination place, as our bookmarks will have shifted.
|
// our pagination place, as our bookmarks will have shifted.
|
||||||
const onUpdateRows = () => {
|
const onUpdateRows = () => {
|
||||||
|
selectedRows = []
|
||||||
fetch.refresh()
|
fetch.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,6 +166,9 @@
|
||||||
disableSorting
|
disableSorting
|
||||||
on:updatecolumns={onUpdateColumns}
|
on:updatecolumns={onUpdateColumns}
|
||||||
on:updaterows={onUpdateRows}
|
on:updaterows={onUpdateRows}
|
||||||
|
on:selectionUpdated={e => {
|
||||||
|
selectedRows = e.detail
|
||||||
|
}}
|
||||||
customPlaceholder
|
customPlaceholder
|
||||||
>
|
>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
|
@ -183,6 +210,9 @@
|
||||||
<ExportButton
|
<ExportButton
|
||||||
disabled={!hasRows || !hasCols}
|
disabled={!hasRows || !hasCols}
|
||||||
view={$tables.selected?._id}
|
view={$tables.selected?._id}
|
||||||
|
filters={appliedFilter}
|
||||||
|
sorting={appliedSort}
|
||||||
|
{selectedRows}
|
||||||
/>
|
/>
|
||||||
{#key id}
|
{#key id}
|
||||||
<TableFilterButton
|
<TableFilterButton
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
UNSORTABLE_TYPES,
|
UNSORTABLE_TYPES,
|
||||||
} from "constants"
|
} from "constants"
|
||||||
import RoleCell from "./cells/RoleCell.svelte"
|
import RoleCell from "./cells/RoleCell.svelte"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
export let schema = {}
|
export let schema = {}
|
||||||
export let data = []
|
export let data = []
|
||||||
|
@ -28,6 +29,8 @@
|
||||||
export let disableSorting = false
|
export let disableSorting = false
|
||||||
export let customPlaceholder = false
|
export let customPlaceholder = false
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let selectedRows = []
|
let selectedRows = []
|
||||||
let editableColumn
|
let editableColumn
|
||||||
let editableRow
|
let editableRow
|
||||||
|
@ -36,6 +39,7 @@
|
||||||
let customRenderers = []
|
let customRenderers = []
|
||||||
let confirmDelete
|
let confirmDelete
|
||||||
|
|
||||||
|
$: selectedRows, dispatch("selectionUpdated", selectedRows)
|
||||||
$: isUsersTable = tableId === TableNames.USERS
|
$: isUsersTable = tableId === TableNames.USERS
|
||||||
$: data && resetSelectedRows()
|
$: data && resetSelectedRows()
|
||||||
$: editRowComponent = isUsersTable ? CreateEditUser : CreateEditRow
|
$: editRowComponent = isUsersTable ? CreateEditUser : CreateEditRow
|
||||||
|
|
|
@ -3,7 +3,10 @@
|
||||||
import ExportModal from "../modals/ExportModal.svelte"
|
import ExportModal from "../modals/ExportModal.svelte"
|
||||||
|
|
||||||
export let view
|
export let view
|
||||||
|
export let filters
|
||||||
|
export let sorting
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
|
export let selectedRows
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
</script>
|
</script>
|
||||||
|
@ -18,5 +21,5 @@
|
||||||
Export
|
Export
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
<ExportModal {view} />
|
<ExportModal {view} {filters} {sorting} {selectedRows} />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select, ModalContent, notifications } from "@budibase/bbui"
|
import {
|
||||||
|
Select,
|
||||||
|
ModalContent,
|
||||||
|
notifications,
|
||||||
|
Body,
|
||||||
|
Table,
|
||||||
|
} from "@budibase/bbui"
|
||||||
import download from "downloadjs"
|
import download from "downloadjs"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
|
import { Constants, LuceneUtils } from "@budibase/frontend-core"
|
||||||
|
|
||||||
const FORMATS = [
|
const FORMATS = [
|
||||||
{
|
{
|
||||||
|
@ -19,8 +26,71 @@
|
||||||
]
|
]
|
||||||
|
|
||||||
export let view
|
export let view
|
||||||
|
export let filters
|
||||||
|
export let sorting
|
||||||
|
export let selectedRows = []
|
||||||
|
|
||||||
let exportFormat = FORMATS[0].key
|
let exportFormat = FORMATS[0].key
|
||||||
|
let filterLookup
|
||||||
|
|
||||||
|
$: luceneFilter = LuceneUtils.buildLuceneQuery(filters)
|
||||||
|
$: exportOpDisplay = buildExportOpDisplay(sorting, filterDisplay, filters)
|
||||||
|
|
||||||
|
const buildFilterLookup = () => {
|
||||||
|
return Object.keys(Constants.OperatorOptions).reduce((acc, key) => {
|
||||||
|
const op = Constants.OperatorOptions[key]
|
||||||
|
acc[op.value] = op.label
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
filterLookup = buildFilterLookup()
|
||||||
|
|
||||||
|
const filterDisplay = () => {
|
||||||
|
if (!filters) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return filters.map(filter => {
|
||||||
|
let newFieldName = filter.field + ""
|
||||||
|
const parts = newFieldName.split(":")
|
||||||
|
parts.shift()
|
||||||
|
newFieldName = parts.join(":")
|
||||||
|
return {
|
||||||
|
Field: newFieldName,
|
||||||
|
Operation: filterLookup[filter.operator],
|
||||||
|
"Field Value": filter.value || "",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildExportOpDisplay = (sorting, filterDisplay) => {
|
||||||
|
let filterDisplayConfig = filterDisplay()
|
||||||
|
if (sorting) {
|
||||||
|
filterDisplayConfig = [
|
||||||
|
...filterDisplayConfig,
|
||||||
|
{
|
||||||
|
Field: sorting.sortColumn,
|
||||||
|
Operation: "Order By",
|
||||||
|
"Field Value": sorting.sortOrder,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return filterDisplayConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
const displaySchema = {
|
||||||
|
Field: {
|
||||||
|
type: "string",
|
||||||
|
fieldName: "Field",
|
||||||
|
},
|
||||||
|
Operation: {
|
||||||
|
type: "string",
|
||||||
|
fieldName: "Operation",
|
||||||
|
},
|
||||||
|
"Field Value": {
|
||||||
|
type: "string",
|
||||||
|
fieldName: "Value",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
async function exportView() {
|
async function exportView() {
|
||||||
try {
|
try {
|
||||||
|
@ -33,9 +103,74 @@
|
||||||
notifications.error(`Unable to export ${exportFormat.toUpperCase()} data`)
|
notifications.error(`Unable to export ${exportFormat.toUpperCase()} data`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function exportRows() {
|
||||||
|
if (selectedRows?.length) {
|
||||||
|
const data = await API.exportRows({
|
||||||
|
tableId: view,
|
||||||
|
rows: selectedRows.map(row => row._id),
|
||||||
|
format: exportFormat,
|
||||||
|
})
|
||||||
|
download(data, `export.${exportFormat}`)
|
||||||
|
} else if (filters || sorting) {
|
||||||
|
const data = await API.exportRows({
|
||||||
|
tableId: view,
|
||||||
|
format: exportFormat,
|
||||||
|
search: {
|
||||||
|
query: luceneFilter,
|
||||||
|
sort: sorting?.sortColumn,
|
||||||
|
sortOrder: sorting?.sortOrder,
|
||||||
|
paginate: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
download(data, `export.${exportFormat}`)
|
||||||
|
} else {
|
||||||
|
await exportView()
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ModalContent title="Export Data" confirmText="Export" onConfirm={exportView}>
|
<ModalContent
|
||||||
|
title="Export Data"
|
||||||
|
confirmText="Export"
|
||||||
|
onConfirm={exportRows}
|
||||||
|
size={filters?.length || sorting ? "M" : "S"}
|
||||||
|
>
|
||||||
|
{#if selectedRows?.length}
|
||||||
|
<Body size="S">
|
||||||
|
<strong>{selectedRows?.length}</strong>
|
||||||
|
{`row${selectedRows?.length > 1 ? "s" : ""} will be exported`}
|
||||||
|
</Body>
|
||||||
|
{:else if filters || (sorting?.sortOrder && sorting?.sortColumn)}
|
||||||
|
<Body size="S">
|
||||||
|
{#if !filters}
|
||||||
|
Exporting <strong>all</strong> rows
|
||||||
|
{:else}
|
||||||
|
Filters applied
|
||||||
|
{/if}
|
||||||
|
</Body>
|
||||||
|
|
||||||
|
<div class="table-wrap">
|
||||||
|
<Table
|
||||||
|
schema={displaySchema}
|
||||||
|
data={exportOpDisplay}
|
||||||
|
{filters}
|
||||||
|
loading={false}
|
||||||
|
rowCount={filters?.length + 1}
|
||||||
|
disableSorting={true}
|
||||||
|
allowSelectRows={false}
|
||||||
|
allowEditRows={false}
|
||||||
|
allowEditColumns={false}
|
||||||
|
quiet={true}
|
||||||
|
compact={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<Body size="S">
|
||||||
|
Exporting <strong>all</strong> rows
|
||||||
|
</Body>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
label="Format"
|
label="Format"
|
||||||
bind:value={exportFormat}
|
bind:value={exportFormat}
|
||||||
|
@ -45,3 +180,9 @@
|
||||||
getOptionValue={x => x.key}
|
getOptionValue={x => x.key}
|
||||||
/>
|
/>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.table-wrap :global(.wrapper) {
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -248,6 +248,7 @@
|
||||||
/>
|
/>
|
||||||
<EnvDropdown
|
<EnvDropdown
|
||||||
label="Password"
|
label="Password"
|
||||||
|
type="password"
|
||||||
bind:value={form.basic.password}
|
bind:value={form.basic.password}
|
||||||
on:change={onFieldChange}
|
on:change={onFieldChange}
|
||||||
on:blur={() => (blurred.basic.password = true)}
|
on:blur={() => (blurred.basic.password = true)}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "2.2.12-alpha.46",
|
"version": "2.2.12-alpha.49",
|
||||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -26,9 +26,9 @@
|
||||||
"outputPath": "build"
|
"outputPath": "build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "2.2.12-alpha.46",
|
"@budibase/backend-core": "2.2.12-alpha.49",
|
||||||
"@budibase/string-templates": "2.2.12-alpha.46",
|
"@budibase/string-templates": "2.2.12-alpha.49",
|
||||||
"@budibase/types": "2.2.12-alpha.46",
|
"@budibase/types": "2.2.12-alpha.49",
|
||||||
"axios": "0.21.2",
|
"axios": "0.21.2",
|
||||||
"chalk": "4.1.0",
|
"chalk": "4.1.0",
|
||||||
"cli-progress": "3.11.2",
|
"cli-progress": "3.11.2",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "2.2.12-alpha.46",
|
"version": "2.2.12-alpha.49",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"module": "dist/budibase-client.js",
|
"module": "dist/budibase-client.js",
|
||||||
"main": "dist/budibase-client.js",
|
"main": "dist/budibase-client.js",
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
"dev:builder": "rollup -cw"
|
"dev:builder": "rollup -cw"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.2.12-alpha.46",
|
"@budibase/bbui": "2.2.12-alpha.49",
|
||||||
"@budibase/frontend-core": "2.2.12-alpha.46",
|
"@budibase/frontend-core": "2.2.12-alpha.49",
|
||||||
"@budibase/string-templates": "2.2.12-alpha.46",
|
"@budibase/string-templates": "2.2.12-alpha.49",
|
||||||
"@spectrum-css/button": "^3.0.3",
|
"@spectrum-css/button": "^3.0.3",
|
||||||
"@spectrum-css/card": "^3.0.3",
|
"@spectrum-css/card": "^3.0.3",
|
||||||
"@spectrum-css/divider": "^1.0.3",
|
"@spectrum-css/divider": "^1.0.3",
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/frontend-core",
|
"name": "@budibase/frontend-core",
|
||||||
"version": "2.2.12-alpha.46",
|
"version": "2.2.12-alpha.49",
|
||||||
"description": "Budibase frontend core libraries used in builder and client",
|
"description": "Budibase frontend core libraries used in builder and client",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.2.12-alpha.46",
|
"@budibase/bbui": "2.2.12-alpha.49",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"svelte": "^3.46.2"
|
"svelte": "^3.46.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,12 +67,13 @@ export const buildRowEndpoints = API => ({
|
||||||
* @param format the format to export (csv or json)
|
* @param format the format to export (csv or json)
|
||||||
* @param columns which columns to export (all if undefined)
|
* @param columns which columns to export (all if undefined)
|
||||||
*/
|
*/
|
||||||
exportRows: async ({ tableId, rows, format, columns }) => {
|
exportRows: async ({ tableId, rows, format, columns, search }) => {
|
||||||
return await API.post({
|
return await API.post({
|
||||||
url: `/api/${tableId}/rows/exportRows?format=${format}`,
|
url: `/api/${tableId}/rows/exportRows?format=${format}`,
|
||||||
body: {
|
body: {
|
||||||
rows,
|
rows,
|
||||||
columns,
|
columns,
|
||||||
|
...search,
|
||||||
},
|
},
|
||||||
parseResponse: async response => {
|
parseResponse: async response => {
|
||||||
return await response.text()
|
return await response.text()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/sdk",
|
"name": "@budibase/sdk",
|
||||||
"version": "2.2.12-alpha.46",
|
"version": "2.2.12-alpha.49",
|
||||||
"description": "Budibase Public API SDK",
|
"description": "Budibase Public API SDK",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "2.2.12-alpha.46",
|
"version": "2.2.12-alpha.49",
|
||||||
"description": "Budibase Web Server",
|
"description": "Budibase Web Server",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -43,11 +43,11 @@
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apidevtools/swagger-parser": "10.0.3",
|
"@apidevtools/swagger-parser": "10.0.3",
|
||||||
"@budibase/backend-core": "2.2.12-alpha.46",
|
"@budibase/backend-core": "2.2.12-alpha.49",
|
||||||
"@budibase/client": "2.2.12-alpha.46",
|
"@budibase/client": "2.2.12-alpha.49",
|
||||||
"@budibase/pro": "2.2.12-alpha.46",
|
"@budibase/pro": "2.2.12-alpha.48",
|
||||||
"@budibase/string-templates": "2.2.12-alpha.46",
|
"@budibase/string-templates": "2.2.12-alpha.49",
|
||||||
"@budibase/types": "2.2.12-alpha.46",
|
"@budibase/types": "2.2.12-alpha.49",
|
||||||
"@bull-board/api": "3.7.0",
|
"@bull-board/api": "3.7.0",
|
||||||
"@bull-board/koa": "3.9.4",
|
"@bull-board/koa": "3.9.4",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
|
|
|
@ -21,6 +21,8 @@ import {
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
|
|
||||||
|
const { cleanExportRows } = require("./utils")
|
||||||
|
|
||||||
export async function handleRequest(
|
export async function handleRequest(
|
||||||
operation: Operation,
|
operation: Operation,
|
||||||
tableId: string,
|
tableId: string,
|
||||||
|
@ -100,7 +102,7 @@ export async function destroy(ctx: BBContext) {
|
||||||
export async function bulkDestroy(ctx: BBContext) {
|
export async function bulkDestroy(ctx: BBContext) {
|
||||||
const { rows } = ctx.request.body
|
const { rows } = ctx.request.body
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
let promises = []
|
let promises: Promise<Row[] | { row: Row; table: Table }>[] = []
|
||||||
for (let row of rows) {
|
for (let row of rows) {
|
||||||
promises.push(
|
promises.push(
|
||||||
handleRequest(Operation.DELETE, tableId, {
|
handleRequest(Operation.DELETE, tableId, {
|
||||||
|
@ -186,20 +188,24 @@ export async function exportRows(ctx: BBContext) {
|
||||||
if (!datasource || !datasource.entities) {
|
if (!datasource || !datasource.entities) {
|
||||||
ctx.throw(400, "Datasource has not been configured for plus API.")
|
ctx.throw(400, "Datasource has not been configured for plus API.")
|
||||||
}
|
}
|
||||||
ctx.request.body = {
|
|
||||||
query: {
|
if (ctx.request.body.rows) {
|
||||||
oneOf: {
|
ctx.request.body = {
|
||||||
_id: ctx.request.body.rows.map(
|
query: {
|
||||||
(row: string) => JSON.parse(decodeURI(row))[0]
|
oneOf: {
|
||||||
),
|
_id: ctx.request.body.rows.map(
|
||||||
|
(row: string) => JSON.parse(decodeURI(row))[0]
|
||||||
|
),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = await search(ctx)
|
let result = await search(ctx)
|
||||||
let rows: Row[] = []
|
let rows: Row[] = []
|
||||||
|
|
||||||
// Filter data to only specified columns if required
|
// Filter data to only specified columns if required
|
||||||
|
|
||||||
if (columns && columns.length) {
|
if (columns && columns.length) {
|
||||||
for (let i = 0; i < result.rows.length; i++) {
|
for (let i = 0; i < result.rows.length; i++) {
|
||||||
rows[i] = {}
|
rows[i] = {}
|
||||||
|
@ -211,14 +217,19 @@ export async function exportRows(ctx: BBContext) {
|
||||||
rows = result.rows
|
rows = result.rows
|
||||||
}
|
}
|
||||||
|
|
||||||
let headers = Object.keys(rows[0])
|
// @ts-ignore
|
||||||
|
let schema = datasource.entities[tableName].schema
|
||||||
|
let exportRows = cleanExportRows(rows, schema, format, columns)
|
||||||
|
|
||||||
|
let headers = Object.keys(schema)
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const exporter = exporters[format]
|
const exporter = exporters[format]
|
||||||
const filename = `export.${format}`
|
const filename = `export.${format}`
|
||||||
|
|
||||||
// send down the file
|
// send down the file
|
||||||
ctx.attachment(filename)
|
ctx.attachment(filename)
|
||||||
return apiFileReturn(exporter(headers, rows))
|
return apiFileReturn(exporter(headers, exportRows))
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchEnrichedRow(ctx: BBContext) {
|
export async function fetchEnrichedRow(ctx: BBContext) {
|
||||||
|
|
|
@ -27,7 +27,7 @@ import {
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { context, db as dbCore } from "@budibase/backend-core"
|
import { context, db as dbCore } from "@budibase/backend-core"
|
||||||
import { finaliseRow, updateRelatedFormula } from "./staticFormula"
|
import { finaliseRow, updateRelatedFormula } from "./staticFormula"
|
||||||
import { csv, json, jsonWithSchema, Format, isFormat } from "../view/exporters"
|
import { csv, json, jsonWithSchema, Format } from "../view/exporters"
|
||||||
import { apiFileReturn } from "../../../utilities/fileSystem"
|
import { apiFileReturn } from "../../../utilities/fileSystem"
|
||||||
import {
|
import {
|
||||||
Ctx,
|
Ctx,
|
||||||
|
@ -38,6 +38,8 @@ import {
|
||||||
Table,
|
Table,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
|
const { cleanExportRows } = require("./utils")
|
||||||
|
|
||||||
const CALCULATION_TYPES = {
|
const CALCULATION_TYPES = {
|
||||||
SUM: "sum",
|
SUM: "sum",
|
||||||
COUNT: "count",
|
COUNT: "count",
|
||||||
|
@ -357,6 +359,14 @@ export async function search(ctx: Ctx) {
|
||||||
params.version = ctx.version
|
params.version = ctx.version
|
||||||
params.tableId = tableId
|
params.tableId = tableId
|
||||||
|
|
||||||
|
let table
|
||||||
|
if (params.sort && !params.sortType) {
|
||||||
|
table = await db.get(tableId)
|
||||||
|
const schema = table.schema
|
||||||
|
const sortField = schema[params.sort]
|
||||||
|
params.sortType = sortField.type == "number" ? "number" : "string"
|
||||||
|
}
|
||||||
|
|
||||||
let response
|
let response
|
||||||
if (paginate) {
|
if (paginate) {
|
||||||
response = await paginatedSearch(query, params)
|
response = await paginatedSearch(query, params)
|
||||||
|
@ -370,7 +380,7 @@ export async function search(ctx: Ctx) {
|
||||||
if (tableId === InternalTables.USER_METADATA) {
|
if (tableId === InternalTables.USER_METADATA) {
|
||||||
response.rows = await getGlobalUsersFromMetadata(response.rows)
|
response.rows = await getGlobalUsersFromMetadata(response.rows)
|
||||||
}
|
}
|
||||||
const table = await db.get(tableId)
|
table = table || (await db.get(tableId))
|
||||||
response.rows = await outputProcessing(table, response.rows)
|
response.rows = await outputProcessing(table, response.rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -389,16 +399,25 @@ export async function exportRows(ctx: Ctx) {
|
||||||
const table = await db.get(ctx.params.tableId)
|
const table = await db.get(ctx.params.tableId)
|
||||||
const rowIds = ctx.request.body.rows
|
const rowIds = ctx.request.body.rows
|
||||||
let format = ctx.query.format
|
let format = ctx.query.format
|
||||||
const { columns } = ctx.request.body
|
const { columns, query } = ctx.request.body
|
||||||
let response = (
|
|
||||||
await db.allDocs({
|
let result
|
||||||
include_docs: true,
|
if (rowIds) {
|
||||||
keys: rowIds,
|
let response = (
|
||||||
})
|
await db.allDocs({
|
||||||
).rows.map(row => row.doc)
|
include_docs: true,
|
||||||
|
keys: rowIds,
|
||||||
|
})
|
||||||
|
).rows.map(row => row.doc)
|
||||||
|
|
||||||
|
result = await outputProcessing(table, response)
|
||||||
|
} else if (query) {
|
||||||
|
let searchResponse = await exports.search(ctx)
|
||||||
|
result = searchResponse.rows
|
||||||
|
}
|
||||||
|
|
||||||
let result = (await outputProcessing(table, response)) as Row[]
|
|
||||||
let rows: Row[] = []
|
let rows: Row[] = []
|
||||||
|
let schema = table.schema
|
||||||
|
|
||||||
// Filter data to only specified columns if required
|
// Filter data to only specified columns if required
|
||||||
if (columns && columns.length) {
|
if (columns && columns.length) {
|
||||||
|
@ -412,12 +431,16 @@ export async function exportRows(ctx: Ctx) {
|
||||||
rows = result
|
rows = result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let exportRows = cleanExportRows(rows, schema, format, columns)
|
||||||
if (format === Format.CSV) {
|
if (format === Format.CSV) {
|
||||||
ctx.attachment("export.csv")
|
ctx.attachment("export.csv")
|
||||||
return apiFileReturn(csv(Object.keys(rows[0]), rows))
|
return apiFileReturn(csv(Object.keys(rows[0]), exportRows))
|
||||||
} else if (format === Format.JSON) {
|
} else if (format === Format.JSON) {
|
||||||
ctx.attachment("export.json")
|
ctx.attachment("export.json")
|
||||||
return apiFileReturn(json(rows))
|
return apiFileReturn(json(exportRows))
|
||||||
|
} else if (format === Format.JSON_WITH_SCHEMA) {
|
||||||
|
ctx.attachment("export.json")
|
||||||
|
return apiFileReturn(jsonWithSchema(schema, exportRows))
|
||||||
} else {
|
} else {
|
||||||
throw "Format not recognised"
|
throw "Format not recognised"
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { BBContext, Row, Table } from "@budibase/types"
|
||||||
export { removeKeyNumbering } from "../../../integrations/base/utils"
|
export { removeKeyNumbering } from "../../../integrations/base/utils"
|
||||||
const validateJs = require("validate.js")
|
const validateJs = require("validate.js")
|
||||||
const { cloneDeep } = require("lodash/fp")
|
const { cloneDeep } = require("lodash/fp")
|
||||||
|
import { Format } from "../view/exporters"
|
||||||
import { Ctx } from "@budibase/types"
|
import { Ctx } from "@budibase/types"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
|
|
||||||
|
@ -117,3 +118,40 @@ export async function validate({
|
||||||
}
|
}
|
||||||
return { valid: Object.keys(errors).length === 0, errors }
|
return { valid: Object.keys(errors).length === 0, errors }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function cleanExportRows(
|
||||||
|
rows: any[],
|
||||||
|
schema: any,
|
||||||
|
format: string,
|
||||||
|
columns: string[]
|
||||||
|
) {
|
||||||
|
let cleanRows = [...rows]
|
||||||
|
|
||||||
|
const relationships = Object.entries(schema)
|
||||||
|
.filter((entry: any[]) => entry[1].type === FieldTypes.LINK)
|
||||||
|
.map(entry => entry[0])
|
||||||
|
|
||||||
|
relationships.forEach(column => {
|
||||||
|
cleanRows.forEach(row => {
|
||||||
|
delete row[column]
|
||||||
|
})
|
||||||
|
delete schema[column]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Intended to avoid 'undefined' in export
|
||||||
|
if (format === Format.CSV) {
|
||||||
|
const schemaKeys = Object.keys(schema)
|
||||||
|
for (let key of schemaKeys) {
|
||||||
|
if (columns?.length && columns.indexOf(key) > 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for (let row of cleanRows) {
|
||||||
|
if (row[key] == null) {
|
||||||
|
row[key] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanRows
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { apiFileReturn } from "../../../utilities/fileSystem"
|
||||||
import { csv, json, jsonWithSchema, Format, isFormat } from "./exporters"
|
import { csv, json, jsonWithSchema, Format, isFormat } from "./exporters"
|
||||||
import { deleteView, getView, getViews, saveView } from "./utils"
|
import { deleteView, getView, getViews, saveView } from "./utils"
|
||||||
import { fetchView } from "../row"
|
import { fetchView } from "../row"
|
||||||
import { FieldTypes } from "../../../constants"
|
|
||||||
import { context, events } from "@budibase/backend-core"
|
import { context, events } from "@budibase/backend-core"
|
||||||
import { DocumentType } from "../../../db/utils"
|
import { DocumentType } from "../../../db/utils"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
|
@ -15,6 +14,7 @@ import {
|
||||||
TableSchema,
|
TableSchema,
|
||||||
View,
|
View,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
import { cleanExportRows } from "../row/utils"
|
||||||
|
|
||||||
const { cloneDeep, isEqual } = require("lodash")
|
const { cloneDeep, isEqual } = require("lodash")
|
||||||
|
|
||||||
|
@ -162,39 +162,17 @@ export async function exportView(ctx: BBContext) {
|
||||||
schema = table.schema
|
schema = table.schema
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove any relationships
|
let exportRows = cleanExportRows(rows, schema, format, [])
|
||||||
const relationships = Object.entries(schema)
|
|
||||||
.filter(entry => entry[1].type === FieldTypes.LINK)
|
|
||||||
.map(entry => entry[0])
|
|
||||||
// iterate relationship columns and remove from and row and schema
|
|
||||||
relationships.forEach(column => {
|
|
||||||
rows.forEach(row => {
|
|
||||||
delete row[column]
|
|
||||||
})
|
|
||||||
delete schema[column]
|
|
||||||
})
|
|
||||||
|
|
||||||
// make sure no "undefined" entries appear in the CSV
|
|
||||||
if (format === Format.CSV) {
|
|
||||||
const schemaKeys = Object.keys(schema)
|
|
||||||
for (let key of schemaKeys) {
|
|
||||||
for (let row of rows) {
|
|
||||||
if (row[key] == null) {
|
|
||||||
row[key] = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (format === Format.CSV) {
|
if (format === Format.CSV) {
|
||||||
ctx.attachment(`${viewName}.csv`)
|
ctx.attachment(`${viewName}.csv`)
|
||||||
ctx.body = apiFileReturn(csv(Object.keys(schema), rows))
|
ctx.body = apiFileReturn(csv(Object.keys(schema), exportRows))
|
||||||
} else if (format === Format.JSON) {
|
} else if (format === Format.JSON) {
|
||||||
ctx.attachment(`${viewName}.json`)
|
ctx.attachment(`${viewName}.json`)
|
||||||
ctx.body = apiFileReturn(json(rows))
|
ctx.body = apiFileReturn(json(exportRows))
|
||||||
} else if (format === Format.JSON_WITH_SCHEMA) {
|
} else if (format === Format.JSON_WITH_SCHEMA) {
|
||||||
ctx.attachment(`${viewName}.json`)
|
ctx.attachment(`${viewName}.json`)
|
||||||
ctx.body = apiFileReturn(jsonWithSchema(schema, rows))
|
ctx.body = apiFileReturn(jsonWithSchema(schema, exportRows))
|
||||||
} else {
|
} else {
|
||||||
throw "Format not recognised"
|
throw "Format not recognised"
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,44 +7,3 @@
|
||||||
export interface QueryOptions {
|
export interface QueryOptions {
|
||||||
disableReturning?: boolean
|
disableReturning?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AuthType {
|
|
||||||
BASIC = "basic",
|
|
||||||
BEARER = "bearer",
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AuthConfig {
|
|
||||||
_id: string
|
|
||||||
name: string
|
|
||||||
type: AuthType
|
|
||||||
config: BasicAuthConfig | BearerAuthConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BasicAuthConfig {
|
|
||||||
username: string
|
|
||||||
password: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BearerAuthConfig {
|
|
||||||
token: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RestConfig {
|
|
||||||
url: string
|
|
||||||
rejectUnauthorized: boolean
|
|
||||||
defaultHeaders: {
|
|
||||||
[key: string]: any
|
|
||||||
}
|
|
||||||
legacyHttpParser: boolean
|
|
||||||
authConfigs: AuthConfig[]
|
|
||||||
staticVariables: {
|
|
||||||
[key: string]: string
|
|
||||||
}
|
|
||||||
dynamicVariables: [
|
|
||||||
{
|
|
||||||
name: string
|
|
||||||
queryId: string
|
|
||||||
value: string
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,13 +6,11 @@ import {
|
||||||
IntegrationBase,
|
IntegrationBase,
|
||||||
PaginationValues,
|
PaginationValues,
|
||||||
RestQueryFields as RestQuery,
|
RestQueryFields as RestQuery,
|
||||||
} from "@budibase/types"
|
|
||||||
import {
|
|
||||||
RestConfig,
|
RestConfig,
|
||||||
AuthType,
|
RestAuthType,
|
||||||
BasicAuthConfig,
|
RestBasicAuthConfig,
|
||||||
BearerAuthConfig,
|
RestBearerAuthConfig,
|
||||||
} from "../definitions/datasource"
|
} from "@budibase/types"
|
||||||
import { get } from "lodash"
|
import { get } from "lodash"
|
||||||
import * as https from "https"
|
import * as https from "https"
|
||||||
import qs from "querystring"
|
import qs from "querystring"
|
||||||
|
@ -331,14 +329,14 @@ class RestIntegration implements IntegrationBase {
|
||||||
if (authConfig) {
|
if (authConfig) {
|
||||||
let config
|
let config
|
||||||
switch (authConfig.type) {
|
switch (authConfig.type) {
|
||||||
case AuthType.BASIC:
|
case RestAuthType.BASIC:
|
||||||
config = authConfig.config as BasicAuthConfig
|
config = authConfig.config as RestBasicAuthConfig
|
||||||
headers.Authorization = `Basic ${Buffer.from(
|
headers.Authorization = `Basic ${Buffer.from(
|
||||||
`${config.username}:${config.password}`
|
`${config.username}:${config.password}`
|
||||||
).toString("base64")}`
|
).toString("base64")}`
|
||||||
break
|
break
|
||||||
case AuthType.BEARER:
|
case RestAuthType.BEARER:
|
||||||
config = authConfig.config as BearerAuthConfig
|
config = authConfig.config as RestBearerAuthConfig
|
||||||
headers.Authorization = `Bearer ${config.token}`
|
headers.Authorization = `Bearer ${config.token}`
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -428,5 +426,4 @@ class RestIntegration implements IntegrationBase {
|
||||||
export default {
|
export default {
|
||||||
schema: SCHEMA,
|
schema: SCHEMA,
|
||||||
integration: RestIntegration,
|
integration: RestIntegration,
|
||||||
AuthType,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ jest.mock("node-fetch", () => {
|
||||||
|
|
||||||
import fetch from "node-fetch"
|
import fetch from "node-fetch"
|
||||||
import { default as RestIntegration } from "../rest"
|
import { default as RestIntegration } from "../rest"
|
||||||
|
import { RestAuthType } from "@budibase/types"
|
||||||
const FormData = require("form-data")
|
const FormData = require("form-data")
|
||||||
const { URLSearchParams } = require("url")
|
const { URLSearchParams } = require("url")
|
||||||
|
|
||||||
|
@ -229,7 +230,7 @@ describe("REST Integration", () => {
|
||||||
const basicAuth = {
|
const basicAuth = {
|
||||||
_id: "c59c14bd1898a43baa08da68959b24686",
|
_id: "c59c14bd1898a43baa08da68959b24686",
|
||||||
name: "basic-1",
|
name: "basic-1",
|
||||||
type: RestIntegration.AuthType.BASIC,
|
type: RestAuthType.BASIC,
|
||||||
config: {
|
config: {
|
||||||
username: "user",
|
username: "user",
|
||||||
password: "password",
|
password: "password",
|
||||||
|
@ -239,7 +240,7 @@ describe("REST Integration", () => {
|
||||||
const bearerAuth = {
|
const bearerAuth = {
|
||||||
_id: "0d91d732f34e4befabeff50b392a8ff3",
|
_id: "0d91d732f34e4befabeff50b392a8ff3",
|
||||||
name: "bearer-1",
|
name: "bearer-1",
|
||||||
type: RestIntegration.AuthType.BEARER,
|
type: RestAuthType.BEARER,
|
||||||
config: {
|
config: {
|
||||||
token: "mytoken",
|
token: "mytoken",
|
||||||
},
|
},
|
||||||
|
@ -581,6 +582,7 @@ describe("REST Integration", () => {
|
||||||
})
|
})
|
||||||
await config.integration.read({})
|
await config.integration.read({})
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
const calls: any = fetch.mock.calls[0]
|
const calls: any = fetch.mock.calls[0]
|
||||||
const url = calls[0]
|
const url = calls[0]
|
||||||
expect(url).toBe(`${BASE_URL}/`)
|
expect(url).toBe(`${BASE_URL}/`)
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
import { context } from "@budibase/backend-core"
|
import { context } from "@budibase/backend-core"
|
||||||
import { processObjectSync, findHBSBlocks } from "@budibase/string-templates"
|
import { findHBSBlocks, processObjectSync } from "@budibase/string-templates"
|
||||||
import {
|
import {
|
||||||
Datasource,
|
Datasource,
|
||||||
DatasourceFieldType,
|
DatasourceFieldType,
|
||||||
Integration,
|
|
||||||
PASSWORD_REPLACEMENT,
|
PASSWORD_REPLACEMENT,
|
||||||
|
RestAuthConfig,
|
||||||
|
RestAuthType,
|
||||||
|
RestBasicAuthConfig,
|
||||||
|
SourceName,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { getEnvironmentVariables } from "../../utils"
|
import { getEnvironmentVariables } from "../../utils"
|
||||||
import { getDefinitions } from "../../../integrations"
|
import { getDefinitions } from "../../../integrations"
|
||||||
|
|
||||||
const ENV_VAR_PREFIX = "env."
|
const ENV_VAR_PREFIX = "env."
|
||||||
const USER_PREFIX = "user"
|
|
||||||
|
|
||||||
async function enrichDatasourceWithValues(datasource: Datasource) {
|
async function enrichDatasourceWithValues(datasource: Datasource) {
|
||||||
const cloned = cloneDeep(datasource)
|
const cloned = cloneDeep(datasource)
|
||||||
|
@ -47,6 +49,18 @@ export async function getWithEnvVars(datasourceId: string) {
|
||||||
return enrichDatasourceWithValues(datasource)
|
return enrichDatasourceWithValues(datasource)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasAuthConfigs(datasource: Datasource) {
|
||||||
|
return datasource.source === SourceName.REST && datasource.config?.authConfigs
|
||||||
|
}
|
||||||
|
|
||||||
|
function useEnvVars(str: any) {
|
||||||
|
if (typeof str !== "string") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const blocks = findHBSBlocks(str)
|
||||||
|
return blocks.find(block => block.includes(ENV_VAR_PREFIX)) != null
|
||||||
|
}
|
||||||
|
|
||||||
export async function removeSecrets(datasources: Datasource[]) {
|
export async function removeSecrets(datasources: Datasource[]) {
|
||||||
const definitions = await getDefinitions()
|
const definitions = await getDefinitions()
|
||||||
for (let datasource of datasources) {
|
for (let datasource of datasources) {
|
||||||
|
@ -56,17 +70,24 @@ export async function removeSecrets(datasources: Datasource[]) {
|
||||||
if (datasource.config.auth) {
|
if (datasource.config.auth) {
|
||||||
delete datasource.config.auth
|
delete datasource.config.auth
|
||||||
}
|
}
|
||||||
// remove passwords
|
// specific to REST datasources, contains passwords
|
||||||
for (let key of Object.keys(datasource.config)) {
|
if (hasAuthConfigs(datasource)) {
|
||||||
if (typeof datasource.config[key] !== "string") {
|
const configs = datasource.config.authConfigs as RestAuthConfig[]
|
||||||
continue
|
for (let config of configs) {
|
||||||
|
if (config.type !== RestAuthType.BASIC) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const basic = config.config as RestBasicAuthConfig
|
||||||
|
if (!useEnvVars(basic.password)) {
|
||||||
|
basic.password = PASSWORD_REPLACEMENT
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const blocks = findHBSBlocks(datasource.config[key] as string)
|
}
|
||||||
const usesEnvVars =
|
// remove general passwords
|
||||||
blocks.find(block => block.includes(ENV_VAR_PREFIX)) != null
|
for (let key of Object.keys(datasource.config)) {
|
||||||
if (
|
if (
|
||||||
!usesEnvVars &&
|
schema.datasource?.[key]?.type === DatasourceFieldType.PASSWORD &&
|
||||||
schema.datasource?.[key]?.type === DatasourceFieldType.PASSWORD
|
!useEnvVars(datasource.config[key])
|
||||||
) {
|
) {
|
||||||
datasource.config[key] = PASSWORD_REPLACEMENT
|
datasource.config[key] = PASSWORD_REPLACEMENT
|
||||||
}
|
}
|
||||||
|
@ -84,6 +105,23 @@ export function mergeConfigs(update: Datasource, old: Datasource) {
|
||||||
if (!update.config) {
|
if (!update.config) {
|
||||||
return update
|
return update
|
||||||
}
|
}
|
||||||
|
// specific to REST datasources, fix the auth configs again if required
|
||||||
|
if (hasAuthConfigs(update)) {
|
||||||
|
const configs = update.config.authConfigs as RestAuthConfig[]
|
||||||
|
const oldConfigs = old.config?.authConfigs as RestAuthConfig[]
|
||||||
|
for (let config of configs) {
|
||||||
|
if (config.type !== RestAuthType.BASIC) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const basic = config.config as RestBasicAuthConfig
|
||||||
|
const oldBasic = oldConfigs.find(old => old.name === config.name)
|
||||||
|
?.config as RestBasicAuthConfig
|
||||||
|
if (basic.password === PASSWORD_REPLACEMENT) {
|
||||||
|
basic.password = oldBasic.password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// update back to actual passwords for everything else
|
||||||
for (let [key, value] of Object.entries(update.config)) {
|
for (let [key, value] of Object.entries(update.config)) {
|
||||||
if (value !== PASSWORD_REPLACEMENT) {
|
if (value !== PASSWORD_REPLACEMENT) {
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -1273,13 +1273,13 @@
|
||||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||||
|
|
||||||
"@budibase/backend-core@2.2.12-alpha.46":
|
"@budibase/backend-core@2.2.12-alpha.48":
|
||||||
version "2.2.12-alpha.46"
|
version "2.2.12-alpha.48"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.46.tgz#ac2c8168c4083e34be69e36e1b898f2b4e60a9bf"
|
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.48.tgz#d211474e8ba57f2aa8d9c7b263cfbe96f7051ab9"
|
||||||
integrity sha512-YNXF3/B17CrnOXfv27mzY1zEbBD/j5Nvr6ADJbakw7yrc3G58diLkJBo4kxzeEH4ujG3ftLDGLcbBP+zzoASZQ==
|
integrity sha512-7UuMHuV7jcaq5Y9fz7PM+YgT5bexuhm2UOoTivAtKOJwUR5Au7lGunVAZqLB5BPp55B9ncD/mQ5fev16vrep0Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/nano" "10.1.1"
|
"@budibase/nano" "10.1.1"
|
||||||
"@budibase/types" "2.2.12-alpha.46"
|
"@budibase/types" "2.2.12-alpha.48"
|
||||||
"@shopify/jest-koa-mocks" "5.0.1"
|
"@shopify/jest-koa-mocks" "5.0.1"
|
||||||
"@techpass/passport-openidconnect" "0.3.2"
|
"@techpass/passport-openidconnect" "0.3.2"
|
||||||
aws-cloudfront-sign "2.2.0"
|
aws-cloudfront-sign "2.2.0"
|
||||||
|
@ -1374,13 +1374,13 @@
|
||||||
qs "^6.11.0"
|
qs "^6.11.0"
|
||||||
tough-cookie "^4.1.2"
|
tough-cookie "^4.1.2"
|
||||||
|
|
||||||
"@budibase/pro@2.2.12-alpha.46":
|
"@budibase/pro@2.2.12-alpha.48":
|
||||||
version "2.2.12-alpha.46"
|
version "2.2.12-alpha.48"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.46.tgz#bdd89640eea306a2fa7980387696c75b60ea0cc0"
|
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.48.tgz#639431a654dbe55c316a94e4220adae76e5f7540"
|
||||||
integrity sha512-baiejsUGZfE+VSCj1Pb7sG5MDNqwzUTZ915t+YJ8tGg05TbR+/RaBLGfKkONbl9iaXK9Nl4KgiNytXvZQCZULw==
|
integrity sha512-QMilBTDN/y5ieTnIjSlovhbmBLfTQV11B3YapJcdtpWKALyGpuZ7eYBY2DIjZtxTx5BE1Ib+BAJZmBxyXlkcHg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/backend-core" "2.2.12-alpha.46"
|
"@budibase/backend-core" "2.2.12-alpha.48"
|
||||||
"@budibase/types" "2.2.12-alpha.46"
|
"@budibase/types" "2.2.12-alpha.48"
|
||||||
"@koa/router" "8.0.8"
|
"@koa/router" "8.0.8"
|
||||||
bull "4.10.1"
|
bull "4.10.1"
|
||||||
joi "17.6.0"
|
joi "17.6.0"
|
||||||
|
@ -1406,10 +1406,10 @@
|
||||||
svelte-apexcharts "^1.0.2"
|
svelte-apexcharts "^1.0.2"
|
||||||
svelte-flatpickr "^3.1.0"
|
svelte-flatpickr "^3.1.0"
|
||||||
|
|
||||||
"@budibase/types@2.2.12-alpha.46":
|
"@budibase/types@2.2.12-alpha.48":
|
||||||
version "2.2.12-alpha.46"
|
version "2.2.12-alpha.48"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.46.tgz#ff305f4b5dc505380f1f075a5a54b1794f24175c"
|
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.48.tgz#5036062395c0290e6d7d83c2438945a4fbe33432"
|
||||||
integrity sha512-zMEIdD1/ylTu/EKbZ7v0Ulh31T6RR7HCa0ClmDjAdjVokeTC7pLQuUKg3OTKGNiJDwIPAPBlX45c1hCB4PZA8g==
|
integrity sha512-DrGGGaJL7QIwM5W40jXFWHdVU7g3mTqqhqSmciglitaapngYnkVAztkS2itjimStB/6WuxE+V2dRKAJgkvKeGw==
|
||||||
|
|
||||||
"@bull-board/api@3.7.0":
|
"@bull-board/api@3.7.0":
|
||||||
version "3.7.0"
|
version "3.7.0"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/string-templates",
|
"name": "@budibase/string-templates",
|
||||||
"version": "2.2.12-alpha.46",
|
"version": "2.2.12-alpha.49",
|
||||||
"description": "Handlebars wrapper for Budibase templating.",
|
"description": "Handlebars wrapper for Budibase templating.",
|
||||||
"main": "src/index.cjs",
|
"main": "src/index.cjs",
|
||||||
"module": "dist/bundle.mjs",
|
"module": "dist/bundle.mjs",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/types",
|
"name": "@budibase/types",
|
||||||
"version": "2.2.12-alpha.46",
|
"version": "2.2.12-alpha.49",
|
||||||
"description": "Budibase types",
|
"description": "Budibase types",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|
|
@ -15,3 +15,44 @@ export interface Datasource extends Document {
|
||||||
[key: string]: Table
|
[key: string]: Table
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum RestAuthType {
|
||||||
|
BASIC = "basic",
|
||||||
|
BEARER = "bearer",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RestBasicAuthConfig {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RestBearerAuthConfig {
|
||||||
|
token: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RestAuthConfig {
|
||||||
|
_id: string
|
||||||
|
name: string
|
||||||
|
type: RestAuthType
|
||||||
|
config: RestBasicAuthConfig | RestBearerAuthConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RestConfig {
|
||||||
|
url: string
|
||||||
|
rejectUnauthorized: boolean
|
||||||
|
defaultHeaders: {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
legacyHttpParser: boolean
|
||||||
|
authConfigs: RestAuthConfig[]
|
||||||
|
staticVariables: {
|
||||||
|
[key: string]: string
|
||||||
|
}
|
||||||
|
dynamicVariables: [
|
||||||
|
{
|
||||||
|
name: string
|
||||||
|
queryId: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/worker",
|
"name": "@budibase/worker",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "2.2.12-alpha.46",
|
"version": "2.2.12-alpha.49",
|
||||||
"description": "Budibase background service",
|
"description": "Budibase background service",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -36,10 +36,10 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "2.2.12-alpha.46",
|
"@budibase/backend-core": "2.2.12-alpha.49",
|
||||||
"@budibase/pro": "2.2.12-alpha.46",
|
"@budibase/pro": "2.2.12-alpha.48",
|
||||||
"@budibase/string-templates": "2.2.12-alpha.46",
|
"@budibase/string-templates": "2.2.12-alpha.49",
|
||||||
"@budibase/types": "2.2.12-alpha.46",
|
"@budibase/types": "2.2.12-alpha.49",
|
||||||
"@koa/router": "8.0.8",
|
"@koa/router": "8.0.8",
|
||||||
"@sentry/node": "6.17.7",
|
"@sentry/node": "6.17.7",
|
||||||
"@techpass/passport-openidconnect": "0.3.2",
|
"@techpass/passport-openidconnect": "0.3.2",
|
||||||
|
|
|
@ -470,13 +470,13 @@
|
||||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||||
|
|
||||||
"@budibase/backend-core@2.2.12-alpha.46":
|
"@budibase/backend-core@2.2.12-alpha.48":
|
||||||
version "2.2.12-alpha.46"
|
version "2.2.12-alpha.48"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.46.tgz#ac2c8168c4083e34be69e36e1b898f2b4e60a9bf"
|
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.48.tgz#d211474e8ba57f2aa8d9c7b263cfbe96f7051ab9"
|
||||||
integrity sha512-YNXF3/B17CrnOXfv27mzY1zEbBD/j5Nvr6ADJbakw7yrc3G58diLkJBo4kxzeEH4ujG3ftLDGLcbBP+zzoASZQ==
|
integrity sha512-7UuMHuV7jcaq5Y9fz7PM+YgT5bexuhm2UOoTivAtKOJwUR5Au7lGunVAZqLB5BPp55B9ncD/mQ5fev16vrep0Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/nano" "10.1.1"
|
"@budibase/nano" "10.1.1"
|
||||||
"@budibase/types" "2.2.12-alpha.46"
|
"@budibase/types" "2.2.12-alpha.48"
|
||||||
"@shopify/jest-koa-mocks" "5.0.1"
|
"@shopify/jest-koa-mocks" "5.0.1"
|
||||||
"@techpass/passport-openidconnect" "0.3.2"
|
"@techpass/passport-openidconnect" "0.3.2"
|
||||||
aws-cloudfront-sign "2.2.0"
|
aws-cloudfront-sign "2.2.0"
|
||||||
|
@ -521,13 +521,13 @@
|
||||||
qs "^6.11.0"
|
qs "^6.11.0"
|
||||||
tough-cookie "^4.1.2"
|
tough-cookie "^4.1.2"
|
||||||
|
|
||||||
"@budibase/pro@2.2.12-alpha.46":
|
"@budibase/pro@2.2.12-alpha.48":
|
||||||
version "2.2.12-alpha.46"
|
version "2.2.12-alpha.48"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.46.tgz#bdd89640eea306a2fa7980387696c75b60ea0cc0"
|
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.48.tgz#639431a654dbe55c316a94e4220adae76e5f7540"
|
||||||
integrity sha512-baiejsUGZfE+VSCj1Pb7sG5MDNqwzUTZ915t+YJ8tGg05TbR+/RaBLGfKkONbl9iaXK9Nl4KgiNytXvZQCZULw==
|
integrity sha512-QMilBTDN/y5ieTnIjSlovhbmBLfTQV11B3YapJcdtpWKALyGpuZ7eYBY2DIjZtxTx5BE1Ib+BAJZmBxyXlkcHg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/backend-core" "2.2.12-alpha.46"
|
"@budibase/backend-core" "2.2.12-alpha.48"
|
||||||
"@budibase/types" "2.2.12-alpha.46"
|
"@budibase/types" "2.2.12-alpha.48"
|
||||||
"@koa/router" "8.0.8"
|
"@koa/router" "8.0.8"
|
||||||
bull "4.10.1"
|
bull "4.10.1"
|
||||||
joi "17.6.0"
|
joi "17.6.0"
|
||||||
|
@ -535,10 +535,10 @@
|
||||||
lru-cache "^7.14.1"
|
lru-cache "^7.14.1"
|
||||||
node-fetch "^2.6.1"
|
node-fetch "^2.6.1"
|
||||||
|
|
||||||
"@budibase/types@2.2.12-alpha.46":
|
"@budibase/types@2.2.12-alpha.48":
|
||||||
version "2.2.12-alpha.46"
|
version "2.2.12-alpha.48"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.46.tgz#ff305f4b5dc505380f1f075a5a54b1794f24175c"
|
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.48.tgz#5036062395c0290e6d7d83c2438945a4fbe33432"
|
||||||
integrity sha512-zMEIdD1/ylTu/EKbZ7v0Ulh31T6RR7HCa0ClmDjAdjVokeTC7pLQuUKg3OTKGNiJDwIPAPBlX45c1hCB4PZA8g==
|
integrity sha512-DrGGGaJL7QIwM5W40jXFWHdVU7g3mTqqhqSmciglitaapngYnkVAztkS2itjimStB/6WuxE+V2dRKAJgkvKeGw==
|
||||||
|
|
||||||
"@cspotcode/source-map-support@^0.8.0":
|
"@cspotcode/source-map-support@^0.8.0":
|
||||||
version "0.8.1"
|
version "0.8.1"
|
||||||
|
|
Loading…
Reference in New Issue