Merge branch 'next' of github.com:Budibase/budibase into datasource-refactor

This commit is contained in:
Andrew Kingston 2021-04-01 17:58:10 +01:00
commit 66ef52bf73
93 changed files with 3652 additions and 1811 deletions

View File

@ -70,7 +70,7 @@ context("Create a View", () => {
.eq(1)
.select("age")
cy.contains("Save").click()
cy.wait(100)
cy.wait(1000)
cy.get(".ag-center-cols-viewport").scrollTo("100%")
cy.get("[data-cy=table-header]").then($headers => {
expect($headers).to.have.length(7)
@ -100,6 +100,7 @@ context("Create a View", () => {
cy.contains("Group By").click()
cy.get("select").select("group")
cy.contains("Save").click()
cy.wait(1000)
cy.get(".ag-center-cols-viewport").scrollTo("100%")
cy.contains("Students").should("be.visible")
cy.contains("Teachers").should("be.visible")
@ -108,7 +109,7 @@ context("Create a View", () => {
.find(".ag-cell")
.then($values => {
const values = Array.from($values).map(value => value.textContent)
expect(values).to.deep.eq([
expect(values.sort()).to.deep.eq([
"Students",
"23.333333333333332",
"1650",
@ -116,7 +117,7 @@ context("Create a View", () => {
"25",
"20",
"70",
])
].sort())
})
})
@ -129,6 +130,7 @@ context("Create a View", () => {
cy.get("input").type(" Updated")
cy.contains("Save").click()
})
cy.wait(1000)
cy.contains("Test View Updated").should("be.visible")
})
@ -138,6 +140,7 @@ context("Create a View", () => {
.click({ force: true })
cy.get("[data-cy=delete-view]").click()
cy.contains("Delete View").click()
cy.wait(1000)
cy.contains("TestView Updated").should("not.be.visible")
})
})

View File

@ -156,7 +156,7 @@ Cypress.Commands.add("addComponent", (category, component) => {
cy.get(`[data-cy="category-${category}"]`).click()
}
cy.get(`[data-cy="component-${component}"]`).click()
cy.wait(500)
cy.wait(1000)
cy.location().then(loc => {
const params = loc.pathname.split("/")
const componentId = params[params.length - 1]

View File

@ -28,7 +28,9 @@
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/internals/mocks/fileMock.js",
"\\.(css|less|sass|scss)$": "identity-obj-proxy",
"components(.*)$": "<rootDir>/src/components$1",
"builderStore(.*)$": "<rootDir>/src/builderStore$1"
"builderStore(.*)$": "<rootDir>/src/builderStore$1",
"stores(.*)$": "<rootDir>/src/stores$1",
"analytics(.*)$": "<rootDir>/src/analytics$1"
},
"moduleFileExtensions": [
"js",
@ -42,16 +44,14 @@
"^.+.svelte$": "svelte-jester"
},
"transformIgnorePatterns": [
"/node_modules/(?!svelte).+\\.js$"
"/node_modules/(?!svelte).+\\.js$",
".*string-templates.*"
],
"modulePathIgnorePatterns": [
"<rootDir>/cypress/"
],
"setupFilesAfterEnv": [
"@testing-library/jest-dom/extend-expect"
],
"setupFiles": [
"./scripts/jestSetup.js"
]
},
"eslintConfig": {
@ -86,22 +86,22 @@
"yup": "0.29.2"
},
"devDependencies": {
"@babel/core": "^7.5.5",
"@babel/plugin-transform-runtime": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@babel/runtime": "^7.5.5",
"@babel/core": "^7.12.14",
"@babel/plugin-transform-runtime": "^7.13.10",
"@babel/preset-env": "^7.13.12",
"@babel/runtime": "^7.13.10",
"@rollup/plugin-alias": "^3.0.1",
"@rollup/plugin-commonjs": "^16.0.0",
"@rollup/plugin-json": "^4.0.3",
"@sveltech/routify": "1.7.11",
"@testing-library/jest-dom": "^5.11.0",
"@testing-library/jest-dom": "^5.11.10",
"@testing-library/svelte": "^3.0.0",
"babel-jest": "^24.8.0",
"babel-jest": "^26.6.3",
"cypress": "^5.1.0",
"cypress-terminal-report": "^1.4.1",
"eslint-plugin-cypress": "^2.11.1",
"identity-obj-proxy": "^3.0.0",
"jest": "^24.8.0",
"jest": "^26.6.3",
"ncp": "^2.0.0",
"rimraf": "^3.0.2",
"rollup": "^2.11.2",

View File

@ -60,6 +60,10 @@ export default {
find: "builderStore",
replacement: path.resolve(projectRootDir, "src/builderStore"),
},
{
find: "stores",
replacement: path.resolve(projectRootDir, "src/stores"),
},
{
find: "constants",
replacement: path.resolve(projectRootDir, "src/constants"),

View File

@ -1,26 +0,0 @@
// Array.flat needs polyfilled in < Node 11
if (!Array.prototype.flat) {
Object.defineProperty(Array.prototype, "flat", {
configurable: true,
value: function flat() {
var depth = isNaN(arguments[0]) ? 1 : Number(arguments[0])
return depth
? Array.prototype.reduce.call(
this,
function(acc, cur) {
if (Array.isArray(cur)) {
acc.push.apply(acc, flat.call(cur, depth - 1))
} else {
acc.push(cur)
}
return acc
},
[]
)
: Array.prototype.slice.call(this)
},
writable: true,
})
}

View File

@ -1,7 +1,11 @@
import { cloneDeep } from "lodash/fp"
import { get } from "svelte/store"
import { backendUiStore, store } from "builderStore"
import { findComponent, findComponentPath } from "./storeUtils"
import { store } from "builderStore"
import {
tables as tablesStore,
queries as queriesStores,
} from "stores/backend/"
import { makePropSafe } from "@budibase/string-templates"
import { TableNames } from "../constants"
@ -248,10 +252,10 @@ export const getSchemaForDatasource = (datasource, isForm = false) => {
if (datasource) {
const { type } = datasource
if (type === "query") {
const queries = get(backendUiStore).queries
const queries = get(queriesStores).queries
table = queries.find(query => query._id === datasource._id)
} else {
const tables = get(backendUiStore).tables
const tables = get(tablesStore).list
table = tables.find(table => table._id === datasource.tableId)
}
if (table) {

View File

@ -1,5 +1,4 @@
import { getFrontendStore } from "./store/frontend"
import { getBackendUiStore } from "./store/backend"
import { getAutomationStore } from "./store/automation"
import { getHostingStore } from "./store/hosting"
import { getThemeStore } from "./store/theme"
@ -9,7 +8,6 @@ import { FrontendTypes, LAYOUT_NAMES } from "../constants"
import { findComponent } from "./storeUtils"
export const store = getFrontendStore()
export const backendUiStore = getBackendUiStore()
export const automationStore = getAutomationStore()
export const themeStore = getThemeStore()
export const hostingStore = getHostingStore()

View File

@ -2,13 +2,21 @@ import { get, writable } from "svelte/store"
import { cloneDeep } from "lodash/fp"
import {
allScreens,
backendUiStore,
hostingStore,
currentAsset,
mainLayout,
selectedComponent,
selectedAccessRole,
} from "builderStore"
// Backendstores
import {
datasources,
integrations,
queries,
database,
tables,
} from "stores/backend/"
import { fetchComponentLibDefinitions } from "../loadComponentLibraries"
import api from "../api"
import { FrontendTypes } from "constants"
@ -57,7 +65,16 @@ export const getFrontendStore = () => {
appInstance: application.instance,
}))
await hostingStore.actions.fetch()
await backendUiStore.actions.database.select(application.instance)
// Initialise backend stores
const [_integrations] = await Promise.all([
api.get("/api/integrations").then(r => r.json()),
])
datasources.init()
integrations.set(_integrations)
queries.init()
database.set(application.instance)
tables.init()
},
routing: {
fetch: async () => {

View File

@ -1,7 +1,7 @@
<script>
import { processStringSync } from "@budibase/string-templates"
import { get } from "lodash/fp"
import { backendUiStore } from "builderStore"
import { tables } from 'stores/backend/'
export let block
@ -15,7 +15,7 @@
let enrichedInputs = { ...inputs, enriched: {} }
const tableId = inputs.tableId || inputs.row?.tableId
if (tableId) {
enrichedInputs.enriched.table = $backendUiStore.tables.find(
enrichedInputs.enriched.table = $tables.list.find(
table => table._id === tableId
)
}

View File

@ -1,5 +1,6 @@
<script>
import { backendUiStore, automationStore } from "builderStore"
import { automationStore } from "builderStore"
import { database } from 'stores/backend/'
import { goto } from "@sveltech/routify"
import { notifier } from "builderStore/store/notifications"
import { Input, ModalContent } from "@budibase/bbui"
@ -8,7 +9,7 @@
let name
$: valid = !!name
$: instanceId = $backendUiStore.selectedDatabase._id
$: instanceId = $database._id
async function createAutomation() {
await automationStore.actions.create({

View File

@ -1,6 +1,7 @@
<script>
import { goto } from "@sveltech/routify"
import { automationStore, backendUiStore } from "builderStore"
import { automationStore } from "builderStore"
import { database } from 'stores/backend/'
import { notifier } from "builderStore/store/notifications"
import { DropdownMenu } from "@budibase/bbui"
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
@ -11,7 +12,7 @@
let anchor
let dropdown
let confirmDeleteDialog
$: instanceId = $backendUiStore.selectedDatabase._id
$: instanceId = $database._id
function showModal() {
dropdown.hide()

View File

@ -1,5 +1,5 @@
<script>
import { backendUiStore } from "builderStore"
import { tables } from 'stores/backend/'
import { Select } from "@budibase/bbui"
import DrawerBindableInput from "../../common/DrawerBindableInput.svelte"
import AutomationBindingPanel from "./AutomationBindingPanel.svelte"
@ -7,7 +7,7 @@
export let value
export let bindings
$: table = $backendUiStore.tables.find(table => table._id === value?.tableId)
$: table = $tables.list.find(table => table._id === value?.tableId)
$: schemaFields = Object.entries(table?.schema ?? {})
// Ensure any nullish tableId values get set to empty string so
@ -22,7 +22,7 @@
<div class="block-field">
<Select bind:value={value.tableId} extraThin secondary>
<option value="">Choose an option</option>
{#each $backendUiStore.tables as table}
{#each $tables.list as table}
<option value={table._id}>{table.name}</option>
{/each}
</Select>

View File

@ -1,5 +1,6 @@
<script>
import { backendUiStore, automationStore } from "builderStore"
import { automationStore } from "builderStore"
import { database } from 'stores/backend/'
import { notifier } from "builderStore/store/notifications"
import AutomationBlockSetup from "./AutomationBlockSetup.svelte"
import { Button, Modal } from "@budibase/bbui"
@ -7,7 +8,7 @@
let webhookModal
$: instanceId = $backendUiStore.selectedDatabase._id
$: instanceId = $database._id
$: automation = $automationStore.selectedAutomation?.automation
$: automationLive = automation?.live

View File

@ -1,5 +1,5 @@
<script>
import { backendUiStore } from "builderStore"
import { tables } from 'stores/backend/'
import { Select } from "@budibase/bbui"
export let value
@ -8,7 +8,7 @@
<div class="block-field">
<Select bind:value secondary extraThin>
<option value="">Choose an option</option>
{#each $backendUiStore.tables as table}
{#each $tables.list as table}
<option value={table._id}>{table.name}</option>
{/each}
</Select>

View File

@ -1,5 +1,6 @@
<script>
import { store, backendUiStore, automationStore } from "builderStore"
import { automationStore } from "builderStore"
import { database } from 'stores/backend/'
import WebhookDisplay from "./WebhookDisplay.svelte"
import { ModalContent } from "@budibase/bbui"
import { onMount, onDestroy } from "svelte"
@ -10,8 +11,7 @@
let schemaURL
let propCount = 0
$: instanceId = $backendUiStore.selectedDatabase._id
$: appId = $store.appId
$: instanceId = $database._id
$: automation = $automationStore.selectedAutomation?.automation
onMount(async () => {

View File

@ -1,5 +1,6 @@
<script>
import { backendUiStore } from "builderStore"
import { tables, views } from 'stores/backend/'
import CreateRowButton from "./buttons/CreateRowButton.svelte"
import CreateColumnButton from "./buttons/CreateColumnButton.svelte"
import CreateViewButton from "./buttons/CreateViewButton.svelte"
@ -16,19 +17,19 @@
let hideAutocolumns = true
let data = []
let loading = false
$: isUsersTable = $backendUiStore.selectedTable?._id === TableNames.USERS
$: title = $backendUiStore.selectedTable.name
$: schema = $backendUiStore.selectedTable.schema
$: isUsersTable = $tables.selected?._id === TableNames.USERS
$: title = $tables.selected.name
$: schema = $tables.selected.schema
$: tableView = {
schema,
name: $backendUiStore.selectedView.name,
name: $views.selected?.name,
}
// Fetch rows for specified table
$: {
if ($backendUiStore.selectedView?.name?.startsWith("all_")) {
if ($views.selected?.name?.startsWith("all_")) {
loading = true
api.fetchDataForView($backendUiStore.selectedView).then(rows => {
api.fetchDataForView($views.selected).then(rows => {
data = rows || []
loading = false
})
@ -39,7 +40,7 @@
<Table
{title}
{schema}
tableId={$backendUiStore.selectedTable?._id}
tableId={$tables.selected?._id}
{data}
allowEditing={true}
bind:hideAutocolumns
@ -50,7 +51,7 @@
title={isUsersTable ? 'Create New User' : 'Create New Row'}
modalContentComponent={isUsersTable ? CreateEditUser : CreateEditRow} />
<CreateViewButton />
<ManageAccessButton resourceId={$backendUiStore.selectedTable?._id} />
<ManageAccessButton resourceId={$tables.selected?._id} />
{#if isUsersTable}
<EditRolesButton />
{/if}

View File

@ -1,8 +1,4 @@
<script>
import { params } from "@sveltech/routify"
import { backendUiStore } from "builderStore"
import { notifier } from "builderStore/store/notifications"
import * as api from "./api"
import Table from "./Table.svelte"
export let query = {}

View File

@ -1,7 +1,7 @@
<script>
import api from "builderStore/api"
import Table from "./Table.svelte"
import { backendUiStore } from "builderStore"
import { tables } from 'stores/backend/'
export let tableId
export let rowId
@ -12,11 +12,11 @@
$: data = row?.[fieldName] ?? []
$: linkedTableId = data?.length ? data[0].tableId : null
$: linkedTable = $backendUiStore.tables.find(
$: linkedTable = $tables.list.find(
table => table._id === linkedTableId
)
$: schema = linkedTable?.schema
$: table = $backendUiStore.tables.find(table => table._id === tableId)
$: table = $tables.list.find(table => table._id === tableId)
$: fetchData(tableId, rowId)
$: {
let rowLabel = row?.[table?.primaryDisplay]

View File

@ -1,6 +1,7 @@
<script>
import api from "builderStore/api"
import { backendUiStore } from "builderStore"
import { tables } from 'stores/backend/'
import Table from "./Table.svelte"
import CalculateButton from "./buttons/CalculateButton.svelte"
import GroupByButton from "./buttons/GroupByButton.svelte"
@ -26,8 +27,8 @@
}
async function fetchViewData(name, field, groupBy, calculation) {
const tables = $backendUiStore.tables
const allTableViews = tables.map(table => table.views)
const _tables = $tables.list
const allTableViews = _tables.map(table => table.views)
const thisView = allTableViews.filter(
views => views != null && views[name] != null
)[0]

View File

@ -1,22 +1,18 @@
<script>
import { TextButton, Icon, Popover } from "@budibase/bbui"
import { backendUiStore } from "builderStore"
import { Roles } from "constants/backend"
import api from "builderStore/api"
import { TextButton, Popover } from "@budibase/bbui"
import { permissions } from 'stores/backend/'
import ManageAccessPopover from "../popovers/ManageAccessPopover.svelte"
export let resourceId
let anchor
let dropdown
let levels
let permissions
let resourcePermissions
async function openDropdown() {
permissions = await backendUiStore.actions.permissions.forResource(
resourcePermissions = await permissions.forResource(
resourceId
)
levels = await backendUiStore.actions.permissions.fetchLevels()
dropdown.show()
}
</script>
@ -30,8 +26,8 @@
<Popover bind:this={dropdown} {anchor} align="left">
<ManageAccessPopover
{resourceId}
{levels}
{permissions}
levels={$permissions}
permissions={resourcePermissions}
onClosed={dropdown.hide} />
</Popover>

View File

@ -1,9 +1,9 @@
<script>
import { backendUiStore } from "builderStore"
import { roles } from 'stores/backend/'
export let roleId
$: role = $backendUiStore.roles.find(role => role._id === roleId)
$: role = $roles.find(role => role._id === roleId)
$: roleName = role?.name ?? "Unknown role"
</script>

View File

@ -9,7 +9,8 @@
Radio,
} from "@budibase/bbui"
import { cloneDeep } from "lodash/fp"
import { backendUiStore } from "builderStore"
import { tables } from 'stores/backend/'
import { TableNames, UNEDITABLE_USER_FIELDS } from "constants"
import {
FIELDS,
@ -33,29 +34,29 @@
constraints: fieldDefinitions.STRING.constraints,
// Initial value for column name in other table for linked records
fieldName: $backendUiStore.selectedTable.name,
fieldName: $tables.selected.name,
}
let originalName = field.name
let primaryDisplay =
$backendUiStore.selectedTable.primaryDisplay == null ||
$backendUiStore.selectedTable.primaryDisplay === field.name
$tables.selected.primaryDisplay == null ||
$tables.selected.primaryDisplay === field.name
let table = $backendUiStore.selectedTable
let indexes = [...($backendUiStore.selectedTable.indexes || [])]
let table = $tables.selected
let indexes = [...($tables.selected.indexes || [])]
let confirmDeleteDialog
let deletion
$: tableOptions = $backendUiStore.tables.filter(
table => table._id !== $backendUiStore.draftTable._id
$: tableOptions = $tables.list.filter(
table => table._id !== $tables.draft._id
)
$: required = !!field?.constraints?.presence || primaryDisplay
$: uneditable =
$backendUiStore.selectedTable?._id === TableNames.USERS &&
$tables.selected?._id === TableNames.USERS &&
UNEDITABLE_USER_FIELDS.includes(field.name)
$: invalid =
(field.type === LINK_TYPE && !field.tableId) ||
Object.keys($backendUiStore.draftTable.schema).some(
Object.keys($tables.draft.schema).some(
key => key === field.name
)
@ -72,28 +73,25 @@
async function saveColumn() {
if (field.type === AUTO_COL) {
field = buildAutoColumn(
$backendUiStore.draftTable.name,
$tables.draft.name,
field.name,
field.subtype
)
}
backendUiStore.update(state => {
backendUiStore.actions.tables.saveField({
tables.saveField({
originalName,
field,
primaryDisplay,
indexes,
})
return state
})
onClosed()
}
function deleteColumn() {
if (field.name === $backendUiStore.selectedTable.primaryDisplay) {
if (field.name === $tables.selected.primaryDisplay) {
notifier.danger("You cannot delete the display column")
} else {
backendUiStore.actions.tables.deleteField(field)
tables.deleteField(field)
notifier.success(`Column ${field.name} deleted.`)
onClosed()
}

View File

@ -1,5 +1,5 @@
<script>
import { backendUiStore } from "builderStore"
import { tables, rows } from 'stores/backend/'
import { notifier } from "builderStore/store/notifications"
import RowFieldControl from "../RowFieldControl.svelte"
import * as api from "../api"
@ -12,8 +12,8 @@
$: creating = row?._id == null
$: table = row.tableId
? $backendUiStore.tables.find(table => table._id === row?.tableId)
: $backendUiStore.selectedTable
? $tables.list.find(table => table._id === row?.tableId)
: $tables.selected
$: tableSchema = Object.entries(table?.schema ?? {})
async function saveRow() {
@ -34,7 +34,7 @@
}
notifier.success("Row saved successfully.")
backendUiStore.actions.rows.save(rowResponse)
rows.save(rowResponse)
}
</script>

View File

@ -1,5 +1,6 @@
<script>
import { backendUiStore } from "builderStore"
import { tables, rows } from 'stores/backend/'
import { roles } from 'stores/backend/'
import { notifier } from "builderStore/store/notifications"
import RowFieldControl from "../RowFieldControl.svelte"
import * as backendApi from "../api"
@ -12,8 +13,8 @@
$: creating = row?._id == null
$: table = row.tableId
? $backendUiStore.tables.find(table => table._id === row?.tableId)
: $backendUiStore.selectedTable
? $tables.list.find(table => table._id === row?.tableId)
: $tables.selected
$: tableSchema = getUserSchema(table)
$: customSchemaKeys = getCustomSchemaKeys(tableSchema)
@ -66,7 +67,7 @@
}
notifier.success("User saved successfully.")
backendUiStore.actions.rows.save(rowResponse)
rows.save(rowResponse)
}
</script>
@ -91,7 +92,7 @@
data-cy="roleId-select"
bind:value={row.roleId}>
<option value="">Choose an option</option>
{#each $backendUiStore.roles as role}
{#each $roles as role}
<option value={role._id}>{role.name}</option>
{/each}
</Select>

View File

@ -1,5 +1,5 @@
<script>
import { backendUiStore } from "builderStore"
import { rows } from 'stores/backend/'
import * as api from "../api"
import { notifier } from "builderStore/store/notifications"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
@ -15,7 +15,7 @@
async function deleteRow() {
await api.deleteRow(row)
notifier.success("Row deleted")
backendUiStore.actions.rows.delete(row)
rows.delete(row)
}
</script>

View File

@ -4,14 +4,14 @@
import api from "builderStore/api"
import { notifier } from "builderStore/store/notifications"
import ErrorsBox from "components/common/ErrorsBox.svelte"
import { backendUiStore } from "builderStore"
import { roles } from 'stores/backend/'
let basePermissions = []
let selectedRole = {}
let errors = []
let builtInRoles = ["Admin", "Power", "Basic", "Public"]
$: selectedRoleId = selectedRole._id
$: otherRoles = $backendUiStore.roles.filter(
$: otherRoles = $roles.filter(
role => role._id !== selectedRoleId
)
$: isCreating = selectedRoleId == null || selectedRoleId === ""
@ -24,7 +24,7 @@
// Changes the selected role
const changeRole = event => {
const id = event?.target?.value
const role = $backendUiStore.roles.find(role => role._id === id)
const role = $roles.find(role => role._id === id)
if (role) {
selectedRole = {
...role,
@ -61,7 +61,7 @@
}
// Save/create the role
const response = await backendUiStore.actions.roles.save(selectedRole)
const response = await roles.save(selectedRole)
if (response.status === 200) {
notifier.success("Role saved successfully.")
} else {
@ -72,7 +72,7 @@
// Deletes the selected role
const deleteRole = async () => {
const response = await backendUiStore.actions.roles.delete(selectedRole)
const response = await roles.delete(selectedRole)
if (response.status === 200) {
changeRole()
notifier.success("Role deleted successfully.")
@ -98,7 +98,7 @@
value={selectedRoleId}
on:change={changeRole}>
<option value="">Create new role</option>
{#each $backendUiStore.roles as role}
{#each $roles as role}
<option value={role._id}>{role.name}</option>
{/each}
</Select>

View File

@ -1,6 +1,7 @@
<script>
import { Button, Select } from "@budibase/bbui"
import { backendUiStore } from "builderStore"
import { tables, views } from 'stores/backend/'
import { notifier } from "builderStore/store/notifications"
import analytics from "analytics"
@ -22,8 +23,8 @@
export let view = {}
export let onClosed
$: viewTable = $backendUiStore.tables.find(
({ _id }) => _id === $backendUiStore.selectedView.tableId
$: viewTable = $tables.list.find(
({ _id }) => _id === $views.selected.tableId
)
$: fields =
viewTable &&
@ -36,7 +37,7 @@
)
function saveView() {
backendUiStore.actions.views.save(view)
views.save(view)
notifier.success(`View ${view.name} saved.`)
onClosed()
analytics.captureEvent("Added View Calculate", { field: view.field })

View File

@ -1,7 +1,8 @@
<script>
import { Button, Input } from "@budibase/bbui"
import { goto } from "@sveltech/routify"
import { backendUiStore } from "builderStore"
import { views as viewsStore } from 'stores/backend/'
import { tables } from 'stores/backend/'
import { notifier } from "builderStore/store/notifications"
import analytics from "analytics"
@ -10,10 +11,7 @@
let name
let field
$: fields = Object.keys($backendUiStore.selectedTable.schema).filter(key => {
return $backendUiStore.selectedTable.schema[key].type === "number"
})
$: views = $backendUiStore.tables.flatMap(table =>
$: views = $tables.list.flatMap(table =>
Object.keys(table.views || {})
)
@ -22,9 +20,9 @@
notifier.danger(`View exists with name ${name}.`)
return
}
backendUiStore.actions.views.save({
viewsStore.save({
name,
tableId: $backendUiStore.selectedTable._id,
tableId: $tables.selected._id,
field,
})
notifier.success(`View ${name} created`)

View File

@ -1,6 +1,6 @@
<script>
import { Button, Input, Select, DatePicker } from "@budibase/bbui"
import { backendUiStore } from "builderStore"
import { tables, views } from 'stores/backend/'
import { notifier } from "builderStore/store/notifications"
import analytics from "analytics"
@ -49,13 +49,13 @@
export let view = {}
export let onClosed
$: viewTable = $backendUiStore.tables.find(
({ _id }) => _id === $backendUiStore.selectedView.tableId
$: viewTable = $tables.list.find(
({ _id }) => _id === $views.selected.tableId
)
$: fields = viewTable && Object.keys(viewTable.schema)
function saveView() {
backendUiStore.actions.views.save(view)
views.save(view)
notifier.success(`View ${view.name} saved.`)
onClosed()
analytics.captureEvent("Added View Filter", {

View File

@ -1,14 +1,14 @@
<script>
import { Button, Select } from "@budibase/bbui"
import { backendUiStore } from "builderStore"
import { tables, views } from 'stores/backend/'
import { notifier } from "builderStore/store/notifications"
import { FIELDS } from "constants/backend"
export let view = {}
export let onClosed
$: viewTable = $backendUiStore.tables.find(
({ _id }) => _id === $backendUiStore.selectedView.tableId
$: viewTable = $tables.list.find(
({ _id }) => _id === $views.selected.tableId
)
$: fields =
viewTable &&
@ -17,7 +17,7 @@
.map(([key]) => key)
function saveView() {
backendUiStore.actions.views.save(view)
views.save(view)
notifier.success(`View ${view.name} saved.`)
onClosed()
}

View File

@ -1,5 +1,5 @@
<script>
import { backendUiStore } from "builderStore"
import { roles, permissions as permissionsStore } from 'stores/backend/'
import { notifier } from "builderStore/store/notifications"
import { Button, Label, Input, Select, Spacer } from "@budibase/bbui"
@ -8,14 +8,14 @@
export let onClosed
async function changePermission(level, role) {
await backendUiStore.actions.permissions.save({
await permissionsStore.save({
level,
role,
resource: resourceId,
})
// Show updated permissions in UI: REMOVE
permissions = await backendUiStore.actions.permissions.forResource(
permissions = await permissionsStore.forResource(
resourceId
)
notifier.success("Updated permissions.")
@ -42,7 +42,7 @@
thin
value={permissions[level]}
on:change={e => changePermission(level, e.target.value)}>
{#each $backendUiStore.roles as role}
{#each $roles as role}
<option value={role._id}>{role.name}</option>
{/each}
</Select>
@ -67,10 +67,6 @@
font-weight: 500;
}
hr {
margin: var(--spacing-s) 0 var(--spacing-m) 0;
}
.footer {
display: flex;
justify-content: flex-end;

View File

@ -1,38 +1,38 @@
<script>
import { onMount } from "svelte"
import { goto } from "@sveltech/routify"
import { backendUiStore } from "builderStore"
import { database, datasources, queries } from 'stores/backend/'
import EditDatasourcePopover from "./popovers/EditDatasourcePopover.svelte"
import EditQueryPopover from "./popovers/EditQueryPopover.svelte"
import NavItem from "components/common/NavItem.svelte"
import ICONS from "./icons"
function selectDatasource(datasource) {
backendUiStore.actions.datasources.select(datasource._id)
datasources.select(datasource._id)
$goto(`./datasource/${datasource._id}`)
}
function onClickQuery(query) {
if ($backendUiStore.selectedQueryId === query._id) {
if ($queries.selected === query._id) {
return
}
backendUiStore.actions.queries.select(query)
queries.select(query)
$goto(`./datasource/${query.datasourceId}/${query._id}`)
}
onMount(() => {
backendUiStore.actions.datasources.fetch()
backendUiStore.actions.queries.fetch()
datasources.fetch()
queries.fetch()
})
</script>
{#if $backendUiStore.selectedDatabase && $backendUiStore.selectedDatabase._id}
{#if $database?._id}
<div class="hierarchy-items-container">
{#each $backendUiStore.datasources as datasource, idx}
{#each $datasources.list as datasource, idx}
<NavItem
border={idx > 0}
text={datasource.name}
selected={$backendUiStore.selectedDatasourceId === datasource._id}
selected={$datasources.selected === datasource._id}
on:click={() => selectDatasource(datasource)}>
<div class="datasource-icon" slot="icon">
<svelte:component
@ -42,12 +42,12 @@
</div>
<EditDatasourcePopover {datasource} />
</NavItem>
{#each $backendUiStore.queries.filter(query => query.datasourceId === datasource._id) as query}
{#each $queries.list.filter(query => query.datasourceId === datasource._id) as query}
<NavItem
indentLevel={1}
icon="ri-eye-line"
text={query.name}
selected={$backendUiStore.selectedQueryId === query._id}
selected={$queries.selected === query._id}
on:click={() => onClickQuery(query)}>
<EditQueryPopover {query} />
</NavItem>

View File

@ -1,8 +1,8 @@
<script>
import { goto, params } from "@sveltech/routify"
import { backendUiStore, store } from "builderStore"
import { goto } from "@sveltech/routify"
import { datasources } from 'stores/backend/'
import { notifier } from "builderStore/store/notifications"
import { Input, Label, ModalContent, Button, Spacer } from "@budibase/bbui"
import { Input, Label, ModalContent } from "@budibase/bbui"
import TableIntegrationMenu from "../TableIntegrationMenu/index.svelte"
import analytics from "analytics"
@ -14,7 +14,7 @@
function checkValid(evt) {
const datasourceName = evt.target.value
if (
$backendUiStore.datasources?.some(
$datasources?.list.some(
datasource => datasource.name === datasourceName
)
) {
@ -28,7 +28,7 @@
const { type, ...config } = integration
// Create datasource
const response = await backendUiStore.actions.datasources.save({
const response = await datasources.save({
name,
source: type,
config,

View File

@ -1,6 +1,6 @@
<script>
import { goto } from "@sveltech/routify"
import { backendUiStore } from "builderStore"
import { datasources } from 'stores/backend/'
import { notifier } from "builderStore/store/notifications"
import { DropdownMenu } from "@budibase/bbui"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
@ -22,8 +22,10 @@
}
async function deleteDatasource() {
const wasSelectedSource = $backendUiStore.selectedDatasourceId
await backendUiStore.actions.datasources.delete(datasource)
const wasSelectedSource = $datasources.selected
console.log(wasSelectedSource)
console.log(datasource)
await datasources.delete(datasource)
notifier.success("Datasource deleted")
// navigate to first index page if the source you are deleting is selected
if (wasSelectedSource === datasource._id) {

View File

@ -1,7 +1,6 @@
<script>
import { backendUiStore, store, allScreens } from "builderStore"
import { notifier } from "builderStore/store/notifications"
import { DropdownMenu, Button, Input } from "@budibase/bbui"
import { DropdownMenu } from "@budibase/bbui"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
@ -10,8 +9,6 @@
let anchor
let dropdown
let confirmDeleteDialog
let error = ""
let willBeDeleted
function hideEditor() {
dropdown?.hide()
@ -23,7 +20,7 @@
}
async function deleteQuery() {
await backendUiStore.actions.queries.delete(query)
await queries.delete(query)
notifier.success("Query deleted")
hideEditor()
}
@ -62,22 +59,4 @@
div.icon i {
font-size: 16px;
}
.actions {
padding: var(--spacing-xl);
display: grid;
grid-gap: var(--spacing-xl);
min-width: 400px;
}
h5 {
margin: 0;
font-weight: 500;
}
footer {
display: flex;
justify-content: flex-end;
gap: var(--spacing-m);
}
</style>

View File

@ -1,21 +1,21 @@
<script>
import { goto } from "@sveltech/routify"
import { backendUiStore } from "builderStore"
import { tables, views, database } from 'stores/backend/'
import { TableNames } from "constants"
import EditTablePopover from "./popovers/EditTablePopover.svelte"
import EditViewPopover from "./popovers/EditViewPopover.svelte"
import NavItem from "components/common/NavItem.svelte"
$: selectedView =
$backendUiStore.selectedView && $backendUiStore.selectedView.name
$views.selected && $views.selected.name
function selectTable(table) {
backendUiStore.actions.tables.select(table)
tables.select(table)
$goto(`./table/${table._id}`)
}
function selectView(view) {
backendUiStore.actions.views.select(view)
views.select(view)
$goto(`./view/${view.name}`)
}
@ -28,11 +28,12 @@
...table.views[viewName],
})
}
</script>
{#if $backendUiStore.selectedDatabase && $backendUiStore.selectedDatabase._id}
{#if $database?._id}
<div class="hierarchy-items-container">
{#each $backendUiStore.tables as table, idx}
{#each $tables.list as table, idx}
<NavItem
border={idx > 0}
icon={`ri-${table._id === TableNames.USERS ? 'user' : 'table'}-line`}
@ -43,7 +44,7 @@
<EditTablePopover {table} />
{/if}
</NavItem>
{#each Object.keys(table.views || {}) as viewName}
{#each Object.keys(table.views || {}) as viewName, idx (idx)}
<NavItem
indentLevel={1}
icon="ri-eye-line"

View File

@ -1,6 +1,7 @@
<script>
import { goto } from "@sveltech/routify"
import { backendUiStore, store } from "builderStore"
import { store } from "builderStore"
import { tables } from 'stores/backend/'
import { notifier } from "builderStore/store/notifications"
import { Input, Label, ModalContent, Toggle } from "@budibase/bbui"
import TableDataImport from "../TableDataImport.svelte"
@ -17,7 +18,7 @@
ROW_LIST_TEMPLATE,
]
$: tableNames = $backendUiStore.tables.map(table => table.name)
$: tableNames = $tables.list.map(table => table.name)
let modal
let name
@ -58,7 +59,7 @@
}
// Create table
const table = await backendUiStore.actions.tables.save(newTable)
const table = await tables.save(newTable)
notifier.success(`Table ${name} created successfully.`)
analytics.captureEvent("Table Created", { name })

View File

@ -1,6 +1,7 @@
<script>
import { goto } from "@sveltech/routify"
import { backendUiStore, store, allScreens } from "builderStore"
import { goto } from '@sveltech/routify'
import { store, allScreens } from "builderStore"
import { tables } from 'stores/backend/'
import { notifier } from "builderStore/store/notifications"
import { DropdownMenu, Button, Input } from "@budibase/bbui"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
@ -37,10 +38,10 @@
}
async function deleteTable() {
const wasSelectedTable = $backendUiStore.selectedTable
await backendUiStore.actions.tables.delete(table)
const wasSelectedTable = $tables.selected
await tables.delete(table)
store.actions.screens.delete(templateScreens)
await backendUiStore.actions.tables.fetch()
await tables.fetch()
notifier.success("Table deleted")
if (wasSelectedTable._id === table._id) {
$goto("./table")
@ -49,21 +50,16 @@
}
async function save() {
await backendUiStore.actions.tables.save(table)
await tables.save(table)
notifier.success("Table renamed successfully")
hideEditor()
}
function checkValid(evt) {
const tableName = evt.target.value
if (
originalName !== tableName &&
$backendUiStore.models?.some(model => model.name === tableName)
) {
error = `Table with name ${tableName} already exists. Please choose another name.`
return
}
error = ""
error = originalName !== tableName
? `Table with name ${tableName} already exists. Please choose another name.`
: ""
}
</script>

View File

@ -1,6 +1,6 @@
<script>
import { goto } from "@sveltech/routify"
import { backendUiStore } from "builderStore"
import { views } from 'stores/backend/'
import { notifier } from "builderStore/store/notifications"
import { DropdownMenu, Button, Input } from "@budibase/bbui"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
@ -29,7 +29,7 @@
}
async function save() {
await backendUiStore.actions.views.save({
await views.save({
originalName,
...view,
})
@ -40,7 +40,7 @@
async function deleteView() {
const name = view.name
const id = view.tableId
await backendUiStore.actions.views.delete(name)
await views.delete(name)
notifier.success("View deleted")
$goto(`./table/${id}`)
}

View File

@ -1,5 +1,5 @@
<script>
import { backendUiStore } from "builderStore"
import { tables } from 'stores/backend/'
import api from "builderStore/api"
import { Select, Label, Multiselect } from "@budibase/bbui"
import { capitalise } from "../../helpers"
@ -13,7 +13,7 @@
$: linkedRows = linkedIds
$: label = capitalise(schema.name)
$: linkedTableId = schema.tableId
$: linkedTable = $backendUiStore.tables.find(
$: linkedTable = $tables.list.find(
table => table._id === linkedTableId
)
$: fetchRows(linkedTableId)

View File

@ -4,10 +4,10 @@
import {
store,
allScreens,
backendUiStore,
selectedAccessRole,
screenSearchString,
} from "builderStore"
import { roles } from 'stores/backend/'
import { FrontendTypes } from "constants"
import ComponentNavigationTree from "components/design/NavigationPanel/ComponentNavigationTree/index.svelte"
import Layout from "components/design/NavigationPanel/Layout.svelte"
@ -81,7 +81,7 @@
on:change={updateAccessRole}
value={$selectedAccessRole}
label="Filter by Access">
{#each $backendUiStore.roles as role}
{#each $roles as role}
<option value={role._id}>{role.name}</option>
{/each}
</Select>

View File

@ -1,5 +1,7 @@
<script>
import { store, backendUiStore, allScreens } from "builderStore"
import { store, allScreens } from "builderStore"
import { tables } from 'stores/backend/'
import { roles } from 'stores/backend/'
import { Input, Select, ModalContent, Toggle } from "@budibase/bbui"
import getTemplates from "builderStore/store/screenTemplates"
import analytics from "analytics"
@ -14,7 +16,7 @@
let createLink = true
let roleId = "BASIC"
$: templates = getTemplates($store, $backendUiStore.tables)
$: templates = getTemplates($store, $tables.list)
$: route = !route && $allScreens.length === 0 ? "*" : route
$: {
if (templates && templateIndex === undefined) {
@ -105,7 +107,7 @@
bind:value={route}
on:change={routeChanged} />
<Select label="Access" bind:value={roleId} secondary>
{#each $backendUiStore.roles as role}
{#each $roles as role}
<option value={role._id}>{role.name}</option>
{/each}
</Select>

View File

@ -9,7 +9,9 @@
Drawer,
} from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import { store, backendUiStore, currentAsset } from "builderStore"
import { store, currentAsset } from "builderStore"
import { tables as tablesStore, queries as queriesStore } from 'stores/backend/'
import { datasources, integrations } from 'stores/backend/'
import { notifier } from "builderStore/store/notifications"
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
import IntegrationQueryEditor from "components/integration/index.svelte"
@ -22,12 +24,12 @@
export let otherSources
export let showAllQueries
$: tables = $backendUiStore.tables.map(m => ({
$: tables = $tablesStore.list.map(m => ({
label: m.name,
tableId: m._id,
type: "table",
}))
$: views = $backendUiStore.tables.reduce((acc, cur) => {
$: views = $tablesStore.list.reduce((acc, cur) => {
let viewsArr = Object.entries(cur.views).map(([key, value]) => ({
label: key,
name: key,
@ -36,7 +38,7 @@
}))
return [...acc, ...viewsArr]
}, [])
$: queries = $backendUiStore.queries
$: queries = $queriesStore.list
.filter(
query => showAllQueries || query.queryVerb === "read" || query.readable
)
@ -81,10 +83,10 @@
}
function fetchQueryDefinition(query) {
const source = $backendUiStore.datasources.find(
const source = $datasources.list.find(
ds => ds._id === query.datasourceId
).source
return $backendUiStore.integrations[source].query[query.queryVerb]
return $integrations[source].query[query.queryVerb]
}
</script>
@ -123,7 +125,7 @@
height={200}
query={value}
schema={fetchQueryDefinition(value)}
datasource={$backendUiStore.datasources.find(ds => ds._id === value.datasourceId)}
datasource={$datasources.list.find(ds => ds._id === value.datasourceId)}
editable={false} />
<Spacer large />
</div>

View File

@ -1,14 +1,15 @@
<script>
import { Select, Label, Spacer } from "@budibase/bbui"
import { store, backendUiStore, currentAsset } from "builderStore"
import { store, currentAsset } from "builderStore"
import { datasources, integrations, queries } from "stores/backend/"
import { getBindableProperties } from "builderStore/dataBinding"
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
import IntegrationQueryEditor from "components/integration/index.svelte"
export let parameters
$: query = $backendUiStore.queries.find(q => q._id === parameters.queryId)
$: datasource = $backendUiStore.datasources.find(
$: query = $queries.list.find(q => q._id === parameters.queryId)
$: datasource = $datasources.list.find(
ds => ds._id === parameters.datasourceId
)
$: bindableProperties = getBindableProperties(
@ -17,52 +18,42 @@
)
function fetchQueryDefinition(query) {
const source = $backendUiStore.datasources.find(
ds => ds._id === query.datasourceId
).source
return $backendUiStore.integrations[source].query[query.queryVerb]
const source = $datasources.list.find(ds => ds._id === query.datasourceId)
.source
return $integrations[source].query[query.queryVerb]
}
</script>
<div class="root">
<Label small>Datasource</Label>
<Select thin secondary bind:value={parameters.datasourceId}>
<Label small>Datasource</Label>
<Select thin secondary bind:value={parameters.datasourceId}>
<option value="" />
{#each $datasources.list as datasource}
<option value={datasource._id}>{datasource.name}</option>
{/each}
</Select>
<Spacer medium />
{#if parameters.datasourceId}
<Label small>Query</Label>
<Select thin secondary bind:value={parameters.queryId}>
<option value="" />
{#each $backendUiStore.datasources as datasource}
<option value={datasource._id}>{datasource.name}</option>
{#each $queries.list.filter(query => query.datasourceId === datasource._id) as query}
<option value={query._id}>{query.name}</option>
{/each}
</Select>
{/if}
<Spacer medium />
<Spacer medium />
{#if parameters.datasourceId}
<Label small>Query</Label>
<Select thin secondary bind:value={parameters.queryId}>
<option value="" />
{#each $backendUiStore.queries.filter(query => query.datasourceId === datasource._id) as query}
<option value={query._id}>{query.name}</option>
{/each}
</Select>
{/if}
<Spacer medium />
{#if query?.parameters?.length > 0}
<ParameterBuilder
bind:customParams={parameters.queryParams}
parameters={query.parameters}
bindings={bindableProperties} />
<IntegrationQueryEditor
height={200}
{query}
schema={fetchQueryDefinition(query)}
editable={false} />
{/if}
</div>
<style>
.root {
max-width: 800px;
margin: 0 auto;
}
</style>
{#if query?.parameters?.length > 0}
<ParameterBuilder
bind:customParams={parameters.queryParams}
parameters={query.parameters}
bindings={bindableProperties} />
<IntegrationQueryEditor
height={200}
{query}
schema={fetchQueryDefinition(query)}
editable={false} />
{/if}

View File

@ -1,13 +1,13 @@
<script>
import { Select } from "@budibase/bbui"
import { backendUiStore } from "builderStore"
import { roles } from 'stores/backend/'
export let value
</script>
<Select bind:value extraThin secondary on:change>
<option value="">Choose an option</option>
{#each $backendUiStore.roles as role}
{#each $roles as role}
<option value={role._id}>{role.name}</option>
{/each}
</Select>

View File

@ -1,6 +1,6 @@
<script>
import { Select } from "@budibase/bbui"
import { backendUiStore } from "builderStore"
import { tables } from 'stores/backend/'
export let value
</script>
@ -8,7 +8,7 @@
<div>
<Select extraThin secondary wide on:change {value}>
<option value="">Choose a table</option>
{#each $backendUiStore.tables as table}
{#each $tables.list as table}
<option value={table._id}>{table.name}</option>
{/each}
</Select>

View File

@ -15,7 +15,7 @@
import IntegrationQueryEditor from "components/integration/index.svelte"
import ExternalDataSourceTable from "components/backend/DataTable/ExternalDataSourceTable.svelte"
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
import { backendUiStore } from "builderStore"
import { datasources, integrations, queries } from 'stores/backend/'
const PREVIEW_HEADINGS = [
{
@ -35,13 +35,11 @@
export let query
export let fields = []
let config
let tab = "JSON"
let parameters
let data = []
let popover
$: datasource = $backendUiStore.datasources.find(
$: datasource = $datasources.list.find(
ds => ds._id === query.datasourceId
)
@ -58,7 +56,7 @@
$: datasourceType = datasource?.source
$: integrationInfo = $backendUiStore.integrations[datasourceType]
$: integrationInfo = $integrations[datasourceType]
$: queryConfig = integrationInfo?.query
$: shouldShowQueryConfig = queryConfig && query.queryVerb
@ -115,7 +113,7 @@
async function saveQuery() {
try {
const { _id } = await backendUiStore.actions.queries.save(
const { _id } = await queries.save(
query.datasourceId,
query
)

View File

@ -2,7 +2,7 @@
import { Input, Label, TextButton } from "@budibase/bbui"
import api from "builderStore/api"
import { notifier } from "builderStore/store/notifications"
import { backendUiStore } from "builderStore"
import { database } from 'stores/backend/'
import analytics from "analytics"
let keys = { budibase: "" }
@ -46,7 +46,7 @@
</TextButton>
<div>
<Label extraSmall grey>Instance ID (Webhooks)</Label>
<span>{$backendUiStore.selectedDatabase._id}</span>
<span>{$database._id}</span>
</div>
</div>

View File

@ -4,14 +4,13 @@
import {
store,
automationStore,
backendUiStore,
hostingStore,
} from "builderStore"
import { string, object } from "yup"
import api, { get } from "builderStore/api"
import Form from "@svelteschool/svelte-forms"
import Spinner from "components/common/Spinner.svelte"
import { API, Info, User } from "./Steps"
import { Info, User } from "./Steps"
import Indicator from "./Indicator.svelte"
import { Button } from "@budibase/bbui"
import { goto } from "@sveltech/routify"
@ -25,8 +24,6 @@
export let template
let lastApiKey
let fetchApiKeyPromise
const infoValidation = {
applicationName: string().required("Your application must have a name."),
@ -152,7 +149,7 @@
)
const pkg = await applicationPkg.json()
if (applicationPkg.ok) {
backendUiStore.actions.reset()
// backendUiStore.actions.reset()
await store.actions.initialise(pkg)
await automationStore.actions.fetch()
} else {

View File

@ -1,5 +1,6 @@
<script>
import { store, automationStore, backendUiStore } from "builderStore"
import { store, automationStore } from "builderStore"
import { roles } from 'stores/backend/'
import { Button } from "@budibase/bbui"
import SettingsLink from "components/settings/Link.svelte"
import ThemeEditorDropdown from "components/settings/ThemeEditorDropdown.svelte"
@ -17,10 +18,10 @@
const pkg = await res.json()
if (res.ok) {
backendUiStore.actions.reset()
// backendUiStore.actions.reset()
await store.actions.initialise(pkg)
await automationStore.actions.fetch()
await backendUiStore.actions.roles.fetch()
await roles.fetch()
return pkg
} else {
throw new Error(pkg)

View File

@ -1,11 +1,13 @@
<script>
import { params } from "@sveltech/routify"
import { backendUiStore } from "builderStore"
import { queries } from "stores/backend/"
if ($params.query) {
const query = $backendUiStore.queries.find(m => m._id === $params.query)
const query = $queries.list.find(
m => m._id === $params.query
)
if (query) {
backendUiStore.actions.queries.select(query)
queries.select(query)
}
}
</script>

View File

@ -1,6 +1,6 @@
<script>
import { params } from "@sveltech/routify"
import { backendUiStore } from "builderStore"
import { database, queries } from 'stores/backend/'
import QueryInterface from "components/integration/QueryViewer.svelte"
async function fetchQueryConfig() {
@ -14,8 +14,8 @@
}
}
$: selectedQuery = $backendUiStore.queries.find(
query => query._id === $backendUiStore.selectedQueryId
$: selectedQuery = $queries.list.find(
query => query._id === $queries.selected
) || {
datasourceId: $params.selectedDatasource,
parameters: [],
@ -26,7 +26,7 @@
<section>
<div class="inner">
{#if $backendUiStore.selectedDatabase._id && selectedQuery}
{#if $database._id && selectedQuery}
<QueryInterface query={selectedQuery} />
{/if}
</div>

View File

@ -1,13 +1,13 @@
<script>
import { params } from "@sveltech/routify"
import { backendUiStore } from "builderStore"
import { datasources } from 'stores/backend/'
if ($params.selectedDatasource) {
const datasource = $backendUiStore.datasources.find(
const datasource = $datasources.list.find(
m => m._id === $params.selectedDatasource
)
if (datasource) {
backendUiStore.actions.datasources.select(datasource._id)
datasources.select(datasource._id)
}
}
</script>

View File

@ -1,30 +1,30 @@
<script>
import { goto, beforeUrlChange } from "@sveltech/routify"
import { Button, Heading, Body, Spacer } from "@budibase/bbui"
import { backendUiStore } from "builderStore"
import { datasources, integrations, queries } from 'stores/backend/'
import { notifier } from "builderStore/store/notifications"
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
import ICONS from "components/backend/DatasourceNavigator/icons"
let unsaved = false
$: datasource = $backendUiStore.datasources.find(
ds => ds._id === $backendUiStore.selectedDatasourceId
$: datasource = $datasources.list.find(
ds => ds._id === $datasources.selected
)
$: integration = datasource && $backendUiStore.integrations[datasource.source]
$: integration = datasource && $integrations[datasource.source]
async function saveDatasource() {
// Create datasource
await backendUiStore.actions.datasources.save(datasource)
await datasources.save(datasource)
notifier.success(`Datasource ${name} saved successfully.`)
unsaved = false
}
function onClickQuery(query) {
if ($backendUiStore.selectedQueryId === query._id) {
if ($queries.selected === query._id) {
return
}
backendUiStore.actions.queries.select(query)
queries.select(query)
$goto(`../${query._id}`)
}
@ -87,7 +87,7 @@
</div>
<Spacer extraLarge />
<div class="query-list">
{#each $backendUiStore.queries.filter(query => query.datasourceId === datasource._id) as query}
{#each $queries.list.filter(query => query.datasourceId === datasource._id) as query}
<div class="query-list-item" on:click={() => onClickQuery(query)}>
<p class="query-name">{query.name}</p>
<p>{query.queryVerb}</p>

View File

@ -1,5 +1,5 @@
<script>
import { backendUiStore } from "builderStore"
import { datasources } from 'stores/backend/'
import { goto, leftover } from "@sveltech/routify"
import { onMount } from "svelte"
@ -7,10 +7,10 @@
// navigate to first datasource in list, if not already selected
if (
!$leftover &&
$backendUiStore.datasources.length > 0 &&
!$backendUiStore.selectedDatasourceId
$datasources.list.length > 0 &&
!$datasources.selected
) {
$goto(`./${$backendUiStore.datasources[0]._id}`)
$goto(`./${$datasources.list[0]._id}`)
}
})
</script>

View File

@ -1,23 +1,22 @@
<script>
import { backendUiStore } from "builderStore"
import { goto } from "@sveltech/routify"
import { onMount } from "svelte"
import { datasources, tables } from 'stores/backend/'
import { goto } from "@sveltech/routify"
import { onMount } from "svelte"
onMount(async () => {
// navigate to first table in list, if not already selected
$backendUiStore.datasources.length > 0 &&
$goto(`../${$backendUiStore.datasources[0]._id}`)
})
</script>
{#if $backendUiStore.tables.length === 0}
<i>Connect your first datasource to start building.</i>
{:else}<i>Select a datasource to edit</i>{/if}
<style>
i {
font-size: var(--font-size-m);
color: var(--grey-5);
margin-top: 2px;
}
</style>
onMount(async () => {
// navigate to first table in list, if not already selected
$datasources.list.length > 0 && $goto(`../${$datasources.list[0]._id}`)
})
</script>
{#if $tables.list.length === 0}
<i>Connect your first datasource to start building.</i>
{:else}<i>Select a datasource to edit</i>{/if}
<style>
i {
font-size: var(--font-size-m);
color: var(--grey-5);
margin-top: 2px;
}
</style>

View File

@ -1,13 +1,13 @@
<script>
import { params } from "@sveltech/routify"
import { backendUiStore } from "builderStore"
import { tables } from 'stores/backend/'
if ($params.selectedTable) {
const table = $backendUiStore.tables.find(
const table = $tables.list.find(
m => m._id === $params.selectedTable
)
if (table) {
backendUiStore.actions.tables.select(table)
tables.select(table)
}
}
</script>

View File

@ -1,11 +1,9 @@
<script>
import TableDataTable from "components/backend/DataTable/DataTable.svelte"
import { backendUiStore } from "builderStore"
$: selectedTable = $backendUiStore.selectedTable
import { tables, database } from 'stores/backend/'
</script>
{#if $backendUiStore.selectedDatabase._id && selectedTable.name}
{#if $database?._id && $tables?.selected?.name}
<TableDataTable />
{:else}<i>Create your first table to start building</i>{/if}

View File

@ -1,5 +1,5 @@
<script>
import { backendUiStore } from "builderStore"
import { tables } from 'stores/backend/'
import { goto, leftover } from "@sveltech/routify"
import { onMount } from "svelte"
@ -8,10 +8,10 @@
// and this is the final url (i.e. no selectedTable)
if (
!$leftover &&
$backendUiStore.tables.length > 0 &&
(!$backendUiStore.selectedTable || !$backendUiStore.selectedTable._id)
$tables.list.length > 0 &&
(!$tables.selected || !$tables.selected._id)
) {
$goto(`./${$backendUiStore.tables[0]._id}`)
$goto(`./${$tables.list[0]._id}`)
}
})
</script>

View File

@ -1,15 +1,14 @@
<script>
import { backendUiStore } from "builderStore"
import { tables } from 'stores/backend/'
import { goto } from "@sveltech/routify"
import { onMount } from "svelte"
onMount(async () => {
$backendUiStore.tables.length > 0 &&
$goto(`../${$backendUiStore.tables[0]._id}`)
$tables.list.length > 0 && $goto(`../${$tables.list[0]._id}`)
})
</script>
{#if $backendUiStore.tables.length === 0}
{#if $tables.list.length === 0}
<i>Create your first table to start building</i>
{:else}<i>Select a table to edit</i>{/if}

View File

@ -1,17 +1,17 @@
<script>
import { params } from "@sveltech/routify"
import { backendUiStore } from "builderStore"
import { tables, views } from 'stores/backend/'
if ($params.selectedView) {
let view
const viewName = decodeURI($params.selectedView)
for (let table of $backendUiStore.tables) {
for (let table of $tables.list) {
if (table.views && table.views[viewName]) {
view = table.views[viewName]
}
}
if (view) {
backendUiStore.actions.views.select({
views.select({
name: viewName,
...view,
})

View File

@ -1,11 +1,11 @@
<script>
import ViewDataTable from "components/backend/DataTable/ViewDataTable"
import { backendUiStore } from "builderStore"
import { views, database } from 'stores/backend/'
$: selectedView = $backendUiStore.selectedView
$: selectedView = $views.selected
</script>
{#if $backendUiStore.selectedDatabase._id && selectedView}
{#if $database._id && selectedView}
<ViewDataTable view={selectedView} />
{:else}<i>Create your first table to start building</i>{/if}

View File

@ -0,0 +1,3 @@
import { writable } from "svelte/store"
export const database = writable({})

View File

@ -0,0 +1,66 @@
import { writable } from "svelte/store"
import { queries } from "./"
import api from "../../builderStore/api"
export const INITIAL_DATASOURCE_VALUES = {
list: [],
selected: null,
}
export function createDatasourcesStore() {
const { subscribe, update, set } = writable(INITIAL_DATASOURCE_VALUES)
return {
subscribe,
update,
init: async () => {
const response = await api.get(`/api/datasources`)
const json = await response.json()
set({ list: json, selected: null })
},
fetch: async () => {
const response = await api.get(`/api/datasources`)
const json = await response.json()
update(state => ({ ...state, list: json }))
return json
},
select: async datasourceId => {
update(state => ({ ...state, selected: datasourceId }))
queries.update(state => ({ ...state, selected: null }))
},
save: async datasource => {
const response = await api.post("/api/datasources", datasource)
const json = await response.json()
update(state => {
const currentIdx = state.list.findIndex(ds => ds._id === json._id)
const sources = state.list
if (currentIdx >= 0) {
sources.splice(currentIdx, 1, json)
} else {
sources.push(json)
}
return { list: sources, selected: json._id }
})
return json
},
delete: async datasource => {
const response = await api.delete(
`/api/datasources/${datasource._id}/${datasource._rev}`
)
update(state => {
const sources = state.list.filter(
existing => existing._id !== datasource._id
)
return { list: sources, selected: null }
})
return response
},
}
}
export const datasources = createDatasourcesStore()

View File

@ -0,0 +1,9 @@
export { database } from "./database"
export { tables } from "./tables"
export { views } from "./views"
export { rows } from "./rows"
export { permissions } from "./permissions"
export { roles } from "./roles"
export { datasources } from "./datasources"
export { integrations } from "./integrations"
export { queries } from "./queries"

View File

@ -0,0 +1,3 @@
import { writable } from "svelte/store"
export const integrations = writable({})

View File

@ -0,0 +1,17 @@
import { writable } from "svelte/store"
import api from "builderStore/api"
export function createPermissionStore() {
const { subscribe } = writable([])
return {
subscribe,
forResource: async resourceId => {
const response = await api.get(`/api/permission/${resourceId}`)
const json = await response.json()
return json
},
}
}
export const permissions = createPermissionStore()

View File

@ -0,0 +1,77 @@
import { writable, get } from "svelte/store"
import { datasources, integrations } from "./"
import api from "builderStore/api"
export function createQueriesStore() {
const { subscribe, set, update } = writable({ list: [], selected: null })
return {
subscribe,
set,
update,
init: async () => {
const response = await api.get(`/api/queries`)
const json = await response.json()
set({ list: json, selected: null })
},
fetch: async () => {
const response = await api.get(`/api/queries`)
const json = await response.json()
update(state => ({ ...state, list: json }))
return json
},
save: async (datasourceId, query) => {
const _integrations = get(integrations)
const dataSource = get(datasources).list.filter(
ds => ds._id === datasourceId
)
// check if readable attribute is found
if (dataSource.length !== 0) {
const integration = _integrations[dataSource[0].source]
const readable = integration.query[query.queryVerb].readable
if (readable) {
query.readable = readable
}
}
query.datasourceId = datasourceId
const response = await api.post(`/api/queries`, query)
if (response.status !== 200) {
throw new Error("Failed saving query.")
}
const json = await response.json()
update(state => {
const currentIdx = state.list.findIndex(query => query._id === json._id)
const queries = state.list
if (currentIdx >= 0) {
queries.splice(currentIdx, 1, json)
} else {
queries.push(json)
}
return { list: queries, selected: json._id }
})
return json
},
select: query => {
update(state => ({ ...state, selected: query._id }))
datasources.update(state => ({ ...state, selected: query.datasourceId }))
},
delete: async query => {
const response = await api.delete(
`/api/queries/${query._id}/${query._rev}`
)
update(state => {
state.list = state.list.filter(existing => existing._id !== query._id)
if (state.selected === query._id) {
state.selected = null
}
return state
})
return response
},
}
}
export const queries = createQueriesStore()

View File

@ -0,0 +1,30 @@
import { writable } from "svelte/store"
import api from "builderStore/api"
export function createRolesStore() {
const { subscribe, update, set } = writable([])
return {
subscribe,
fetch: async () => {
set(await getRoles())
},
delete: async role => {
const response = await api.delete(`/api/roles/${role._id}/${role._rev}`)
update(state => state.filter(existing => existing._id !== role._id))
return response
},
save: async role => {
const response = await api.post("/api/roles", role)
set(await getRoles())
return response
},
}
}
async function getRoles() {
const response = await api.get("/api/roles")
return await response.json()
}
export const roles = createRolesStore()

View File

@ -0,0 +1,14 @@
import { writable, get } from "svelte/store"
import { views } from "./"
export function createRowsStore() {
const { subscribe } = writable([])
return {
subscribe,
save: () => views.select(get(views).selected),
delete: () => views.select(get(views).selected),
}
}
export const rows = createRowsStore()

View File

@ -0,0 +1,128 @@
import { writable, get } from "svelte/store"
import { views } from "./"
import { cloneDeep } from "lodash/fp"
import api from "builderStore/api"
export function createTablesStore() {
const store = writable({})
const { subscribe, update, set } = store
async function fetch() {
const tablesResponse = await api.get(`/api/tables`)
const tables = await tablesResponse.json()
update(state => ({ ...state, list: tables }))
}
async function select(table) {
if (!table) {
update(state => ({
...state,
selected: {},
}))
} else {
update(state => ({
...state,
selected: table,
draft: cloneDeep(table),
}))
views.select({ name: `all_${table._id}` })
}
}
async function save(table) {
const updatedTable = cloneDeep(table)
const oldTable = get(store).list.filter(t => t._id === table._id)[0]
const fieldNames = []
// update any renamed schema keys to reflect their names
for (let key of Object.keys(updatedTable.schema)) {
// if field name has been seen before remove it
if (fieldNames.indexOf(key.toLowerCase()) !== -1) {
delete updatedTable.schema[key]
continue
}
const field = updatedTable.schema[key]
const oldField = oldTable?.schema[key]
// if the type has changed then revert back to the old field
if (oldField != null && oldField?.type !== field.type) {
updatedTable.schema[key] = oldField
}
// field has been renamed
if (field.name && field.name !== key) {
updatedTable.schema[field.name] = field
updatedTable._rename = { old: key, updated: field.name }
delete updatedTable.schema[key]
}
// finally record this field has been used
fieldNames.push(key.toLowerCase())
}
const response = await api.post(`/api/tables`, updatedTable)
const savedTable = await response.json()
await fetch()
await select(savedTable)
return savedTable
}
return {
subscribe,
fetch,
select,
save,
init: async () => {
const response = await api.get("/api/tables")
const json = await response.json()
set({
list: json,
selected: {},
draft: {},
})
},
delete: async table => {
await api.delete(`/api/tables/${table._id}/${table._rev}`)
update(state => ({
...state,
list: state.list.filter(existing => existing._id !== table._id),
selected: {},
}))
},
saveField: ({ originalName, field, primaryDisplay = false, indexes }) => {
update(state => {
// delete the original if renaming
// need to handle if the column had no name, empty string
if (originalName || originalName === "") {
delete state.draft.schema[originalName]
state.draft._rename = {
old: originalName,
updated: field.name,
}
}
// Optionally set display column
if (primaryDisplay) {
state.draft.primaryDisplay = field.name
}
if (indexes) {
state.draft.indexes = indexes
}
state.draft.schema = {
...state.draft.schema,
[field.name]: cloneDeep(field),
}
save(state.draft)
return state
})
},
deleteField: field => {
update(state => {
delete state.draft.schema[field.name]
save(state.draft)
return state
})
},
}
}
export const tables = createTablesStore()

View File

@ -0,0 +1,68 @@
import { get } from 'svelte/store'
import api from 'builderStore/api'
jest.mock('builderStore/api');
import { SOME_DATASOURCE, SAVE_DATASOURCE} from './fixtures/datasources'
import { createDatasourcesStore } from "../datasources"
import { queries } from '../queries'
describe("Datasources Store", () => {
let store = createDatasourcesStore()
beforeEach(async () => {
api.get.mockReturnValue({ json: () => [SOME_DATASOURCE]})
await store.init()
})
it("Initialises correctly", async () => {
api.get.mockReturnValue({ json: () => [SOME_DATASOURCE]})
await store.init()
expect(get(store)).toEqual({ list: [SOME_DATASOURCE], selected: null})
})
it("fetches all the datasources and updates the store", async () => {
api.get.mockReturnValue({ json: () => [SOME_DATASOURCE]})
await store.fetch()
expect(get(store)).toEqual({ list: [SOME_DATASOURCE], selected: null})
})
it("selects a datasource", async () => {
store.select(SOME_DATASOURCE._id)
expect(get(store).select).toEqual(SOME_DATASOURCE._id)
})
it("resets the queries store when new datasource is selected", async () => {
await store.select(SOME_DATASOURCE._id)
const queriesValue = get(queries)
expect(queriesValue.selected).toEqual(null)
})
it("saves the datasource, updates the store and returns status message", async () => {
api.post.mockReturnValue({ json: () => SAVE_DATASOURCE})
await store.save({
name: 'CoolDB',
source: 'REST',
config: SOME_DATASOURCE[0].config
})
expect(get(store).list).toEqual(expect.arrayContaining([SAVE_DATASOURCE]))
})
it("deletes a datasource, updates the store and returns status message", async () => {
api.get.mockReturnValue({ json: () => SOME_DATASOURCE})
await store.fetch()
api.delete.mockReturnValue({status: 200, message: 'Datasource deleted.'})
await store.delete(SOME_DATASOURCE[0])
expect(get(store)).toEqual({ list: [], selected: null})
})
})

View File

@ -0,0 +1,25 @@
export const SOME_DATASOURCE = [
{
type: "datasource",
name: "erterter",
source: "REST",
config: {
url: "localhost",
defaultHeaders: {},
},
_id: "datasource_04b003a7b4a8428eadd3bb2f7eae0255",
_rev: "1-4e72002f1011e9392e655948469b7908",
},
]
export const SAVE_DATASOURCE = {
type: "datasource",
name: "CoolDB",
source: "REST",
config: {
url: "localhost",
defaultHeaders: {},
},
_id: "datasource_04b003a7b4a8428eadd3bb2f7eae0255",
_rev: "1-4e72002f1011e9392e655948469b7908",
}

View File

@ -0,0 +1,82 @@
export const SOME_QUERY = {
datasourceId: "datasource_04b003a7b4a8428eadd3bb2f7eae0255",
parameters: [],
fields: {
headers: {},
queryString: "",
path: "Speakers",
},
queryVerb: "read",
schema: {},
name: "Speakers",
_id:
"query_datasource_04b003a7b4a8428eadd3bb2f7eae0255_bcb8ffc6fcbc484e8d63121fc0bf986f",
_rev: "2-941f8699eb0adf995f8bd59c99203b26",
readable: true,
}
export const SAVE_QUERY_RESPONSE = {
datasourceId: "datasource_04b003a7b4a8428eadd3bb2f7eae0255",
parameters: [],
fields: {
headers: {},
queryString: "",
path: "Speakers",
},
queryVerb: "read",
schema: {
id: {
name: "id",
type: "string",
},
firstName: {
name: "firstName",
type: "string",
},
lastName: {
name: "lastName",
type: "string",
},
fullName: {
name: "fullName",
type: "string",
},
bio: {
name: "bio",
type: "string",
},
tagLine: {
name: "tagLine",
type: "string",
},
profilePicture: {
name: "profilePicture",
type: "string",
},
sessions: {
name: "sessions",
type: "string",
},
isTopSpeaker: {
name: "isTopSpeaker",
type: "string",
},
links: {
name: "links",
type: "string",
},
questionAnswers: {
name: "questionAnswers",
type: "string",
},
categories: {
name: "categories",
type: "string",
},
},
name: "Speakers",
_id:
"query_datasource_04b003a7b4a8428eadd3bb2f7eae0255_bcb8ffc6fcbc484e8d63121fc0bf986f",
_rev: "3-5a64adef494b1e9c793dc91b51ce73c6",
readable: true,
}

View File

@ -0,0 +1,32 @@
export const ROLES = [
{
name: "Test",
permissionId: "admin",
inherits: "ADMIN",
_id: "role_04681b7e71914a0aa53e09a5bea3584f",
_rev: "1-179c71ea61d7fd987306b84b6d64b00e",
},
{
_id: "ADMIN",
name: "Admin",
permissionId: "admin",
inherits: "POWER",
},
{
_id: "POWER",
name: "Power",
permissionId: "power",
inherits: "BASIC",
},
{
_id: "BASIC",
name: "Basic",
permissionId: "write",
inherits: "PUBLIC",
},
{
_id: "PUBLIC",
name: "Public",
permissionId: "public",
},
]

View File

@ -0,0 +1,717 @@
export const SOME_TABLES = [
{
type: "table",
views: {},
name: "Guest",
schema: {
"Auto ID": {
name: "Auto ID",
type: "number",
subtype: "autoID",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "number",
presence: false,
numericality: {
greaterThanOrEqualTo: "",
lessThanOrEqualTo: "",
},
},
lastID: 1,
},
"Created By": {
name: "Created By",
type: "link",
subtype: "createdBy",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "array",
presence: false,
},
tableId: "ta_users",
fieldName: "Guest-Created By",
relationshipType: "many-to-many",
},
"Created At": {
name: "Created At",
type: "datetime",
subtype: "createdAt",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "string",
length: {},
presence: false,
datetime: {
latest: "",
earliest: "",
},
},
},
"Updated By": {
name: "Updated By",
type: "link",
subtype: "updatedBy",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "array",
presence: false,
},
tableId: "ta_users",
fieldName: "Guest-Updated By",
relationshipType: "many-to-many",
},
"Updated At": {
name: "Updated At",
type: "datetime",
subtype: "updatedAt",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "string",
length: {},
presence: false,
datetime: {
latest: "",
earliest: "",
},
},
},
Name: {
type: "string",
constraints: {
type: "string",
length: {
maximum: "",
},
presence: false,
},
fieldName: "Guest",
name: "Name",
},
Episode: {
name: "Episode",
type: "link",
tableId: "ta_d4bf541ce0d84b16a1a8e0a060e5f7f7",
fieldName: "Guest",
relationshipType: "one-to-many",
},
},
primaryDisplay: "Name",
indexes: [],
_id: "ta_3c78cffe33664ca9bfb6b2b6cb3ee55a",
_rev: "10-27f034bf50ec3e2f180d8f96db1f0f31",
},
{
type: "table",
views: {},
name: "Sponsors",
schema: {
"Auto ID": {
name: "Auto ID",
type: "number",
subtype: "autoID",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "number",
presence: false,
numericality: {
greaterThanOrEqualTo: "",
lessThanOrEqualTo: "",
},
},
lastID: 1,
},
"Created By": {
name: "Created By",
type: "link",
subtype: "createdBy",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "array",
presence: false,
},
tableId: "ta_users",
fieldName: "Sponsors-Created By",
relationshipType: "many-to-many",
},
"Created At": {
name: "Created At",
type: "datetime",
subtype: "createdAt",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "string",
length: {},
presence: false,
datetime: {
latest: "",
earliest: "",
},
},
},
"Updated By": {
name: "Updated By",
type: "link",
subtype: "updatedBy",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "array",
presence: false,
},
tableId: "ta_users",
fieldName: "Sponsors-Updated By",
relationshipType: "many-to-many",
},
"Updated At": {
name: "Updated At",
type: "datetime",
subtype: "updatedAt",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "string",
length: {},
presence: false,
datetime: {
latest: "",
earliest: "",
},
},
},
Name: {
type: "string",
constraints: {
type: "string",
length: {
maximum: "",
},
presence: false,
},
fieldName: "Sponsors",
name: "Name",
},
Spot: {
type: "longform",
constraints: {
type: "string",
length: {},
presence: false,
},
fieldName: "Sponsors",
name: "Spot",
},
Episode: {
name: "Episode",
type: "link",
tableId: "ta_d4bf541ce0d84b16a1a8e0a060e5f7f7",
fieldName: "Sponsors",
relationshipType: "many-to-many",
},
},
primaryDisplay: "Name",
indexes: [],
_id: "ta_7fd0fa15edd54e0f91a47f50b7577281",
_rev: "7-de89b81e21ae4b3f65a6b655144fe097",
},
{
type: "table",
views: {},
name: "Episode",
schema: {
"Auto ID": {
name: "Auto ID",
type: "number",
subtype: "autoID",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "number",
presence: false,
numericality: {
greaterThanOrEqualTo: "",
lessThanOrEqualTo: "",
},
},
lastID: 1,
},
"Created By": {
name: "Created By",
type: "link",
subtype: "createdBy",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "array",
presence: false,
},
tableId: "ta_users",
fieldName: "Episode-Created By",
relationshipType: "many-to-many",
},
"Created At": {
name: "Created At",
type: "datetime",
subtype: "createdAt",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "string",
length: {},
presence: false,
datetime: {
latest: "",
earliest: "",
},
},
},
"Updated By": {
name: "Updated By",
type: "link",
subtype: "updatedBy",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "array",
presence: false,
},
tableId: "ta_users",
fieldName: "Episode-Updated By",
relationshipType: "many-to-many",
},
"Updated At": {
name: "Updated At",
type: "datetime",
subtype: "updatedAt",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "string",
length: {},
presence: false,
datetime: {
latest: "",
earliest: "",
},
},
},
Summary: {
type: "longform",
constraints: {
type: "string",
length: {},
presence: false,
},
fieldName: "Episode",
name: "Summary",
},
Author: {
type: "string",
constraints: {
type: "string",
length: {
maximum: "",
},
presence: false,
},
fieldName: "Episode",
name: "Author",
},
Published: {
type: "boolean",
constraints: {
type: "boolean",
presence: false,
},
fieldName: "Episode",
name: "Published",
},
Guest: {
type: "link",
constraints: {
type: "array",
presence: false,
},
fieldName: "Episode",
name: "Guest",
relationshipType: "many-to-one",
tableId: "ta_3c78cffe33664ca9bfb6b2b6cb3ee55a",
},
Title: {
type: "string",
constraints: {
type: "string",
length: {
maximum: "",
},
presence: false,
},
fieldName: "Episode",
name: "Title",
},
"Show Notes": {
type: "longform",
constraints: {
type: "string",
length: {},
presence: false,
},
fieldName: "Episode",
name: "Show Notes",
},
Sponsors: {
type: "link",
constraints: {
type: "array",
presence: false,
},
fieldName: "Episode",
name: "Sponsors",
relationshipType: "many-to-many",
tableId: "ta_7fd0fa15edd54e0f91a47f50b7577281",
},
Number: {
type: "number",
constraints: {
type: "number",
presence: false,
numericality: {
greaterThanOrEqualTo: "",
lessThanOrEqualTo: "",
},
},
fieldName: "Episode",
name: "Number",
},
Audio: {
type: "attachment",
constraints: {
type: "array",
presence: false,
},
fieldName: "Episode",
name: "Audio",
},
},
indexes: [],
primaryDisplay: "Author",
_id: "ta_d4bf541ce0d84b16a1a8e0a060e5f7f7",
_rev: "13-9d70dee825154a9df5c22e1d39bf269c",
},
{
type: "table",
views: {},
name: "Users",
schema: {
email: {
type: "string",
constraints: {
type: "string",
email: true,
length: {
maximum: "",
},
presence: true,
},
fieldName: "email",
name: "email",
},
roleId: {
fieldName: "roleId",
name: "roleId",
type: "options",
constraints: {
type: "string",
presence: false,
inclusion: ["ADMIN", "POWER", "BASIC", "PUBLIC", "BUILDER"],
},
},
status: {
fieldName: "status",
name: "status",
type: "options",
constraints: {
type: "string",
presence: false,
inclusion: ["active", "inactive"],
},
},
"Episode-Created By": {
name: "Episode-Created By",
type: "link",
tableId: "ta_d4bf541ce0d84b16a1a8e0a060e5f7f7",
fieldName: "Created By",
relationshipType: "many-to-many",
autocolumn: true,
},
"Episode-Updated By": {
name: "Episode-Updated By",
type: "link",
tableId: "ta_d4bf541ce0d84b16a1a8e0a060e5f7f7",
fieldName: "Updated By",
relationshipType: "many-to-many",
autocolumn: true,
},
"Guest-Created By": {
name: "Guest-Created By",
type: "link",
tableId: "ta_3c78cffe33664ca9bfb6b2b6cb3ee55a",
fieldName: "Created By",
relationshipType: "many-to-many",
autocolumn: true,
},
"Guest-Updated By": {
name: "Guest-Updated By",
type: "link",
tableId: "ta_3c78cffe33664ca9bfb6b2b6cb3ee55a",
fieldName: "Updated By",
relationshipType: "many-to-many",
autocolumn: true,
},
"Sponsors-Created By": {
name: "Sponsors-Created By",
type: "link",
tableId: "ta_7fd0fa15edd54e0f91a47f50b7577281",
fieldName: "Created By",
relationshipType: "many-to-many",
autocolumn: true,
},
"Sponsors-Updated By": {
name: "Sponsors-Updated By",
type: "link",
tableId: "ta_7fd0fa15edd54e0f91a47f50b7577281",
fieldName: "Updated By",
relationshipType: "many-to-many",
autocolumn: true,
},
},
primaryDisplay: "email",
_id: "ta_users",
_rev: "67-1833e6a0028c100633e31788fe958a62",
},
]
export const SAVE_TABLES_RESPONSE = {
type: "table",
_id: "ta_3c78cffe33664ca9bfb6b2b6cb3ee55a",
views: {},
name: "Guest",
schema: {
"Auto ID": {
name: "Auto ID",
type: "number",
subtype: "autoID",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "number",
presence: false,
numericality: {
greaterThanOrEqualTo: "",
lessThanOrEqualTo: "",
},
},
lastID: 1,
},
"Created By": {
name: "Created By",
type: "link",
subtype: "createdBy",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "array",
presence: false,
},
tableId: "ta_users",
fieldName: "Guest-Created By",
relationshipType: "many-to-many",
},
"Created At": {
name: "Created At",
type: "datetime",
subtype: "createdAt",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "string",
length: {},
presence: false,
datetime: {
latest: "",
earliest: "",
},
},
},
"Updated By": {
name: "Updated By",
type: "link",
subtype: "updatedBy",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "array",
presence: false,
},
tableId: "ta_users",
fieldName: "Guest-Updated By",
relationshipType: "many-to-many",
},
"Updated At": {
name: "Updated At",
type: "datetime",
subtype: "updatedAt",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "string",
length: {},
presence: false,
datetime: {
latest: "",
earliest: "",
},
},
},
Episode: {
name: "Episode",
type: "link",
tableId: "ta_d4bf541ce0d84b16a1a8e0a060e5f7f7",
fieldName: "Guest",
relationshipType: "one-to-many",
},
Names: {
type: "string",
constraints: {
type: "string",
length: {
maximum: "",
},
presence: false,
},
fieldName: "Guest",
name: "Names",
},
},
primaryDisplay: "Names",
indexes: [],
_rev: "11-7c153edbc6d7c43821cfd5ed526266cf",
}
export const A_TABLE = {
type: "table",
views: {},
name: "Guest",
schema: {
"Auto ID": {
name: "Auto ID",
type: "number",
subtype: "autoID",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "number",
presence: false,
numericality: {
greaterThanOrEqualTo: "",
lessThanOrEqualTo: "",
},
},
lastID: 1,
},
"Created By": {
name: "Created By",
type: "link",
subtype: "createdBy",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "array",
presence: false,
},
tableId: "ta_users",
fieldName: "Guest-Created By",
relationshipType: "many-to-many",
},
"Created At": {
name: "Created At",
type: "datetime",
subtype: "createdAt",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "string",
length: {},
presence: false,
datetime: {
latest: "",
earliest: "",
},
},
},
"Updated By": {
name: "Updated By",
type: "link",
subtype: "updatedBy",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "array",
presence: false,
},
tableId: "ta_users",
fieldName: "Guest-Updated By",
relationshipType: "many-to-many",
},
"Updated At": {
name: "Updated At",
type: "datetime",
subtype: "updatedAt",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "string",
length: {},
presence: false,
datetime: {
latest: "",
earliest: "",
},
},
},
Episode: {
name: "Episode",
type: "link",
tableId: "ta_d4bf541ce0d84b16a1a8e0a060e5f7f7",
fieldName: "Guest",
relationshipType: "one-to-many",
},
Names: {
type: "string",
constraints: {
type: "string",
length: {
maximum: "",
},
presence: false,
},
fieldName: "Guest",
name: "Names",
},
},
primaryDisplay: "Names",
indexes: [],
_id: "ta_3c78cffe33664ca9bfb6b2b6cb3ee55a",
_rev: "10-27f034bf50ec3e2f180d8f96db1f0f31",
_rename: {
old: "Name",
updated: "Names",
},
}

View File

@ -0,0 +1,102 @@
export const A_VIEW = {
name: "Published",
tableId: "ta_3c78cffe33664ca9bfb6b2b6cb3ee55a",
filters: [],
schema: {
"Auto ID": {
name: "Auto ID",
type: "number",
subtype: "autoID",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "number",
presence: false,
numericality: {
greaterThanOrEqualTo: "",
lessThanOrEqualTo: "",
},
},
lastID: 2,
},
"Created By": {
name: "Created By",
type: "link",
subtype: "createdBy",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "array",
presence: false,
},
tableId: "ta_users",
fieldName: "Guest-Created By",
relationshipType: "many-to-many",
},
"Created At": {
name: "Created At",
type: "datetime",
subtype: "createdAt",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "string",
length: {},
presence: false,
datetime: {
latest: "",
earliest: "",
},
},
},
"Updated By": {
name: "Updated By",
type: "link",
subtype: "updatedBy",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "array",
presence: false,
},
tableId: "ta_users",
fieldName: "Guest-Updated By",
relationshipType: "many-to-many",
},
"Updated At": {
name: "Updated At",
type: "datetime",
subtype: "updatedAt",
icon: "ri-magic-line",
autocolumn: true,
constraints: {
type: "string",
length: {},
presence: false,
datetime: {
latest: "",
earliest: "",
},
},
},
Episode: {
name: "Episode",
type: "link",
tableId: "ta_d4bf541ce0d84b16a1a8e0a060e5f7f7",
fieldName: "Guest",
relationshipType: "one-to-many",
},
Names: {
type: "string",
constraints: {
type: "string",
length: {
maximum: "",
},
presence: false,
},
fieldName: "Guest",
name: "Names",
},
},
}

View File

@ -0,0 +1,25 @@
import api from 'builderStore/api'
jest.mock('builderStore/api');
const PERMISSIONS_FOR_RESOURCE = {
"write": "BASIC",
"read": "BASIC"
}
import { createPermissionStore } from "../permissions"
describe("Permissions Store", () => {
const store = createPermissionStore()
it("fetches permissions for specific resource", async () => {
api.get.mockReturnValueOnce({ json: () => PERMISSIONS_FOR_RESOURCE})
const resourceId = "ta_013657543b4043b89dbb17e9d3a4723a"
const permissions = await store.forResource(resourceId)
expect(api.get).toBeCalledWith(`/api/permission/${resourceId}`)
expect(permissions).toEqual(PERMISSIONS_FOR_RESOURCE)
})
})

View File

@ -0,0 +1,54 @@
import { get } from 'svelte/store'
import api from 'builderStore/api'
jest.mock('builderStore/api');
import { SOME_QUERY, SAVE_QUERY_RESPONSE } from './fixtures/queries'
import { createQueriesStore } from "../queries"
import { datasources } from '../datasources'
describe("Queries Store", () => {
let store = createQueriesStore()
beforeEach(async () => {
api.get.mockReturnValue({ json: () => [SOME_QUERY]})
await store.init()
})
it("Initialises correctly", async () => {
api.get.mockReturnValue({ json: () => [SOME_QUERY]})
await store.init()
expect(get(store)).toEqual({ list: [SOME_QUERY], selected: null})
})
it("fetches all the queries", async () => {
api.get.mockReturnValue({ json: () => [SOME_QUERY]})
await store.fetch()
expect(get(store)).toEqual({ list: [SOME_QUERY], selected: null})
})
it("selects a query and updates selected datasource", async () => {
await store.select(SOME_QUERY)
expect(get(store).selected).toEqual(SOME_QUERY._id)
expect(get(datasources).selected).toEqual(SOME_QUERY.datasourceId)
})
it("saves the query, updates the store and returns status message", async () => {
api.post.mockReturnValue({ json: () => SAVE_QUERY_RESPONSE})
await store.select(SOME_QUERY.datasourceId, SOME_QUERY)
expect(get(store).list).toEqual(expect.arrayContaining([SOME_QUERY]))
})
it("deletes a query, updates the store and returns status message", async () => {
api.delete.mockReturnValue({status: 200, message: `Query deleted.`})
await store.delete(SOME_QUERY)
expect(get(store)).toEqual({ list: [], selected: null})
})
})

View File

@ -0,0 +1,35 @@
import { get } from 'svelte/store'
import api from 'builderStore/api'
jest.mock('builderStore/api');
import { createRolesStore } from "../roles"
import { ROLES } from './fixtures/roles'
describe("Roles Store", () => {
let store = createRolesStore()
beforeEach( async() => {
store = createRolesStore()
})
it("fetches roles from backend", async () => {
api.get.mockReturnValue({ json: () => ROLES})
await store.fetch()
expect(api.get).toBeCalledWith("/api/roles")
expect(get(store)).toEqual(ROLES)
})
it("deletes a role", async () => {
api.get.mockReturnValueOnce({ json: () => ROLES})
await store.fetch()
api.delete.mockReturnValue({status: 200, message: `Role deleted.`})
const updatedRoles = [...ROLES.slice(1)]
await store.delete(ROLES[0])
expect(get(store)).toEqual(updatedRoles)
})
})

View File

@ -0,0 +1,75 @@
import { get } from 'svelte/store'
import api from 'builderStore/api'
jest.mock('builderStore/api');
import { SOME_TABLES, SAVE_TABLES_RESPONSE, A_TABLE } from './fixtures/tables'
import { createTablesStore } from "../tables"
import { views } from '../views'
describe("Tables Store", () => {
let store = createTablesStore()
beforeEach(async () => {
api.get.mockReturnValue({ json: () => SOME_TABLES})
await store.init()
})
it("Initialises correctly", async () => {
expect(get(store)).toEqual({ list: SOME_TABLES, selected: {}, draft: {}})
})
it("fetches all the tables", async () => {
api.get.mockReturnValue({ json: () => SOME_TABLES})
await store.fetch()
expect(get(store)).toEqual({ list: SOME_TABLES, selected: {}, draft: {}})
})
it("selects a table", async () => {
const tableToSelect = SOME_TABLES[0]
await store.select(tableToSelect)
expect(get(store).selected).toEqual(tableToSelect)
expect(get(store).draft).toEqual(tableToSelect)
})
it("selecting without a param resets the selected property", async () => {
await store.select()
expect(get(store).draft).toEqual({})
})
it("selecting a table updates the view store", async () => {
const tableToSelect = SOME_TABLES[0]
await store.select(tableToSelect)
expect(get(store).selected).toEqual(tableToSelect)
expect(get(views).selected).toEqual({ name: `all_${tableToSelect._id}` })
})
it("saving a table also selects it", async () => {
api.post.mockReturnValue({ json: () => SAVE_TABLES_RESPONSE})
await store.save(A_TABLE)
expect(get(store).selected).toEqual(SAVE_TABLES_RESPONSE)
})
it("saving the table returns a response", async () => {
api.post.mockReturnValue({ json: () => SAVE_TABLES_RESPONSE})
const response = await store.save(A_TABLE)
expect(response).toEqual(SAVE_TABLES_RESPONSE)
})
it("deleting a table removes it from the store", async () => {
api.delete.mockReturnValue({status: 200, message: `Table deleted.`})
await store.delete(A_TABLE)
expect(get(store).list).toEqual(expect.not.arrayContaining([A_TABLE]))
})
// TODO: Write tests for saving and deleting fields
})

View File

@ -0,0 +1,45 @@
import { writable, get } from "svelte/store"
import { tables } from "./"
import api from "builderStore/api"
export function createViewsStore() {
const { subscribe, update } = writable({
list: [],
selected: null,
})
return {
subscribe,
select: async view => {
update(state => ({
...state,
selected: view,
}))
},
delete: async view => {
await api.delete(`/api/views/${view}`)
await tables.fetch()
},
save: async view => {
const response = await api.post(`/api/views`, view)
const json = await response.json()
const viewMeta = {
name: view.name,
...json,
}
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(state => ({ ...state, selected: viewMeta }))
},
}
}
export const views = createViewsStore()

View File

@ -1,111 +0,0 @@
export const componentsAndScreens = () => ({
components: [
{
_instanceName: "TextBox",
tags: ["Text", "input"],
children: false,
props: {
size: { type: "options", options: ["small", "medium", "large"] },
isPassword: "bool",
placeholder: "string",
label: "string",
},
},
{
_instanceName: "Button",
tags: ["input"],
children: true,
props: {
size: { type: "options", options: ["small", "medium", "large"] },
css: "string",
contentText: "string",
},
},
{
_instanceName: "div",
tags: ["input"],
props: {
width: "number",
},
},
{
_instanceName: "Row View",
tags: ["row"],
props: {
data: "state",
},
},
],
screens: [
{
props: {
_component: "budibase-components/TextBox",
_instanceName: "SmallTextbox",
size: "small",
},
},
{
name: "common/PasswordBox",
tags: ["mask"],
props: {
_component: "budibase-components/TextBox",
_instanceName: "PasswordBox",
isPassword: true,
size: "small",
},
},
{
props: {
_component: "budibase-components/Button",
_instanceName: "PrimaryButton",
css: "btn-primary",
},
},
{
route: "",
props: {
_component: "budibase-components/div",
width: 100,
_instanceName: "Screen 1",
_children: [
{
_component: "budibase-components/Button",
contentText: "Button 1",
_instanceName: "Button 1",
},
{
_component: "budibase-components/Button",
contentText: "Button 2",
_instanceName: "Button 2",
},
{
_component: "budibase-components/TextBox",
_instanceName: "TextBox",
isPassword: true,
size: "small",
},
],
},
},
{
props: {
_component: "budibase-components/div",
_instanceName: "Field",
_children: [
{
_component: "common/SmallTextbox",
},
],
},
},
],
})
export const stripStandardProps = props => {
delete props._id
delete props._styles
}

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,7 @@
"inquirer": "^8.0.0",
"lookpath": "^1.1.0",
"pkg": "^4.4.9",
"posthog-node": "^1.0.7",
"randomstring": "^1.1.5"
},
"devDependencies": {

View File

@ -0,0 +1,38 @@
const PostHog = require("posthog-node")
const {
BUDIBASE_POSTHOG_URL,
BUDIBASE_POSTHOG_TOKEN,
AnalyticsEvents,
} = require("../constants")
const ConfigManager = require("../structures/ConfigManager")
class AnalyticsClient {
constructor() {
this.client = new PostHog(BUDIBASE_POSTHOG_TOKEN, {
host: BUDIBASE_POSTHOG_URL,
})
this.configManager = new ConfigManager()
}
capture(event) {
if (this.manager.config.analyticsDisabled) return
this.client.capture(event)
}
enable() {
this.configManager.removeKey("analyticsDisabled")
this.client.capture({ event: AnalyticsEvents.OptIn, distinctId: "cli" })
}
disable() {
this.client.capture({ event: AnalyticsEvents.OptOut, distinctId: "cli" })
this.configManager.setValue("analyticsDisabled", true)
}
status() {
return this.configManager.config.analyticsDisabled ? "disabled" : "enabled"
}
}
module.exports = AnalyticsClient

View File

@ -0,0 +1,63 @@
const Command = require("../structures/Command")
const { CommandWords } = require("../constants")
const { success, error } = require("../utils")
const AnalyticsClient = require("./Client")
const client = new AnalyticsClient()
async function optOut() {
try {
// opt them out
client.disable()
console.log(
success(
"Successfully opted out of budibase analytics. You can opt in at any time by running 'budi analytics opt-in'"
)
)
} catch (err) {
console.log(
error(
"Error opting out of budibase analytics. Please try again later.",
err
)
)
}
}
async function optIn() {
try {
// opt them in
client.enable()
console.log(
success(
"Successfully opted in to budibase analytics. Thank you for helping us make budibase better!"
)
)
} catch (err) {
console.log(
error("Error opting in to budibase analytics. Please try again later.")
)
}
}
async function status() {
try {
console.log(success(`Budibase analytics ${client.status()}`))
} catch (err) {
console.log(
error("Error fetching analytics status. Please try again later.")
)
}
}
const command = new Command(`${CommandWords.ANALYTICS}`)
.addHelp("Control the analytics you send to budibase.")
.addSubOption("--optin", "Opt in to sending analytics to budibase", optIn)
.addSubOption("--optout", "Opt out of sending analytics to budibase.", optOut)
.addSubOption(
"--status",
"Check whether you are currently opted in to budibase analytics.",
status
)
exports.command = command

View File

@ -1,5 +1,6 @@
exports.CommandWords = {
HOSTING: "hosting",
ANALYTICS: "analytics",
HELP: "help",
}
@ -7,3 +8,12 @@ exports.InitTypes = {
QUICK: "quick",
DIGITAL_OCEAN: "do",
}
exports.AnalyticsEvents = {
OptOut: "analytics_opt_out",
OptIn: "analytics_opt_in",
SelfHostInit: "hosting_init",
}
exports.BUDIBASE_POSTHOG_URL = "https://posthog.budi.live"
exports.BUDIBASE_POSTHOG_TOKEN = process.env.BUDIBASE_POSTHOG_TOKEN

View File

@ -1,5 +1,5 @@
const Command = require("../structures/Command")
const { CommandWords, InitTypes } = require("../constants")
const { CommandWords, InitTypes, AnalyticsEvents } = require("../constants")
const { lookpath } = require("lookpath")
const {
downloadFile,
@ -13,6 +13,7 @@ const fs = require("fs")
const compose = require("docker-compose")
const makeEnv = require("./makeEnv")
const axios = require("axios")
const AnalyticsClient = require("../analytics/Client")
const BUDIBASE_SERVICES = ["app-service", "worker-service"]
const ERROR_FILE = "docker-error.log"
@ -22,6 +23,8 @@ const FILE_URLS = [
]
const DO_USER_DATA_URL = "http://169.254.169.254/metadata/v1/user-data"
const client = new AnalyticsClient()
async function downloadFiles() {
const promises = []
for (let url of FILE_URLS) {
@ -70,6 +73,13 @@ async function init(type) {
return
}
}
client.capture({
distinctId: "cli",
event: AnalyticsEvents.SelfHostInit,
properties: {
type,
},
})
await downloadFiles()
const config = isQuick ? makeEnv.QUICK_CONFIG : {}
if (type === InitTypes.DIGITAL_OCEAN) {

View File

@ -1,5 +1,6 @@
const analytics = require("./analytics")
const hosting = require("./hosting")
exports.getCommands = () => {
return [hosting.command]
return [hosting.command, analytics.command]
}

View File

@ -0,0 +1,50 @@
const fs = require("fs")
const path = require("path")
const os = require("os")
const { error } = require("../utils")
class ConfigManager {
constructor() {
this.path = path.join(os.homedir(), ".budibase.json")
if (!fs.existsSync(this.path)) {
fs.writeFileSync(this.path, "{}")
}
}
get config() {
try {
return JSON.parse(fs.readFileSync(this.path, "utf8"))
} catch (err) {
console.log(
error(
"Error parsing configuration file. Please check your .budibase.json is valid."
)
)
return {}
}
}
set config(json) {
fs.writeFileSync(this.path, JSON.stringify(json))
}
getValue(key) {
return this.config[key]
}
setValue(key, value) {
const updated = {
...this.config,
[key]: value,
}
this.config = updated
}
removeKey(key) {
const updated = { ...this.config }
delete updated[key]
this.config = updated
}
}
module.exports = ConfigManager

View File

@ -42,7 +42,7 @@ exports.info = info => {
}
exports.logErrorToFile = (file, error) => {
fs.writeFileSync(path.resolve(`./${file}`), `Budiase Error\n${error}`)
fs.writeFileSync(path.resolve(`./${file}`), `Budibase Error\n${error}`)
}
exports.parseEnv = env => {

View File

@ -181,6 +181,13 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
axios-retry@^3.1.9:
version "3.1.9"
resolved "https://registry.yarnpkg.com/axios-retry/-/axios-retry-3.1.9.tgz#6c30fc9aeb4519aebaec758b90ef56fa03fe72e8"
integrity sha512-NFCoNIHq8lYkJa6ku4m+V1837TP6lCa7n79Iuf8/AqATAHYB0ISaAS1eyIenDOfHOLtym34W65Sjke2xjg2fsA==
dependencies:
is-retry-allowed "^1.1.0"
axios@^0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
@ -260,6 +267,11 @@ chardet@^0.7.0:
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
charenc@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=
cli-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
@ -308,6 +320,11 @@ commander@^7.1.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-7.1.0.tgz#f2eaecf131f10e36e07d894698226e36ae0eb5ff"
integrity sha512-pRxBna3MJe6HKnBGsDyMv8ETbptw3axEdYHoqNh7gu5oDcew8fs0xnivZGm06Ogk8zGAJ9VX+OPEr2GXEQK4dg==
component-type@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/component-type/-/component-type-1.2.1.tgz#8a47901700238e4fc32269771230226f24b415a9"
integrity sha1-ikeQFwAjjk/DIml3EjAibyS0Fak=
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@ -327,6 +344,11 @@ cross-spawn@^7.0.2:
shebang-command "^2.0.0"
which "^2.0.1"
crypt@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=
dashdash@^1.12.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
@ -832,6 +854,11 @@ into-stream@^5.1.1:
from2 "^2.3.0"
p-is-promise "^3.0.0"
is-buffer@~1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
is-core-module@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a"
@ -861,6 +888,11 @@ is-number@^7.0.0:
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
is-retry-allowed@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4"
integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==
is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
@ -881,6 +913,11 @@ isstream@~0.1.2:
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
join-component@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/join-component/-/join-component-1.1.0.tgz#b8417b750661a392bee2c2537c68b2a9d4977cd5"
integrity sha1-uEF7dQZho5K+4sJTfGiyqdSXfNU=
js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@ -974,6 +1011,15 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
md5@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f"
integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==
dependencies:
charenc "0.0.2"
crypt "0.0.2"
is-buffer "~1.1.6"
merge2@^1.3.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
@ -1028,6 +1074,11 @@ ms@2.1.2:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
ms@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
multistream@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/multistream/-/multistream-2.1.1.tgz#629d3a29bd76623489980d04519a2c365948148c"
@ -1172,6 +1223,20 @@ pkg@^4.4.9:
resolve "^1.15.1"
stream-meter "^1.0.4"
posthog-node@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/posthog-node/-/posthog-node-1.0.7.tgz#a7a9525eebff23312117e57cff3ddac82afb2262"
integrity sha512-KTCwyU+PU1eAQtjy5ZSJ47mrxv2d/mMkxo+vvV5c+YqfE4mBAY1UPEPMv1nElb5Vq0UnxvyQXaUnOn8d8Xr6Eg==
dependencies:
axios "^0.21.1"
axios-retry "^3.1.9"
component-type "^1.2.1"
join-component "^1.1.0"
md5 "^2.3.0"
ms "^2.1.3"
remove-trailing-slash "^0.1.1"
uuid "^8.3.2"
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@ -1242,6 +1307,11 @@ regexpp@^3.1.0:
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2"
integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==
remove-trailing-slash@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/remove-trailing-slash/-/remove-trailing-slash-0.1.1.tgz#be2285a59f39c74d1bce4f825950061915e3780d"
integrity sha512-o4S4Qh6L2jpnCy83ysZDau+VORNvnFw07CKSAymkd6ICNVEPisMyzlc00KlvvicsxKck94SEwhDnMNdICzO+tA==
request-progress@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe"
@ -1593,6 +1663,11 @@ uuid@^3.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
v8-compile-cache@^2.0.3:
version "2.3.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"