diff --git a/packages/frontend-core/src/components/grid/layout/Grid.svelte b/packages/frontend-core/src/components/grid/layout/Grid.svelte index 2035ec4d39..3b0e74d882 100644 --- a/packages/frontend-core/src/components/grid/layout/Grid.svelte +++ b/packages/frontend-core/src/components/grid/layout/Grid.svelte @@ -1,5 +1,5 @@
{ }) socket.on("row-update", data => { if (data.id) { - rows.actions.refreshRow(data.id) + rows.actions.replaceRow(data.id, data.row) } }) socket.on("user-update", user => { diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index b6dc8c05d0..ee5ac6c1d2 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -268,27 +268,25 @@ export const deriveStores = context => { return res?.rows?.[0] } - // Refreshes a specific row, handling updates, addition or deletion - const refreshRow = async id => { - // Fetch row from the server again - const newRow = await fetchRow(id) - + // Replaces a row in state with the newly defined row, handling updates, + // addition and deletion + const replaceRow = (id, row) => { // Get index of row to check if it exists const $rows = get(rows) const $rowLookupMap = get(rowLookupMap) const index = $rowLookupMap[id] // Process as either an update, addition or deletion - if (newRow) { + if (row) { if (index != null) { // An existing row was updated rows.update(state => { - state[index] = { ...newRow } + state[index] = { ...row } return state }) } else { // A new row was created - handleNewRows([newRow]) + handleNewRows([row]) } } else if (index != null) { // A row was removed @@ -296,6 +294,15 @@ export const deriveStores = context => { } } + // Refreshes a specific row + const refreshRow = async id => { + // Fetch row from the server again + const row = await fetchRow(id) + + // Update local state + replaceRow(id, row) + } + // Refreshes all data const refreshData = () => { get(fetch)?.getInitialData() @@ -455,6 +462,7 @@ export const deriveStores = context => { hasRow, loadNextPage, refreshRow, + replaceRow, refreshData, refreshTableDefinition, }, diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index 349c4e72e7..55d2d27cce 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -4,6 +4,7 @@ import * as external from "./external" import { isExternalTable } from "../../../integrations/utils" import { Ctx } from "@budibase/types" import * as utils from "./utils" +import { gridSocket } from "../../../websockets" function pickApi(tableId: any) { if (isExternalTable(tableId)) { @@ -12,21 +13,9 @@ function pickApi(tableId: any) { return internal } -function getTableId(ctx: any) { - if (ctx.request.body && ctx.request.body.tableId) { - return ctx.request.body.tableId - } - if (ctx.params && ctx.params.tableId) { - return ctx.params.tableId - } - if (ctx.params && ctx.params.viewName) { - return ctx.params.viewName - } -} - export async function patch(ctx: any): Promise { const appId = ctx.appId - const tableId = getTableId(ctx) + const tableId = utils.getTableId(ctx) const body = ctx.request.body // if it doesn't have an _id then its save if (body && !body._id) { @@ -47,6 +36,7 @@ export async function patch(ctx: any): Promise { ctx.eventEmitter.emitRow(`row:update`, appId, row, table) ctx.message = `${table.name} updated successfully.` ctx.body = row + gridSocket?.emitRowUpdate(ctx, row) } catch (err) { ctx.throw(400, err) } @@ -54,7 +44,7 @@ export async function patch(ctx: any): Promise { export const save = async (ctx: any) => { const appId = ctx.appId - const tableId = getTableId(ctx) + const tableId = utils.getTableId(ctx) const body = ctx.request.body // if it has an ID already then its a patch if (body && body._id) { @@ -69,23 +59,24 @@ export const save = async (ctx: any) => { ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:save`, appId, row, table) ctx.message = `${table.name} saved successfully` ctx.body = row + gridSocket?.emitRowUpdate(ctx, row) } export async function fetchView(ctx: any) { - const tableId = getTableId(ctx) + const tableId = utils.getTableId(ctx) ctx.body = await quotas.addQuery(() => pickApi(tableId).fetchView(ctx), { datasourceId: tableId, }) } export async function fetch(ctx: any) { - const tableId = getTableId(ctx) + const tableId = utils.getTableId(ctx) ctx.body = await quotas.addQuery(() => pickApi(tableId).fetch(ctx), { datasourceId: tableId, }) } export async function find(ctx: any) { - const tableId = getTableId(ctx) + const tableId = utils.getTableId(ctx) ctx.body = await quotas.addQuery(() => pickApi(tableId).find(ctx), { datasourceId: tableId, }) @@ -94,7 +85,7 @@ export async function find(ctx: any) { export async function destroy(ctx: any) { const appId = ctx.appId const inputs = ctx.request.body - const tableId = getTableId(ctx) + const tableId = utils.getTableId(ctx) let response, row if (inputs.rows) { let { rows } = await quotas.addQuery( @@ -107,6 +98,7 @@ export async function destroy(ctx: any) { response = rows for (let row of rows) { ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, appId, row) + gridSocket?.emitRowDeletion(ctx, row._id) } } else { let resp = await quotas.addQuery(() => pickApi(tableId).destroy(ctx), { @@ -116,6 +108,7 @@ export async function destroy(ctx: any) { response = resp.response row = resp.row ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, appId, row) + gridSocket?.emitRowDeletion(ctx, row._id) } ctx.status = 200 // for automations include the row that was deleted @@ -124,7 +117,7 @@ export async function destroy(ctx: any) { } export async function search(ctx: any) { - const tableId = getTableId(ctx) + const tableId = utils.getTableId(ctx) ctx.status = 200 ctx.body = await quotas.addQuery(() => pickApi(tableId).search(ctx), { datasourceId: tableId, @@ -132,7 +125,7 @@ export async function search(ctx: any) { } export async function validate(ctx: Ctx) { - const tableId = getTableId(ctx) + const tableId = utils.getTableId(ctx) // external tables are hard to validate currently if (isExternalTable(tableId)) { ctx.body = { valid: true } @@ -145,7 +138,7 @@ export async function validate(ctx: Ctx) { } export async function fetchEnrichedRow(ctx: any) { - const tableId = getTableId(ctx) + const tableId = utils.getTableId(ctx) ctx.body = await quotas.addQuery( () => pickApi(tableId).fetchEnrichedRow(ctx), { @@ -155,7 +148,7 @@ export async function fetchEnrichedRow(ctx: any) { } export const exportRows = async (ctx: any) => { - const tableId = getTableId(ctx) + const tableId = utils.getTableId(ctx) ctx.body = await quotas.addQuery(() => pickApi(tableId).exportRows(ctx), { datasourceId: tableId, }) diff --git a/packages/server/src/api/controllers/row/utils.ts b/packages/server/src/api/controllers/row/utils.ts index f6a87dd24c..f1edbf538b 100644 --- a/packages/server/src/api/controllers/row/utils.ts +++ b/packages/server/src/api/controllers/row/utils.ts @@ -154,3 +154,15 @@ export function cleanExportRows( return cleanRows } + +export function getTableId(ctx: any) { + if (ctx.request.body && ctx.request.body.tableId) { + return ctx.request.body.tableId + } + if (ctx.params && ctx.params.tableId) { + return ctx.params.tableId + } + if (ctx.params && ctx.params.viewName) { + return ctx.params.viewName + } +} diff --git a/packages/server/src/websockets/grid.ts b/packages/server/src/websockets/grid.ts index 886784cd2c..bb23ec8e18 100644 --- a/packages/server/src/websockets/grid.ts +++ b/packages/server/src/websockets/grid.ts @@ -3,6 +3,8 @@ import Socket from "./websocket" import { permissions } from "@budibase/backend-core" import http from "http" import Koa from "koa" +import { getTableId } from "../api/controllers/row/utils" +import { Row } from "@budibase/types" export default class GridSocket extends Socket { constructor(app: Koa, server: http.Server) { @@ -52,4 +54,14 @@ export default class GridSocket extends Socket { }) }) } + + emitRowUpdate(ctx: any, row: Row) { + const tableId = getTableId(ctx) + this.io.in(tableId).emit("row-update", { id: row._id, row }) + } + + emitRowDeletion(ctx: any, id: string) { + const tableId = getTableId(ctx) + this.io.in(tableId).emit("row-update", { id, row: null }) + } }