Handle table, query and datasource events using builder socket
This commit is contained in:
parent
3794d8e204
commit
cfa07a68ae
|
@ -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: () => {
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
allowAddRows={!isUsersTable}
|
||||
allowDeleteRows={!isUsersTable}
|
||||
schemaOverrides={isUsersTable ? userSchemaOverrides : null}
|
||||
on:updatetable={e => tables.updateTable(e.detail)}
|
||||
showAvatars={false}
|
||||
>
|
||||
<svelte:fragment slot="controls">
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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<any, UpdateDatasourceResponse>) {
|
|||
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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,4 +14,4 @@ export const initialise = (app: Koa, server: http.Server) => {
|
|||
builderSocket = new BuilderSocket(app, server)
|
||||
}
|
||||
|
||||
export { clientAppSocket, gridSocket }
|
||||
export { clientAppSocket, gridSocket, builderSocket }
|
||||
|
|
Loading…
Reference in New Issue