diff --git a/lerna.json b/lerna.json index cc0b07a99e..256258d6b1 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.3.8", + "version": "0.4.2", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/builder/cypress/support/commands.js b/packages/builder/cypress/support/commands.js index 94faa124a8..e92034c120 100644 --- a/packages/builder/cypress/support/commands.js +++ b/packages/builder/cypress/support/commands.js @@ -115,22 +115,22 @@ Cypress.Commands.add("createUser", (email, password, role) => { // Create User cy.contains("Users").click() - cy.contains("Create New Row").click() + cy.contains("Create New User").click() cy.get(".modal").within(() => { cy.get("input") .first() - .type(password) + .type(email) cy.get("input") .eq(1) - .type(email) + .type(password) cy.get("select") .first() .select(role) // Save cy.get(".buttons") - .contains("Create Row") + .contains("Create User") .click() }) }) diff --git a/packages/builder/package.json b/packages/builder/package.json index 542c35205e..6c023efd29 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.3.8", + "version": "0.4.2", "license": "AGPL-3.0", "private": true, "scripts": { @@ -64,7 +64,7 @@ }, "dependencies": { "@budibase/bbui": "^1.52.2", - "@budibase/client": "^0.3.8", + "@budibase/client": "^0.4.2", "@budibase/colorpicker": "^1.0.1", "@budibase/svelte-ag-grid": "^0.0.16", "@fortawesome/fontawesome-free": "^5.14.0", diff --git a/packages/builder/src/builderStore/index.js b/packages/builder/src/builderStore/index.js index 887ef733e4..cf668670bb 100644 --- a/packages/builder/src/builderStore/index.js +++ b/packages/builder/src/builderStore/index.js @@ -2,9 +2,9 @@ import { getFrontendStore } from "./store/frontend" import { getBackendUiStore } from "./store/backend" import { getAutomationStore } from "./store/automation/" import { getThemeStore } from "./store/theme" -import { derived } from "svelte/store" +import { derived, writable } from "svelte/store" import analytics from "analytics" -import { LAYOUT_NAMES } from "../constants" +import { FrontendTypes, LAYOUT_NAMES } from "../constants" import { makePropsSafe } from "components/userInterface/assetParsing/createProps" export const store = getFrontendStore() @@ -13,18 +13,12 @@ export const automationStore = getAutomationStore() export const themeStore = getThemeStore() export const currentAsset = derived(store, $store => { - const layout = $store.layouts - ? $store.layouts.find(layout => layout._id === $store.currentAssetId) - : null - - if (layout) return layout - - const screen = $store.screens - ? $store.screens.find(screen => screen._id === $store.currentAssetId) - : null - - if (screen) return screen - + const type = $store.currentFrontEndType + if (type === FrontendTypes.SCREEN) { + return $store.screens.find(screen => screen._id === $store.selectedScreenId) + } else if (type === FrontendTypes.LAYOUT) { + return $store.layouts.find(layout => layout._id === $store.selectedLayoutId) + } return null }) @@ -59,8 +53,14 @@ export const selectedComponent = derived( } ) -export const currentAssetName = derived(store, () => { - return currentAsset.name +export const currentAssetId = derived(store, $store => { + return $store.currentFrontEndType === FrontendTypes.SCREEN + ? $store.selectedScreenId + : $store.selectedLayoutId +}) + +export const currentAssetName = derived(currentAsset, $currentAsset => { + return $currentAsset?.name }) // leave this as before for consistency @@ -74,6 +74,8 @@ export const mainLayout = derived(store, $store => { ) }) +export const selectedAccessRole = writable("BASIC") + export const initialise = async () => { try { await analytics.activate() diff --git a/packages/builder/src/builderStore/store/backend.js b/packages/builder/src/builderStore/store/backend.js index 3cdabf1ed5..a1e3cfffdb 100644 --- a/packages/builder/src/builderStore/store/backend.js +++ b/packages/builder/src/builderStore/store/backend.js @@ -1,11 +1,14 @@ import { writable, get } from "svelte/store" import { cloneDeep } from "lodash/fp" import api from "../api" +import { backendUiStore } from ".." const INITIAL_BACKEND_UI_STATE = { tables: [], views: [], users: [], + roles: [], + datasources: [], selectedDatabase: {}, selectedTable: {}, draftTable: {}, @@ -20,9 +23,12 @@ export const getBackendUiStore = () => { select: async db => { const tablesResponse = await api.get(`/api/tables`) const tables = await tablesResponse.json() + const datasourcesResponse = await api.get(`/api/datasources`) + const datasources = await datasourcesResponse.json() store.update(state => { state.selectedDatabase = db state.tables = tables + state.datasources = datasources return state }) }, @@ -44,6 +50,69 @@ export const getBackendUiStore = () => { return state }), }, + datasources: { + fetch: async () => { + const response = await api.get(`/api/datasources`) + const json = await response.json() + store.update(state => { + state.datasources = json + return state + }) + return json + }, + select: async datasource => { + store.update(state => { + state.selectedDatasourceId = datasource._id + return state + }) + }, + save: async datasource => { + const response = await api.post("/api/datasources", datasource) + const json = await response.json() + store.update(state => { + const currentIdx = state.datasources.findIndex( + ds => ds._id === json._id + ) + + if (currentIdx >= 0) { + state.datasources.splice(currentIdx, 1, json) + } else { + state.datasources.push(json) + } + + state.datasources = state.datasources + return state + }) + }, + saveQuery: async (datasourceId, query) => { + const response = await api.post( + `/api/datasources/${datasourceId}/queries`, + query + ) + const json = await response.json() + store.update(state => { + const currentIdx = state.datasources.findIndex( + ds => ds._id === json._id + ) + + if (currentIdx >= 0) { + state.datasources.splice(currentIdx, 1, json) + } else { + state.datasources.push(json) + } + + state.datasources = state.datasources + return state + }) + }, + }, + queries: { + select: queryId => + store.update(state => { + state.selectedQueryId = queryId + return state + }), + }, tables: { fetch: async () => { const tablesResponse = await api.get(`/api/tables`) @@ -177,6 +246,26 @@ export const getBackendUiStore = () => { return state }), }, + roles: { + fetch: async () => { + const response = await api.get("/api/roles") + const roles = await response.json() + store.update(state => { + state.roles = roles + return state + }) + }, + delete: async role => { + const response = await api.delete(`/api/roles/${role._id}/${role._rev}`) + await store.actions.roles.fetch() + return response + }, + save: async role => { + const response = await api.post("/api/roles", role) + await store.actions.roles.fetch() + return response + }, + }, } return store diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index 5bb10a24c2..263041aadd 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -3,7 +3,6 @@ import { cloneDeep } from "lodash/fp" import { createProps, getBuiltin, - makePropsSafe, } from "components/userInterface/assetParsing/createProps" import { allScreens, @@ -11,6 +10,7 @@ import { currentAsset, mainLayout, selectedComponent, + selectedAccessRole, } from "builderStore" import { fetchComponentLibDefinitions } from "../loadComponentLibraries" import api from "../api" @@ -32,7 +32,8 @@ const INITIAL_FRONTEND_STATE = { screens: [], components: [], currentFrontEndType: "none", - currentAssetId: "", + selectedScreenId: "", + selectedLayoutId: "", selectedComponentId: "", errors: [], hasAppPackage: false, @@ -83,28 +84,31 @@ export const getFrontendStore = () => { }, }, screens: { - select: async screenId => { - let promise + select: screenId => { store.update(state => { - const screen = get(allScreens).find(screen => screen._id === screenId) + let screens = get(allScreens) + let screen = + screens.find(screen => screen._id === screenId) || screens[0] if (!screen) return state - state.currentFrontEndType = FrontendTypes.SCREEN - state.currentAssetId = screenId - state.currentView = "detail" + // Update role to the screen's role setting so that it will always + // be visible + selectedAccessRole.set(screen.routing.roleId) - promise = store.actions.screens.regenerateCss(screen) + state.currentFrontEndType = FrontendTypes.SCREEN + state.selectedScreenId = screen._id + state.currentView = "detail" state.selectedComponentId = screen.props?._id return state }) - await promise }, create: async screen => { screen = await store.actions.screens.save(screen) store.update(state => { - state.currentAssetId = screen._id + state.selectedScreenId = screen._id state.selectedComponentId = screen.props._id state.currentFrontEndType = FrontendTypes.SCREEN + selectedAccessRole.set(screen.routing.roleId) return state }) return screen @@ -113,6 +117,7 @@ export const getFrontendStore = () => { const creatingNewScreen = screen._id === undefined const response = await api.post(`/api/screens`, screen) screen = await response.json() + await store.actions.routing.fetch() store.update(state => { const foundScreen = state.screens.findIndex( @@ -122,28 +127,14 @@ export const getFrontendStore = () => { state.screens.splice(foundScreen, 1) } state.screens.push(screen) - - if (creatingNewScreen) { - const safeProps = makePropsSafe( - state.components[screen.props._component], - screen.props - ) - state.selectedComponentId = safeProps._id - screen.props = safeProps - } return state }) - return screen - }, - regenerateCss: async asset => { - const response = await api.post("/api/css/generate", asset) - asset._css = (await response.json())?.css - }, - regenerateCssForCurrentScreen: async () => { - const asset = get(currentAsset) - if (asset) { - await store.actions.screens.regenerateCss(asset) + + if (creatingNewScreen) { + store.actions.screens.select(screen._id) } + + return screen }, delete: async screens => { const screensToDelete = Array.isArray(screens) ? screens : [screens] @@ -159,8 +150,8 @@ export const getFrontendStore = () => { `/api/screens/${screenToDelete._id}/${screenToDelete._rev}` ) ) - if (screenToDelete._id === state.currentAssetId) { - state.currentAssetId = "" + if (screenToDelete._id === state.selectedScreenId) { + state.selectedScreenId = null } } return state @@ -181,50 +172,44 @@ export const getFrontendStore = () => { }, }, layouts: { - select: async layoutId => { + select: layoutId => { store.update(state => { - const layout = store.actions.layouts.find(layoutId) - + const layout = + store.actions.layouts.find(layoutId) || get(store).layouts[0] + if (!layout) return state.currentFrontEndType = FrontendTypes.LAYOUT state.currentView = "detail" - - state.currentAssetId = layout._id + state.selectedLayoutId = layout._id state.selectedComponentId = layout.props?._id - return state }) - let cssPromises = [] - cssPromises.push(store.actions.screens.regenerateCssForCurrentScreen()) - - for (let screen of get(allScreens)) { - cssPromises.push(store.actions.screens.regenerateCss(screen)) - } - await Promise.all(cssPromises) }, save: async layout => { const layoutToSave = cloneDeep(layout) - delete layoutToSave._css - + const creatingNewLayout = layoutToSave._id === undefined const response = await api.post(`/api/layouts`, layoutToSave) - - const json = await response.json() + const savedLayout = await response.json() store.update(state => { const layoutIdx = state.layouts.findIndex( - stateLayout => stateLayout._id === json._id + stateLayout => stateLayout._id === savedLayout._id ) - if (layoutIdx >= 0) { // update existing layout - state.layouts.splice(layoutIdx, 1, json) + state.layouts.splice(layoutIdx, 1, savedLayout) } else { // save new layout - state.layouts.push(json) + state.layouts.push(savedLayout) } - - state.currentAssetId = json._id return state }) + + // Select layout if creating a new one + if (creatingNewLayout) { + store.actions.layouts.select(savedLayout._id) + } + + return savedLayout }, find: layoutId => { if (!layoutId) { @@ -237,16 +222,17 @@ export const getFrontendStore = () => { const response = await api.delete( `/api/layouts/${layoutToDelete._id}/${layoutToDelete._rev}` ) - if (response.status !== 200) { const json = await response.json() throw new Error(json.message) } - store.update(state => { state.layouts = state.layouts.filter( layout => layout._id !== layoutToDelete._id ) + if (layoutToDelete._id === state.selectedLayoutId) { + state.selectedLayoutId = get(mainLayout)._id + } return state }) }, @@ -372,7 +358,6 @@ export const getFrontendStore = () => { const index = mode === "above" ? targetIndex : targetIndex + 1 parent._children.splice(index, 0, cloneDeep(componentToPaste)) - promises.push(store.actions.screens.regenerateCssForCurrentScreen()) promises.push(store.actions.preview.saveSelected()) store.actions.components.select(componentToPaste) @@ -390,8 +375,6 @@ export const getFrontendStore = () => { } selected._styles[type][name] = value - promises.push(store.actions.screens.regenerateCssForCurrentScreen()) - // save without messing with the store promises.push(store.actions.preview.saveSelected()) return state @@ -476,13 +459,8 @@ export const getFrontendStore = () => { }).props } - // Save layout and regenerate all CSS because otherwise weird things happen + // Save layout nav._children = [...nav._children, newLink] - state.currentAssetId = layout._id - promises.push(store.actions.screens.regenerateCss(layout)) - for (let screen of get(allScreens)) { - promises.push(store.actions.screens.regenerateCss(screen)) - } promises.push(store.actions.layouts.save(layout)) } return state diff --git a/packages/builder/src/components/backend/DataTable/DataTable.svelte b/packages/builder/src/components/backend/DataTable/DataTable.svelte index 063ca34633..436a3b4dee 100644 --- a/packages/builder/src/components/backend/DataTable/DataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/DataTable.svelte @@ -4,12 +4,17 @@ import CreateColumnButton from "./buttons/CreateColumnButton.svelte" import CreateViewButton from "./buttons/CreateViewButton.svelte" import ExportButton from "./buttons/ExportButton.svelte" + import EditRolesButton from "./buttons/EditRolesButton.svelte" import * as api from "./api" import Table from "./Table.svelte" + import { TableNames } from "constants" + import CreateEditUser from "./modals/CreateEditUser.svelte" + import CreateEditRow from "./modals/CreateEditRow.svelte" let data = [] let loading = false + $: isUsersTable = $backendUiStore.selectedTable?._id === TableNames.USERS $: title = $backendUiStore.selectedTable.name $: schema = $backendUiStore.selectedTable.schema $: tableView = { @@ -29,11 +34,22 @@ } -
{JSON.stringify(preview, undefined, 2)}+ {:else if previewTab === 'SCHEMA'} + {#each fields as field, idx} +
{JSON.stringify(smartSchemaRow, undefined, 2)}+ {/if} {#each fields as field, idx}