diff --git a/packages/builder/src/builderStore/websocket.js b/packages/builder/src/builderStore/websocket.js index d9b203edb0..d0baae38c6 100644 --- a/packages/builder/src/builderStore/websocket.js +++ b/packages/builder/src/builderStore/websocket.js @@ -1,21 +1,34 @@ import { createWebsocket } from "@budibase/frontend-core" import { userStore } from "builderStore" +import { datasources, tables } from "stores/backend" export const createBuilderWebsocket = () => { const socket = createWebsocket("/socket/builder") + // Connection events socket.on("connect", () => { socket.emit("get-users", null, response => { userStore.actions.init(response.users) }) }) - - socket.on("user-update", userStore.actions.updateUser) - socket.on("user-disconnect", userStore.actions.removeUser) socket.on("connect_error", err => { console.log("Failed to connect to builder websocket:", err.message) }) + // User events + socket.on("user-update", userStore.actions.updateUser) + socket.on("user-disconnect", userStore.actions.removeUser) + + // Table events + socket.on("table-change", ({ id, table }) => { + tables.replaceTable(id, table) + }) + + // Table events + socket.on("datasource-change", ({ id, datasource }) => { + datasources.replaceDatasource(id, datasource) + }) + return { ...socket, disconnect: () => { diff --git a/packages/builder/src/components/backend/DataTable/DataTable.svelte b/packages/builder/src/components/backend/DataTable/DataTable.svelte index b5211b7d9b..79b49d2541 100644 --- a/packages/builder/src/components/backend/DataTable/DataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/DataTable.svelte @@ -35,7 +35,6 @@ allowAddRows={!isUsersTable} allowDeleteRows={!isUsersTable} schemaOverrides={isUsersTable ? userSchemaOverrides : null} - on:updatetable={e => tables.updateTable(e.detail)} showAvatars={false} > diff --git a/packages/builder/src/stores/backend/datasources.js b/packages/builder/src/stores/backend/datasources.js index ed84bb8ee9..e414e080a2 100644 --- a/packages/builder/src/stores/backend/datasources.js +++ b/packages/builder/src/stores/backend/datasources.js @@ -1,4 +1,4 @@ -import { writable, derived } from "svelte/store" +import { writable, derived, get } from "svelte/store" import { queries, tables } from "./" import { API } from "api" @@ -89,6 +89,39 @@ export function createDatasourcesStore() { }) } + // Handles external updates of tables + const replaceDatasource = (datasourceId, datasource) => { + if (!datasourceId) { + return + } + + // Handle deletion + if (!datasource) { + store.update(state => ({ + ...state, + list: state.list.filter(x => x._id !== datasourceId), + })) + return + } + + // Add new datasource + const index = get(store).list.findIndex(x => x._id === datasource._id) + if (index === -1) { + store.update(state => ({ + ...state, + list: [...state.list, datasource], + })) + } + + // Update existing datasource + else if (datasource) { + store.update(state => { + state.list[index] = datasource + return state + }) + } + } + return { subscribe: derivedStore.subscribe, fetch, @@ -98,6 +131,7 @@ export function createDatasourcesStore() { save, delete: deleteDatasource, removeSchemaError, + replaceDatasource, } } diff --git a/packages/builder/src/stores/backend/tables.js b/packages/builder/src/stores/backend/tables.js index 75679532f3..ba900b7df9 100644 --- a/packages/builder/src/stores/backend/tables.js +++ b/packages/builder/src/stores/backend/tables.js @@ -22,18 +22,6 @@ export function createTablesStore() { })) } - const fetchTable = async tableId => { - const table = await API.fetchTableDefinition(tableId) - - store.update(state => { - const indexToUpdate = state.list.findIndex(t => t._id === table._id) - state.list[indexToUpdate] = table - return { - ...state, - } - }) - } - const select = tableId => { store.update(state => ({ ...state, @@ -74,20 +62,23 @@ export function createTablesStore() { } const savedTable = await API.saveTable(updatedTable) - await fetch() + replaceTable(table._id, savedTable) if (table.type === "external") { await datasources.fetch() } - await select(savedTable._id) + select(savedTable._id) return savedTable } const deleteTable = async table => { + if (!table?._id || !table?._rev) { + return + } await API.deleteTable({ - tableId: table?._id, - tableRev: table?._rev, + tableId: table._id, + tableRev: table._rev, }) - await fetch() + replaceTable(table._id, null) } const saveField = async ({ @@ -135,35 +126,56 @@ export function createTablesStore() { await save(draft) } - const updateTable = table => { - const index = get(store).list.findIndex(x => x._id === table._id) - if (index === -1) { + // Handles external updates of tables + const replaceTable = (tableId, table) => { + if (!tableId) { return } - // This function has to merge state as there discrepancies with the table - // API endpoints. The table list endpoint and get table endpoint use the - // "type" property to mean different things. - store.update(state => { - state.list[index] = { - ...table, - type: state.list[index].type, - } - return state - }) + // Handle deletion + if (!table) { + store.update(state => ({ + ...state, + list: state.list.filter(x => x._id !== tableId), + })) + return + } + + // Add new table + const index = get(store).list.findIndex(x => x._id === table._id) + if (index === -1) { + store.update(state => ({ + ...state, + list: [...state.list, table], + })) + } + + // Update existing table + else if (table) { + // This function has to merge state as there discrepancies with the table + // API endpoints. The table list endpoint and get table endpoint use the + // "type" property to mean different things. + store.update(state => { + state.list[index] = { + ...table, + type: state.list[index].type, + } + return state + }) + } } return { + ...store, subscribe: derivedStore.subscribe, fetch, - fetchTable, init: fetch, select, save, delete: deleteTable, saveField, deleteField, - updateTable, + replaceTable, } } diff --git a/packages/builder/src/stores/backend/views.js b/packages/builder/src/stores/backend/views.js index 71e9a44c5c..2f396096c8 100644 --- a/packages/builder/src/stores/backend/views.js +++ b/packages/builder/src/stores/backend/views.js @@ -1,4 +1,4 @@ -import { writable, get, derived } from "svelte/store" +import { writable, derived } from "svelte/store" import { tables } from "./" import { API } from "api" @@ -27,21 +27,31 @@ export function createViewsStore() { const deleteView = async view => { await API.deleteView(view) - await tables.fetch() + + // Update tables + tables.update(state => { + const table = state.list.find(table => table._id === view.tableId) + if (table) { + delete table.views[view.name] + } + return { ...state } + }) } const save = async view => { const savedView = await API.saveView(view) - const viewMeta = { - name: view.name, - ...savedView, - } - const viewTable = get(tables).list.find(table => table._id === view.tableId) - - if (view.originalName) delete viewTable.views[view.originalName] - viewTable.views[view.name] = viewMeta - await tables.save(viewTable) + // Update tables + tables.update(state => { + const table = state.list.find(table => table._id === view.tableId) + if (table) { + if (view.originalName) { + delete table.views[view.originalName] + } + table.views[view.name] = savedView + } + return { ...state } + }) } return { diff --git a/packages/frontend-core/src/components/grid/lib/websocket.js b/packages/frontend-core/src/components/grid/lib/websocket.js index e8cead849e..a62910c2fa 100644 --- a/packages/frontend-core/src/components/grid/lib/websocket.js +++ b/packages/frontend-core/src/components/grid/lib/websocket.js @@ -2,7 +2,7 @@ import { get } from "svelte/store" import { createWebsocket } from "../../../utils" export const createGridWebsocket = context => { - const { rows, tableId, users, focusedCellId } = context + const { rows, tableId, users, focusedCellId, table } = context const socket = createWebsocket("/socket/grid") const connectToTable = tableId => { @@ -16,23 +16,36 @@ export const createGridWebsocket = context => { }) } - // Event handlers + // Connection events socket.on("connect", () => { connectToTable(get(tableId)) }) - socket.on("row-update", data => { - if (data.id) { - rows.actions.replaceRow(data.id, data.row) - } + socket.on("connect_error", err => { + console.log("Failed to connect to grid websocket:", err.message) }) + + // User events socket.on("user-update", user => { users.actions.updateUser(user) }) socket.on("user-disconnect", user => { users.actions.removeUser(user) }) - socket.on("connect_error", err => { - console.log("Failed to connect to grid websocket:", err.message) + + // Row events + socket.on("row-change", data => { + if (data.id) { + rows.actions.replaceRow(data.id, data.row) + } + }) + + // Table events + socket.on("table-change", data => { + // Only update table if one exists. If the table was deleted then we don't + // want to know - let the builder navigate away + if (data.table) { + table.set(data.table) + } }) // Change websocket connection when table changes diff --git a/packages/frontend-core/src/components/grid/stores/columns.js b/packages/frontend-core/src/components/grid/stores/columns.js index e953977487..3b286ac0f0 100644 --- a/packages/frontend-core/src/components/grid/stores/columns.js +++ b/packages/frontend-core/src/components/grid/stores/columns.js @@ -90,10 +90,6 @@ export const deriveStores = context => { // Update local state table.set(newTable) - // Broadcast event so that we can keep sync with external state - // (e.g. data section which maintains a list of table definitions) - dispatch("updatetable", newTable) - // Update server await API.saveTable(newTable) } diff --git a/packages/server/src/api/controllers/datasource.ts b/packages/server/src/api/controllers/datasource.ts index 8f13e0e618..ecaf1ef58b 100644 --- a/packages/server/src/api/controllers/datasource.ts +++ b/packages/server/src/api/controllers/datasource.ts @@ -25,6 +25,7 @@ import { DatasourcePlus, } from "@budibase/types" import sdk from "../../sdk" +import { builderSocket } from "../../websockets" function getErrorTables(errors: any, errorType: string) { return Object.entries(errors) @@ -280,6 +281,7 @@ export async function update(ctx: UserCtx) { ctx.body = { datasource: await sdk.datasources.removeSecretSingle(datasource), } + builderSocket.emitDatasourceUpdate(ctx, datasource) } export async function save( @@ -322,6 +324,7 @@ export async function save( response.error = schemaError } ctx.body = response + builderSocket.emitDatasourceUpdate(ctx, datasource) } async function destroyInternalTablesBySourceId(datasourceId: string) { @@ -381,6 +384,7 @@ export async function destroy(ctx: UserCtx) { ctx.message = `Datasource deleted.` ctx.status = 200 + builderSocket.emitDatasourceDeletion(ctx, datasourceId) } export async function find(ctx: UserCtx) { diff --git a/packages/server/src/api/controllers/table/index.ts b/packages/server/src/api/controllers/table/index.ts index cbbda7b930..271f3e82fa 100644 --- a/packages/server/src/api/controllers/table/index.ts +++ b/packages/server/src/api/controllers/table/index.ts @@ -11,6 +11,7 @@ import { context, events } from "@budibase/backend-core" import { Table, UserCtx } from "@budibase/types" import sdk from "../../../sdk" import { jsonFromCsvString } from "../../../utilities/csv" +import { builderSocket } from "../../../websockets" function pickApi({ tableId, table }: { tableId?: string; table?: Table }) { if (table && !tableId) { @@ -77,6 +78,7 @@ export async function save(ctx: UserCtx) { ctx.eventEmitter && ctx.eventEmitter.emitTable(`table:save`, appId, savedTable) ctx.body = savedTable + builderSocket.emitTableUpdate(ctx, savedTable) } export async function destroy(ctx: UserCtx) { @@ -89,6 +91,7 @@ export async function destroy(ctx: UserCtx) { ctx.status = 200 ctx.table = deletedTable ctx.body = { message: `Table ${tableId} deleted.` } + builderSocket.emitTableDeletion(ctx, tableId) } export async function bulkImport(ctx: UserCtx) { diff --git a/packages/server/src/api/controllers/view/index.ts b/packages/server/src/api/controllers/view/index.ts index ed2302f723..28b0d0a81f 100644 --- a/packages/server/src/api/controllers/view/index.ts +++ b/packages/server/src/api/controllers/view/index.ts @@ -16,6 +16,7 @@ import { View, } from "@budibase/types" import { cleanExportRows } from "../row/utils" +import { builderSocket } from "../../../websockets" const { cloneDeep, isEqual } = require("lodash") @@ -48,7 +49,7 @@ export async function save(ctx: Ctx) { if (!view.meta.schema) { view.meta.schema = table.schema } - table.views[viewName] = view.meta + table.views[viewName] = { ...view.meta, name: viewName } if (originalName) { delete table.views[originalName] existingTable.views[viewName] = existingTable.views[originalName] @@ -56,10 +57,8 @@ export async function save(ctx: Ctx) { await db.put(table) await handleViewEvents(existingTable.views[viewName], table.views[viewName]) - ctx.body = { - ...table.views[viewToSave.name], - name: viewToSave.name, - } + ctx.body = table.views[viewName] + builderSocket.emitTableUpdate(ctx, table) } export async function calculationEvents(existingView: View, newView: View) { @@ -128,6 +127,7 @@ export async function destroy(ctx: Ctx) { await events.view.deleted(view) ctx.body = view + builderSocket.emitTableUpdate(ctx, table) } export async function exportView(ctx: Ctx) { diff --git a/packages/server/src/websockets/builder.ts b/packages/server/src/websockets/builder.ts index 5c23682a7b..ae32a7de18 100644 --- a/packages/server/src/websockets/builder.ts +++ b/packages/server/src/websockets/builder.ts @@ -3,7 +3,8 @@ import Socket from "./websocket" import { permissions } from "@budibase/backend-core" import http from "http" import Koa from "koa" -import { Table } from "@budibase/types" +import { Datasource, Table } from "@budibase/types" +import { gridSocket } from "./index" export default class BuilderSocket extends Socket { constructor(app: Koa, server: http.Server) { @@ -22,7 +23,6 @@ export default class BuilderSocket extends Socket { const sockets = await this.io.in(appId).fetchSockets() callback({ users: sockets.map(socket => socket.data.user), - id: user.id, }) }) @@ -34,10 +34,22 @@ export default class BuilderSocket extends Socket { } emitTableUpdate(ctx: any, table: Table) { - this.io.in(ctx.appId).emit("table-update", { id: table._id, table }) + this.io.in(ctx.appId).emit("table-change", { id: table._id, table }) + gridSocket.emitTableUpdate(table) } emitTableDeletion(ctx: any, id: string) { - this.io.in(ctx.appId).emit("table-update", { id, table: null }) + this.io.in(ctx.appId).emit("table-change", { id, table: null }) + gridSocket.emitTableDeletion(id) + } + + emitDatasourceUpdate(ctx: any, datasource: Datasource) { + this.io + .in(ctx.appId) + .emit("datasource-change", { id: datasource._id, datasource }) + } + + emitDatasourceDeletion(ctx: any, id: string) { + this.io.in(ctx.appId).emit("datasource-change", { id, datasource: null }) } } diff --git a/packages/server/src/websockets/grid.ts b/packages/server/src/websockets/grid.ts index 731c920e07..09b9ee0e3d 100644 --- a/packages/server/src/websockets/grid.ts +++ b/packages/server/src/websockets/grid.ts @@ -4,7 +4,7 @@ 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" +import { Row, Table } from "@budibase/types" export default class GridSocket extends Socket { constructor(app: Koa, server: http.Server) { @@ -56,11 +56,19 @@ 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 }) + this.io.in(tableId).emit("row-change", { id: row._id, row }) } emitRowDeletion(ctx: any, id: string) { const tableId = getTableId(ctx) - this.io.in(tableId).emit("row-update", { id, row: null }) + this.io.in(tableId).emit("row-change", { id, row: null }) + } + + emitTableUpdate(table: Table) { + this.io.in(table._id!).emit("table-change", { id: table._id, table }) + } + + emitTableDeletion(id: string) { + this.io.in(id).emit("table-change", { id, table: null }) } } diff --git a/packages/server/src/websockets/index.ts b/packages/server/src/websockets/index.ts index 2761c4d9da..b74a8adfca 100644 --- a/packages/server/src/websockets/index.ts +++ b/packages/server/src/websockets/index.ts @@ -14,4 +14,4 @@ export const initialise = (app: Koa, server: http.Server) => { builderSocket = new BuilderSocket(app, server) } -export { clientAppSocket, gridSocket } +export { clientAppSocket, gridSocket, builderSocket }