From 6845f866897a3ce8efc86f85888b66776b6928fe Mon Sep 17 00:00:00 2001 From: Andrew Kingston <andrew@kingston.dev> Date: Mon, 20 Jun 2022 12:32:13 +0100 Subject: [PATCH] Add option to customise which columns are exported in export data action --- .../actions/ExportData.svelte | 63 +++++++++++++------ packages/client/src/utils/buttonActions.js | 1 + packages/frontend-core/src/api/rows.js | 5 +- .../src/api/controllers/row/external.js | 21 ++++++- 4 files changed, 66 insertions(+), 24 deletions(-) diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/actions/ExportData.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/actions/ExportData.svelte index 062b9abd4c..aa3bf2a36b 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/actions/ExportData.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/actions/ExportData.svelte @@ -1,27 +1,18 @@ <script> - import { Label, Select, Body } from "@budibase/bbui" - import { findAllMatchingComponents } from "builderStore/componentUtils" + import { Label, Select, Body, Multiselect } from "@budibase/bbui" + import { + findAllMatchingComponents, + findComponent, + } from "builderStore/componentUtils" import { currentAsset } from "builderStore" import { onMount } from "svelte" + import { + getDatasourceForProvider, + getSchemaForDatasource, + } from "builderStore/dataBinding" export let parameters - $: tables = findAllMatchingComponents($currentAsset?.props, component => - component._component.endsWith("table") - ).map(table => ({ - label: table._instanceName, - value: table._id, - })) - - $: tableBlocks = findAllMatchingComponents($currentAsset?.props, component => - component._component.endsWith("tableblock") - ).map(block => ({ - label: block._instanceName, - value: `${block._id}-table`, - })) - - $: componentOptions = tables.concat(tableBlocks) - const FORMATS = [ { label: "CSV", @@ -33,6 +24,32 @@ }, ] + $: tables = findAllMatchingComponents($currentAsset?.props, component => + component._component.endsWith("table") + ).map(table => ({ + label: table._instanceName, + value: table._id, + })) + $: tableBlocks = findAllMatchingComponents($currentAsset?.props, component => + component._component.endsWith("tableblock") + ).map(block => ({ + label: block._instanceName, + value: `${block._id}-table`, + })) + $: componentOptions = tables.concat(tableBlocks) + $: columnOptions = getColumnOptions(parameters.tableComponentId) + + const getColumnOptions = tableId => { + // Strip block suffix if block component + if (tableId?.includes("-")) { + tableId = tableId.split("-")[0] + } + const selectedTable = findComponent($currentAsset?.props, tableId) + const datasource = getDatasourceForProvider($currentAsset, selectedTable) + const { schema } = getSchemaForDatasource($currentAsset, datasource) + return Object.keys(schema || {}) + } + onMount(() => { if (!parameters.type) { parameters.type = "csv" @@ -53,10 +70,16 @@ <Select bind:value={parameters.tableComponentId} options={componentOptions} + on:change={() => (parameters.columns = [])} /> - <Label small>Export as</Label> <Select bind:value={parameters.type} options={FORMATS} /> + <Label small>Export columns</Label> + <Multiselect + placeholder="All columns" + bind:value={parameters.columns} + options={columnOptions} + /> </div> </div> @@ -80,7 +103,7 @@ display: grid; column-gap: var(--spacing-xs); row-gap: var(--spacing-s); - grid-template-columns: 70px 1fr; + grid-template-columns: 90px 1fr; align-items: center; } </style> diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js index 473b563bb1..319c0a5feb 100644 --- a/packages/client/src/utils/buttonActions.js +++ b/packages/client/src/utils/buttonActions.js @@ -270,6 +270,7 @@ const exportDataHandler = async action => { tableId: selection.tableId, rows: selection.selectedRows, format: action.parameters.type, + columns: action.parameters.columns, }) download(data, `${selection.tableId}.${action.parameters.type}`) } catch (error) { diff --git a/packages/frontend-core/src/api/rows.js b/packages/frontend-core/src/api/rows.js index 9f980678c5..70030d7f80 100644 --- a/packages/frontend-core/src/api/rows.js +++ b/packages/frontend-core/src/api/rows.js @@ -65,12 +65,15 @@ export const buildRowEndpoints = API => ({ * Exports rows. * @param tableId the table ID to export the rows from * @param rows the array of rows to export + * @param format the format to export (csv or json) + * @param columns which columns to export (all if undefined) */ - exportRows: async ({ tableId, rows, format }) => { + exportRows: async ({ tableId, rows, format, columns }) => { return await API.post({ url: `/api/${tableId}/rows/exportRows?format=${format}`, body: { rows, + columns, }, parseResponse: async response => { return await response.text() diff --git a/packages/server/src/api/controllers/row/external.js b/packages/server/src/api/controllers/row/external.js index 29238f2678..b1c322b8b6 100644 --- a/packages/server/src/api/controllers/row/external.js +++ b/packages/server/src/api/controllers/row/external.js @@ -157,7 +157,8 @@ exports.validate = async () => { exports.exportRows = async ctx => { const { datasourceId } = breakExternalTableId(ctx.params.tableId) const db = getAppDB() - let format = ctx.query.format + const format = ctx.query.format + const { columns } = ctx.request.body const datasource = await db.get(datasourceId) if (!datasource || !datasource.entities) { ctx.throw(400, "Datasource has not been configured for plus API.") @@ -171,13 +172,27 @@ exports.exportRows = async ctx => { } let result = await exports.search(ctx) - let headers = Object.keys(result.rows[0]) + let rows = [] + + // 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] + } + } + } else { + rows = result.rows + } + + let headers = Object.keys(rows[0]) const exporter = exporters[format] const filename = `export.${format}` // send down the file ctx.attachment(filename) - return apiFileReturn(exporter(headers, result.rows)) + return apiFileReturn(exporter(headers, rows)) } exports.fetchEnrichedRow = async ctx => {