diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js
index 60cce2b1fd..5b9bebcbf5 100644
--- a/packages/builder/src/builderStore/dataBinding.js
+++ b/packages/builder/src/builderStore/dataBinding.js
@@ -331,7 +331,9 @@ const getSelectedRowsBindings = asset => {
bindings = bindings.concat(
tables.map(table => ({
type: "context",
- runtimeBinding: `${safeState}.${makePropSafe(table._id)}`,
+ runtimeBinding: `${safeState}.${makePropSafe(table._id)}.${makePropSafe(
+ "selectedRows"
+ )}`,
readableBinding: `${table._instanceName}.Selected rows`,
}))
)
@@ -343,7 +345,9 @@ const getSelectedRowsBindings = asset => {
bindings = bindings.concat(
tableBlocks.map(block => ({
type: "context",
- runtimeBinding: `${safeState}.${makePropSafe(block._id + "-table")}`,
+ runtimeBinding: `${safeState}.${makePropSafe(
+ block._id + "-table"
+ )}.${makePropSafe("selectedRows")}`,
readableBinding: `${block._instanceName}.Selected rows`,
}))
)
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
new file mode 100644
index 0000000000..93b9a556a6
--- /dev/null
+++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/actions/ExportData.svelte
@@ -0,0 +1,71 @@
+
+
+
+
+ Choose the table that you would like to export your row selection from.
+
+ Please ensure you have enabled row selection in the table settings
+
+
+
+ Table
+ option.name}
+ getOptionValue={option => option._id}
+ />
+
+ Type
+
+
+
+
+
diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/actions/index.js b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/actions/index.js
index 416ebffb1a..6593c9cbd4 100644
--- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/actions/index.js
+++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/actions/index.js
@@ -12,3 +12,4 @@ export { default as UpdateState } from "./UpdateState.svelte"
export { default as RefreshDataProvider } from "./RefreshDataProvider.svelte"
export { default as DuplicateRow } from "./DuplicateRow.svelte"
export { default as S3Upload } from "./S3Upload.svelte"
+export { default as ExportData } from "./ExportData.svelte"
diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/manifest.json b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/manifest.json
index ecbf0d8065..0f6d3344b2 100644
--- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/manifest.json
+++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/manifest.json
@@ -80,6 +80,10 @@
"value": "publicUrl"
}
]
+ },
+ {
+ "name": "Export Data",
+ "component": "ExportData"
}
]
}
\ No newline at end of file
diff --git a/packages/client/package.json b/packages/client/package.json
index 53838962f7..ccd67be70b 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -32,6 +32,7 @@
"@spectrum-css/vars": "^3.0.1",
"apexcharts": "^3.22.1",
"dayjs": "^1.10.5",
+ "downloadjs": "1.4.7",
"regexparam": "^1.3.0",
"rollup-plugin-polyfill-node": "^0.8.0",
"shortid": "^2.2.15",
diff --git a/packages/client/src/components/app/table/Table.svelte b/packages/client/src/components/app/table/Table.svelte
index f5f591688f..e8dcd30929 100644
--- a/packages/client/src/components/app/table/Table.svelte
+++ b/packages/client/src/components/app/table/Table.svelte
@@ -42,6 +42,7 @@
$: {
rowSelectionStore.actions.updateSelection(
$component.id,
+ selectedRows.length ? selectedRows[0].tableId : "",
selectedRows.map(row => row._id)
)
}
diff --git a/packages/client/src/stores/rowSelection.js b/packages/client/src/stores/rowSelection.js
index 3d1f2038aa..4561557c6f 100644
--- a/packages/client/src/stores/rowSelection.js
+++ b/packages/client/src/stores/rowSelection.js
@@ -1,20 +1,29 @@
-import { writable } from "svelte/store"
+import { get, writable } from "svelte/store"
const createRowSelectionStore = () => {
const store = writable({})
- function updateSelection(componentId, selectedRows) {
+ function updateSelection(componentId, tableId, selectedRows) {
store.update(state => {
- state[componentId] = [...selectedRows]
+ state[componentId] = { tableId: tableId, selectedRows: selectedRows }
return state
})
}
+ function getSelection(tableId) {
+ const selection = get(store)
+ const componentId = Object.keys(selection).find(
+ componentId => selection[componentId].tableId === tableId
+ )
+ return componentId ? selection[componentId] : {}
+ }
+
return {
subscribe: store.subscribe,
set: store.set,
actions: {
updateSelection,
+ getSelection,
},
}
}
diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js
index fec966725b..5d0f4ff71e 100644
--- a/packages/client/src/utils/buttonActions.js
+++ b/packages/client/src/utils/buttonActions.js
@@ -1,4 +1,5 @@
import { get } from "svelte/store"
+import download from "downloadjs"
import {
routeStore,
builderStore,
@@ -8,6 +9,7 @@ import {
notificationStore,
dataSourceStore,
uploadStore,
+ rowSelectionStore,
} from "stores"
import { API } from "api"
import { ActionTypes } from "constants"
@@ -239,6 +241,26 @@ const s3UploadHandler = async action => {
}
}
+const exportDataHandler = async action => {
+ let selection = rowSelectionStore.actions.getSelection(
+ action.parameters.tableId
+ )
+ if (selection.selectedRows && selection.selectedRows.length > 0) {
+ try {
+ const data = await API.exportRows({
+ tableId: selection.tableId,
+ rows: selection.selectedRows,
+ })
+
+ download(JSON.stringify(data), `export.${action.parameters.type}`)
+ } catch (error) {
+ notificationStore.actions.error("There was an error exporting the data")
+ }
+ } else {
+ notificationStore.actions.error("Please select at least one row")
+ }
+}
+
const handlerMap = {
["Save Row"]: saveRowHandler,
["Duplicate Row"]: duplicateRowHandler,
@@ -254,6 +276,7 @@ const handlerMap = {
["Change Form Step"]: changeFormStepHandler,
["Update State"]: updateStateHandler,
["Upload File to S3"]: s3UploadHandler,
+ ["Export Data"]: exportDataHandler,
}
const confirmTextMap = {
diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock
index 54d3ae755f..405591fd90 100644
--- a/packages/client/yarn.lock
+++ b/packages/client/yarn.lock
@@ -464,6 +464,11 @@ domutils@^2.6.0:
domelementtype "^2.2.0"
domhandler "^4.2.0"
+downloadjs@1.4.7:
+ version "1.4.7"
+ resolved "https://registry.yarnpkg.com/downloadjs/-/downloadjs-1.4.7.tgz#f69f96f940e0d0553dac291139865a3cd0101e3c"
+ integrity sha1-9p+W+UDg0FU9rCkROYZaPNAQHjw=
+
electron-to-chromium@^1.3.896:
version "1.3.900"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.900.tgz#5be2c5818a2a012c511b4b43e87b6ab7a296d4f5"
diff --git a/packages/frontend-core/src/api/rows.js b/packages/frontend-core/src/api/rows.js
index 553cf8e0de..1b6efe624f 100644
--- a/packages/frontend-core/src/api/rows.js
+++ b/packages/frontend-core/src/api/rows.js
@@ -60,4 +60,18 @@ export const buildRowEndpoints = API => ({
},
})
},
+
+ /**
+ * Exports rows.
+ * @param tableId the table ID to export the rows from
+ * @param rows the array of rows to export
+ */
+ exportRows: async ({ tableId, rows }) => {
+ return await API.post({
+ url: `/api/${tableId}/rows/exportRows`,
+ body: {
+ rows,
+ },
+ })
+ },
})
diff --git a/packages/server/src/api/controllers/row/external.js b/packages/server/src/api/controllers/row/external.js
index 66a1e30ca6..b7c7a2bc6e 100644
--- a/packages/server/src/api/controllers/row/external.js
+++ b/packages/server/src/api/controllers/row/external.js
@@ -152,6 +152,27 @@ exports.validate = async () => {
return { valid: true }
}
+exports.exportRows = async ctx => {
+ const { datasourceId, tableName } = breakExternalTableId(ctx.params.tableId)
+ const db = getAppDB()
+ const datasource = await db.get(datasourceId)
+ if (!datasource || !datasource.entities) {
+ ctx.throw(400, "Datasource has not been configured for plus API.")
+ }
+ const tables = datasource.entities
+ const table = tables[tableName]
+ ctx.request.body = {
+ query: {
+ oneOf: {
+ [table.primaryDisplay]: ctx.request.body.map(
+ id => breakRowIdField(id)[0]
+ ),
+ },
+ },
+ }
+ return exports.search(ctx)
+}
+
exports.fetchEnrichedRow = async ctx => {
const id = ctx.params.rowId
const tableId = ctx.params.tableId
diff --git a/packages/server/src/api/controllers/row/index.js b/packages/server/src/api/controllers/row/index.js
index 1d003ebd18..c4a9ac8f06 100644
--- a/packages/server/src/api/controllers/row/index.js
+++ b/packages/server/src/api/controllers/row/index.js
@@ -137,3 +137,12 @@ exports.fetchEnrichedRow = async function (ctx) {
ctx.throw(400, err)
}
}
+
+exports.export = async function (ctx) {
+ const tableId = getTableId(ctx)
+ try {
+ ctx.body = await pickApi(tableId).exportRows(ctx)
+ } catch (err) {
+ ctx.throw(400, err)
+ }
+}
diff --git a/packages/server/src/api/controllers/row/internal.js b/packages/server/src/api/controllers/row/internal.js
index 7650ac275f..068a485a8a 100644
--- a/packages/server/src/api/controllers/row/internal.js
+++ b/packages/server/src/api/controllers/row/internal.js
@@ -362,6 +362,22 @@ exports.validate = async ctx => {
})
}
+exports.exportRows = async ctx => {
+ const db = getAppDB()
+ const table = await db.get(ctx.params.tableId)
+ const rowIds = ctx.request.body.rows
+ let response = (
+ await db.allDocs({
+ include_docs: true,
+ keys: rowIds,
+ })
+ ).rows.map(row => row.doc)
+
+ let rows = await outputProcessing(table, response)
+
+ return rows
+}
+
exports.fetchEnrichedRow = async ctx => {
const db = getAppDB()
const tableId = ctx.params.tableId
diff --git a/packages/server/src/api/routes/row.js b/packages/server/src/api/routes/row.js
index 2ceddc779a..272b7d168d 100644
--- a/packages/server/src/api/routes/row.js
+++ b/packages/server/src/api/routes/row.js
@@ -252,4 +252,25 @@ router
rowController.destroy
)
+ /**
+ * @api {post} /api/:tableId/rows/exportRows Export Rows
+ * @apiName Export rows
+ * @apiGroup rows
+ * @apiPermission table write access
+ * @apiDescription This API can export a number of provided rows
+ *
+ * @apiParam {string} tableId The ID of the table the row is to be deleted from.
+ *
+ * @apiParam (Body) {object[]} [rows] The row IDs which are to be exported
+ *
+ * @apiSuccess {object[]|object}
+ */
+ .post(
+ "/api/:tableId/rows/exportRows",
+ paramResource("tableId"),
+ authorized(PermissionTypes.TABLE, PermissionLevels.WRITE),
+ usage,
+ rowController.export
+ )
+
module.exports = router