Merge remote-tracking branch 'origin/master' into feat/automation-store-ts-conversion
This commit is contained in:
commit
3807568498
|
@ -12,6 +12,7 @@
|
|||
export let name
|
||||
export let config
|
||||
export let showModal = () => {}
|
||||
export let placeholder
|
||||
|
||||
const selectComponent = type => {
|
||||
if (type === "object") {
|
||||
|
@ -40,6 +41,7 @@
|
|||
{name}
|
||||
{config}
|
||||
{showModal}
|
||||
{placeholder}
|
||||
on:blur
|
||||
on:change
|
||||
/>
|
||||
|
|
|
@ -5,11 +5,12 @@
|
|||
export let name
|
||||
export let value
|
||||
export let error
|
||||
export let placeholder
|
||||
</script>
|
||||
|
||||
<div class="form-row">
|
||||
<Label>{name}</Label>
|
||||
<TextArea on:blur on:change {type} {value} {error} />
|
||||
<TextArea on:blur on:change {type} {value} {error} {placeholder} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
export let value
|
||||
export let error
|
||||
export let config
|
||||
export let placeholder
|
||||
</script>
|
||||
|
||||
<div class="form-row">
|
||||
|
@ -17,6 +18,7 @@
|
|||
{type}
|
||||
value={value || undefined}
|
||||
{error}
|
||||
{placeholder}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
export let name
|
||||
export let value
|
||||
export let error
|
||||
export let placeholder
|
||||
export let showModal = () => {}
|
||||
|
||||
async function handleUpgradePanel() {
|
||||
|
@ -22,6 +23,7 @@
|
|||
type={type === "port" ? "string" : type}
|
||||
{value}
|
||||
{error}
|
||||
{placeholder}
|
||||
variables={$environment.variables}
|
||||
environmentVariablesEnabled={$licensing.environmentVariablesEnabled}
|
||||
{showModal}
|
||||
|
|
|
@ -85,7 +85,7 @@
|
|||
/>
|
||||
{/if}
|
||||
|
||||
{#each $configStore.validatedConfig as { type, key, value, error, name, hidden, config }}
|
||||
{#each $configStore.validatedConfig as { type, key, value, error, name, hidden, config, placeholder }}
|
||||
{#if hidden === undefined || !eval(processStringSync(hidden, $configStore.config))}
|
||||
<ConfigInput
|
||||
{type}
|
||||
|
@ -93,6 +93,7 @@
|
|||
{error}
|
||||
{name}
|
||||
{config}
|
||||
{placeholder}
|
||||
showModal={() =>
|
||||
showModal(newValue => configStore.updateFieldValue(key, newValue))}
|
||||
on:blur={() => configStore.markFieldActive(key)}
|
||||
|
|
|
@ -114,6 +114,7 @@ export const createValidatedConfigStore = (integration, config) => {
|
|||
value: getValue(),
|
||||
error: $errorsStore[key],
|
||||
name: capitalise(properties.display || key),
|
||||
placeholder: properties.placeholder,
|
||||
type: properties.type,
|
||||
hidden: properties.hidden,
|
||||
config: properties.config,
|
||||
|
|
|
@ -1,30 +1,64 @@
|
|||
import { FieldType } from "@budibase/types"
|
||||
import {
|
||||
FieldSchema,
|
||||
FieldType,
|
||||
SaveTableRequest,
|
||||
Table,
|
||||
} from "@budibase/types"
|
||||
import { SWITCHABLE_TYPES } from "@budibase/shared-core"
|
||||
import { get, writable, derived } from "svelte/store"
|
||||
import { get, derived, Writable } from "svelte/store"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
import { API } from "api"
|
||||
import { DerivedBudiStore } from "stores/BudiStore"
|
||||
|
||||
export function createTablesStore() {
|
||||
const store = writable({
|
||||
list: [],
|
||||
selectedTableId: null,
|
||||
})
|
||||
const derivedStore = derived(store, $store => ({
|
||||
...$store,
|
||||
selected: $store.list?.find(table => table._id === $store.selectedTableId),
|
||||
}))
|
||||
interface BuilderTableStore {
|
||||
list: Table[]
|
||||
selectedTableId?: string
|
||||
}
|
||||
|
||||
const fetch = async () => {
|
||||
interface DerivedTableStore extends BuilderTableStore {
|
||||
selected?: Table
|
||||
}
|
||||
|
||||
export class TableStore extends DerivedBudiStore<
|
||||
BuilderTableStore,
|
||||
DerivedTableStore
|
||||
> {
|
||||
constructor() {
|
||||
const makeDerivedStore = (store: Writable<BuilderTableStore>) => {
|
||||
return derived(store, $store => ({
|
||||
...$store,
|
||||
selected: $store.list?.find(
|
||||
table => table._id === $store.selectedTableId
|
||||
),
|
||||
}))
|
||||
}
|
||||
|
||||
super(
|
||||
{
|
||||
list: [],
|
||||
selectedTableId: undefined,
|
||||
},
|
||||
makeDerivedStore
|
||||
)
|
||||
|
||||
this.select = this.select.bind(this)
|
||||
}
|
||||
|
||||
async init() {
|
||||
return this.fetch()
|
||||
}
|
||||
|
||||
async fetch() {
|
||||
const tables = await API.getTables()
|
||||
store.update(state => ({
|
||||
this.store.update(state => ({
|
||||
...state,
|
||||
list: tables,
|
||||
}))
|
||||
}
|
||||
|
||||
const singleFetch = async tableId => {
|
||||
private async singleFetch(tableId: string) {
|
||||
const table = await API.getTable(tableId)
|
||||
store.update(state => {
|
||||
this.store.update(state => {
|
||||
const list = []
|
||||
// update the list, keep order accurate
|
||||
for (let tbl of state.list) {
|
||||
|
@ -39,16 +73,16 @@ export function createTablesStore() {
|
|||
})
|
||||
}
|
||||
|
||||
const select = tableId => {
|
||||
store.update(state => ({
|
||||
select(tableId: string | undefined) {
|
||||
this.store.update(state => ({
|
||||
...state,
|
||||
selectedTableId: tableId,
|
||||
}))
|
||||
}
|
||||
|
||||
const save = async table => {
|
||||
const updatedTable = cloneDeep(table)
|
||||
const oldTable = get(store).list.filter(t => t._id === table._id)[0]
|
||||
async save(table: Table) {
|
||||
const updatedTable: SaveTableRequest = cloneDeep(table)
|
||||
const oldTable = get(this.store).list.filter(t => t._id === table._id)[0]
|
||||
|
||||
const fieldNames = []
|
||||
// Update any renamed schema keys to reflect their names
|
||||
|
@ -79,8 +113,8 @@ export function createTablesStore() {
|
|||
}
|
||||
|
||||
const savedTable = await API.saveTable(updatedTable)
|
||||
replaceTable(savedTable._id, savedTable)
|
||||
select(savedTable._id)
|
||||
this.replaceTable(savedTable._id, savedTable)
|
||||
this.select(savedTable._id)
|
||||
// make sure tables up to date (related)
|
||||
let newTableIds = []
|
||||
for (let column of Object.values(updatedTable?.schema || {})) {
|
||||
|
@ -99,28 +133,30 @@ export function createTablesStore() {
|
|||
const tableIdsToFetch = [...new Set([...newTableIds, ...oldTableIds])]
|
||||
// too many tables to fetch, just get all
|
||||
if (tableIdsToFetch.length > 3) {
|
||||
await fetch()
|
||||
await this.fetch()
|
||||
} else {
|
||||
await Promise.all(tableIdsToFetch.map(id => singleFetch(id)))
|
||||
await Promise.all(tableIdsToFetch.map(id => this.singleFetch(id)))
|
||||
}
|
||||
return savedTable
|
||||
}
|
||||
|
||||
const deleteTable = async table => {
|
||||
if (!table?._id) {
|
||||
return
|
||||
}
|
||||
await API.deleteTable(table._id, table._rev || "rev")
|
||||
replaceTable(table._id, null)
|
||||
async delete(table: { _id: string; _rev: string }) {
|
||||
await API.deleteTable(table._id, table._rev)
|
||||
this.replaceTable(table._id, null)
|
||||
}
|
||||
|
||||
const saveField = async ({
|
||||
async saveField({
|
||||
originalName,
|
||||
field,
|
||||
primaryDisplay = false,
|
||||
indexes,
|
||||
}) => {
|
||||
let draft = cloneDeep(get(derivedStore).selected)
|
||||
}: {
|
||||
originalName: string
|
||||
field: FieldSchema
|
||||
primaryDisplay: boolean
|
||||
indexes: Record<string, any>
|
||||
}) {
|
||||
const draft: SaveTableRequest = cloneDeep(get(this.derivedStore).selected!)
|
||||
|
||||
// delete the original if renaming
|
||||
// need to handle if the column had no name, empty string
|
||||
|
@ -139,7 +175,7 @@ export function createTablesStore() {
|
|||
const fields = Object.keys(draft.schema)
|
||||
// pick another display column randomly if unselecting
|
||||
draft.primaryDisplay = fields.filter(
|
||||
name => name !== originalName || name !== field
|
||||
name => name !== originalName || name !== field.name
|
||||
)[0]
|
||||
}
|
||||
if (indexes) {
|
||||
|
@ -150,24 +186,24 @@ export function createTablesStore() {
|
|||
[field.name]: cloneDeep(field),
|
||||
}
|
||||
|
||||
await save(draft)
|
||||
await this.save(draft)
|
||||
}
|
||||
|
||||
const deleteField = async field => {
|
||||
let draft = cloneDeep(get(derivedStore).selected)
|
||||
async deleteField(field: { name: string | number }) {
|
||||
let draft = cloneDeep(get(this.derivedStore).selected!)
|
||||
delete draft.schema[field.name]
|
||||
await save(draft)
|
||||
await this.save(draft)
|
||||
}
|
||||
|
||||
// Handles external updates of tables
|
||||
const replaceTable = (tableId, table) => {
|
||||
replaceTable(tableId: string | undefined, table: Table | null) {
|
||||
if (!tableId) {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle deletion
|
||||
if (!table) {
|
||||
store.update(state => ({
|
||||
this.store.update(state => ({
|
||||
...state,
|
||||
list: state.list.filter(x => x._id !== tableId),
|
||||
}))
|
||||
|
@ -175,9 +211,9 @@ export function createTablesStore() {
|
|||
}
|
||||
|
||||
// Add new table
|
||||
const index = get(store).list.findIndex(x => x._id === table._id)
|
||||
const index = get(this.store).list.findIndex(x => x._id === table._id)
|
||||
if (index === -1) {
|
||||
store.update(state => ({
|
||||
this.store.update(state => ({
|
||||
...state,
|
||||
list: [...state.list, table],
|
||||
}))
|
||||
|
@ -188,7 +224,7 @@ export function createTablesStore() {
|
|||
// This function has to merge state as there discrepancies with the table
|
||||
// API endpoints. The table list endpoint and get table endpoint use the
|
||||
// "type" property to mean different things.
|
||||
store.update(state => {
|
||||
this.store.update(state => {
|
||||
state.list[index] = {
|
||||
...table,
|
||||
type: state.list[index].type,
|
||||
|
@ -198,26 +234,12 @@ export function createTablesStore() {
|
|||
}
|
||||
}
|
||||
|
||||
const removeDatasourceTables = datasourceId => {
|
||||
store.update(state => ({
|
||||
removeDatasourceTables(datasourceId: string) {
|
||||
this.store.update(state => ({
|
||||
...state,
|
||||
list: state.list.filter(table => table.sourceId !== datasourceId),
|
||||
}))
|
||||
}
|
||||
|
||||
return {
|
||||
...store,
|
||||
subscribe: derivedStore.subscribe,
|
||||
fetch,
|
||||
init: fetch,
|
||||
select,
|
||||
save,
|
||||
delete: deleteTable,
|
||||
saveField,
|
||||
deleteField,
|
||||
replaceTable,
|
||||
removeDatasourceTables,
|
||||
}
|
||||
}
|
||||
|
||||
export const tables = createTablesStore()
|
||||
export const tables = new TableStore()
|
|
@ -1,102 +0,0 @@
|
|||
import { writable, derived, get } from "svelte/store"
|
||||
import { tables } from "./tables"
|
||||
import { API } from "api"
|
||||
|
||||
export function createViewsV2Store() {
|
||||
const store = writable({
|
||||
selectedViewId: null,
|
||||
})
|
||||
const derivedStore = derived([store, tables], ([$store, $tables]) => {
|
||||
let list = []
|
||||
$tables.list?.forEach(table => {
|
||||
const views = Object.values(table?.views || {}).filter(view => {
|
||||
return view.version === 2
|
||||
})
|
||||
list = list.concat(views)
|
||||
})
|
||||
return {
|
||||
...$store,
|
||||
list,
|
||||
selected: list.find(view => view.id === $store.selectedViewId),
|
||||
}
|
||||
})
|
||||
|
||||
const select = id => {
|
||||
store.update(state => ({
|
||||
...state,
|
||||
selectedViewId: id,
|
||||
}))
|
||||
}
|
||||
|
||||
const deleteView = async view => {
|
||||
await API.viewV2.delete(view.id)
|
||||
replaceView(view.id, null)
|
||||
}
|
||||
|
||||
const create = async view => {
|
||||
const savedViewResponse = await API.viewV2.create(view)
|
||||
const savedView = savedViewResponse.data
|
||||
replaceView(savedView.id, savedView)
|
||||
return savedView
|
||||
}
|
||||
|
||||
const save = async view => {
|
||||
const res = await API.viewV2.update(view)
|
||||
const savedView = res?.data
|
||||
replaceView(view.id, savedView)
|
||||
}
|
||||
|
||||
// Handles external updates of tables
|
||||
const replaceView = (viewId, view) => {
|
||||
if (!viewId) {
|
||||
return
|
||||
}
|
||||
const existingView = get(derivedStore).list.find(view => view.id === viewId)
|
||||
const tableIndex = get(tables).list.findIndex(table => {
|
||||
return table._id === view?.tableId || table._id === existingView?.tableId
|
||||
})
|
||||
if (tableIndex === -1) {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle deletion
|
||||
if (!view) {
|
||||
tables.update(state => {
|
||||
delete state.list[tableIndex].views[existingView.name]
|
||||
return state
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Add new view
|
||||
if (!existingView) {
|
||||
tables.update(state => {
|
||||
state.list[tableIndex].views[view.name] = view
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
// Update existing view
|
||||
else {
|
||||
tables.update(state => {
|
||||
// Remove old view
|
||||
delete state.list[tableIndex].views[existingView.name]
|
||||
|
||||
// Add new view
|
||||
state.list[tableIndex].views[view.name] = view
|
||||
return state
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: derivedStore.subscribe,
|
||||
select,
|
||||
delete: deleteView,
|
||||
create,
|
||||
save,
|
||||
replaceView,
|
||||
}
|
||||
}
|
||||
|
||||
export const viewsV2 = createViewsV2Store()
|
|
@ -0,0 +1,122 @@
|
|||
import { derived, get, Writable } from "svelte/store"
|
||||
import { tables } from "./tables"
|
||||
import { API } from "api"
|
||||
import { DerivedBudiStore } from "stores/BudiStore"
|
||||
import { CreateViewRequest, UpdateViewRequest, ViewV2 } from "@budibase/types"
|
||||
import { helpers } from "@budibase/shared-core"
|
||||
|
||||
interface BuilderViewV2Store {
|
||||
selectedViewId: string | null
|
||||
}
|
||||
|
||||
interface DerivedViewV2Store extends BuilderViewV2Store {
|
||||
list: ViewV2[]
|
||||
selected?: ViewV2
|
||||
}
|
||||
|
||||
export class ViewV2Store extends DerivedBudiStore<
|
||||
BuilderViewV2Store,
|
||||
DerivedViewV2Store
|
||||
> {
|
||||
constructor() {
|
||||
const makeDerivedStore = (store: Writable<BuilderViewV2Store>) => {
|
||||
return derived(
|
||||
[store, tables],
|
||||
([$store, $tables]): DerivedViewV2Store => {
|
||||
let list: ViewV2[] = []
|
||||
$tables.list?.forEach(table => {
|
||||
const views = Object.values(table?.views || {}).filter(
|
||||
helpers.views.isV2
|
||||
)
|
||||
list = list.concat(views)
|
||||
})
|
||||
return {
|
||||
...$store,
|
||||
list,
|
||||
selected: list.find(view => view.id === $store.selectedViewId),
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
super(
|
||||
{
|
||||
selectedViewId: null,
|
||||
},
|
||||
makeDerivedStore
|
||||
)
|
||||
|
||||
this.select = this.select.bind(this)
|
||||
}
|
||||
|
||||
select(id: string) {
|
||||
this.store.update(state => ({
|
||||
...state,
|
||||
selectedViewId: id,
|
||||
}))
|
||||
}
|
||||
|
||||
async delete(view: { id: string }) {
|
||||
await API.viewV2.delete(view.id)
|
||||
this.replaceView(view.id, null)
|
||||
}
|
||||
|
||||
async create(view: CreateViewRequest) {
|
||||
const savedViewResponse = await API.viewV2.create(view)
|
||||
const savedView = savedViewResponse.data
|
||||
this.replaceView(savedView.id, savedView)
|
||||
return savedView
|
||||
}
|
||||
|
||||
async save(view: UpdateViewRequest) {
|
||||
const res = await API.viewV2.update(view)
|
||||
const savedView = res?.data
|
||||
this.replaceView(view.id, savedView)
|
||||
}
|
||||
|
||||
// Handles external updates of tables
|
||||
replaceView(viewId: string, view: ViewV2 | null) {
|
||||
const existingView = get(this.derivedStore).list.find(
|
||||
view => view.id === viewId
|
||||
)
|
||||
const tableIndex = get(tables).list.findIndex(table => {
|
||||
return table._id === view?.tableId || table._id === existingView?.tableId
|
||||
})
|
||||
if (tableIndex === -1) {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle deletion
|
||||
if (!view && existingView) {
|
||||
tables.update(state => {
|
||||
delete state.list[tableIndex].views![existingView.name]
|
||||
return state
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Add new view
|
||||
else if (!existingView && view) {
|
||||
tables.update(state => {
|
||||
state.list[tableIndex].views ??= {}
|
||||
state.list[tableIndex].views[view.name] = view
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
// Update existing view
|
||||
else if (existingView && view) {
|
||||
tables.update(state => {
|
||||
// Remove old view
|
||||
state.list[tableIndex].views ??= {}
|
||||
delete state.list[tableIndex].views[existingView.name]
|
||||
|
||||
// Add new view
|
||||
state.list[tableIndex].views[view.name] = view
|
||||
return state
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const viewsV2 = new ViewV2Store()
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"extends": "./tsconfig.build.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"assets/*": ["./assets/*"],
|
||||
|
|
|
@ -1,8 +1,25 @@
|
|||
import { derived, get, writable } from "svelte/store"
|
||||
import { derived, get, Readable, Writable, writable } from "svelte/store"
|
||||
import { DefaultColumnWidth, GutterWidth } from "../lib/constants"
|
||||
import { UIColumn } from "@budibase/types"
|
||||
import { Store as StoreContext } from "."
|
||||
|
||||
export const createStores = () => {
|
||||
const columns = writable([])
|
||||
interface ColumnStore {
|
||||
columns: Writable<UIColumn[]>
|
||||
}
|
||||
|
||||
interface DerivedColumnStore {
|
||||
tableColumns: Readable<UIColumn[]>
|
||||
displayColumn: Readable<UIColumn | undefined>
|
||||
columnLookupMap: Readable<Record<string, UIColumn>>
|
||||
visibleColumns: Readable<UIColumn[]>
|
||||
scrollableColumns: Readable<UIColumn[]>
|
||||
hasNonAutoColumn: Readable<boolean>
|
||||
}
|
||||
|
||||
export type Store = ColumnStore & DerivedColumnStore
|
||||
|
||||
export const createStores = (): ColumnStore => {
|
||||
const columns = writable<UIColumn[]>([])
|
||||
|
||||
// Enrich columns with metadata about their display position
|
||||
const enrichedColumns = derived(columns, $columns => {
|
||||
|
@ -16,7 +33,7 @@ export const createStores = () => {
|
|||
}
|
||||
if (col.visible) {
|
||||
idx++
|
||||
offset += col.width
|
||||
offset += col.width ?? 0
|
||||
}
|
||||
return enriched
|
||||
})
|
||||
|
@ -30,12 +47,12 @@ export const createStores = () => {
|
|||
}
|
||||
}
|
||||
|
||||
export const deriveStores = context => {
|
||||
export const deriveStores = (context: StoreContext): DerivedColumnStore => {
|
||||
const { columns } = context
|
||||
|
||||
// Derive a lookup map for all columns by name
|
||||
const columnLookupMap = derived(columns, $columns => {
|
||||
let map = {}
|
||||
let map: Record<string, UIColumn> = {}
|
||||
$columns.forEach(column => {
|
||||
map[column.name] = column
|
||||
})
|
||||
|
@ -78,11 +95,11 @@ export const deriveStores = context => {
|
|||
}
|
||||
}
|
||||
|
||||
export const createActions = context => {
|
||||
export const createActions = (context: StoreContext) => {
|
||||
const { columns, datasource } = context
|
||||
|
||||
// Updates the width of all columns
|
||||
const changeAllColumnWidths = async width => {
|
||||
const changeAllColumnWidths = async (width: number) => {
|
||||
const $columns = get(columns)
|
||||
$columns.forEach(column => {
|
||||
const { related } = column
|
||||
|
@ -101,7 +118,7 @@ export const createActions = context => {
|
|||
}
|
||||
|
||||
// Checks if a column is readonly
|
||||
const isReadonly = column => {
|
||||
const isReadonly = (column: UIColumn) => {
|
||||
if (!column?.schema) {
|
||||
return false
|
||||
}
|
||||
|
@ -125,11 +142,11 @@ export const createActions = context => {
|
|||
}
|
||||
}
|
||||
|
||||
export const initialise = context => {
|
||||
export const initialise = (context: StoreContext) => {
|
||||
const { definition, columns, displayColumn, enrichedSchema } = context
|
||||
|
||||
// Merge new schema fields with existing schema in order to preserve widths
|
||||
const processColumns = $enrichedSchema => {
|
||||
const processColumns = ($enrichedSchema: any) => {
|
||||
if (!$enrichedSchema) {
|
||||
columns.set([])
|
||||
return
|
||||
|
@ -139,7 +156,7 @@ export const initialise = context => {
|
|||
const $displayColumn = get(displayColumn)
|
||||
|
||||
// Find primary display
|
||||
let primaryDisplay
|
||||
let primaryDisplay: string
|
||||
const candidatePD = $definition.primaryDisplay || $displayColumn?.name
|
||||
if (candidatePD && $enrichedSchema[candidatePD]) {
|
||||
primaryDisplay = candidatePD
|
||||
|
@ -151,7 +168,8 @@ export const initialise = context => {
|
|||
.map(field => {
|
||||
const fieldSchema = $enrichedSchema[field]
|
||||
const oldColumn = $columns?.find(col => col.name === field)
|
||||
const column = {
|
||||
const column: UIColumn = {
|
||||
type: fieldSchema.type,
|
||||
name: field,
|
||||
label: fieldSchema.displayName || field,
|
||||
schema: fieldSchema,
|
|
@ -1,7 +1,24 @@
|
|||
import { SortOrder } from "@budibase/types"
|
||||
import { SortOrder, UIDatasource } from "@budibase/types"
|
||||
import { get } from "svelte/store"
|
||||
import { Store as StoreContext } from ".."
|
||||
|
||||
export const createActions = context => {
|
||||
interface NonPlusActions {
|
||||
nonPlus: {
|
||||
actions: {
|
||||
saveDefinition: () => Promise<void>
|
||||
addRow: () => Promise<void>
|
||||
updateRow: () => Promise<void>
|
||||
deleteRows: () => Promise<void>
|
||||
getRow: () => Promise<void>
|
||||
isDatasourceValid: (datasource: UIDatasource) => boolean
|
||||
canUseColumn: (name: string) => boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type Store = NonPlusActions
|
||||
|
||||
export const createActions = (context: StoreContext): NonPlusActions => {
|
||||
const { columns, table, viewV2 } = context
|
||||
|
||||
const saveDefinition = async () => {
|
||||
|
@ -20,7 +37,7 @@ export const createActions = context => {
|
|||
throw "This datasource does not support fetching individual rows"
|
||||
}
|
||||
|
||||
const isDatasourceValid = datasource => {
|
||||
const isDatasourceValid = (datasource: UIDatasource) => {
|
||||
// There are many different types and shapes of datasource, so we only
|
||||
// check that we aren't null
|
||||
return (
|
||||
|
@ -30,7 +47,7 @@ export const createActions = context => {
|
|||
)
|
||||
}
|
||||
|
||||
const canUseColumn = name => {
|
||||
const canUseColumn = (name: string) => {
|
||||
return get(columns).some(col => col.name === name)
|
||||
}
|
||||
|
||||
|
@ -50,11 +67,11 @@ export const createActions = context => {
|
|||
}
|
||||
|
||||
// Small util to compare datasource definitions
|
||||
const isSameDatasource = (a, b) => {
|
||||
const isSameDatasource = (a: any, b: any) => {
|
||||
return JSON.stringify(a) === JSON.stringify(b)
|
||||
}
|
||||
|
||||
export const initialise = context => {
|
||||
export const initialise = (context: StoreContext) => {
|
||||
const {
|
||||
datasource,
|
||||
sort,
|
||||
|
@ -69,7 +86,7 @@ export const initialise = context => {
|
|||
} = context
|
||||
// Keep a list of subscriptions so that we can clear them when the datasource
|
||||
// config changes
|
||||
let unsubscribers = []
|
||||
let unsubscribers: any[] = []
|
||||
|
||||
// Observe datasource changes and apply logic for view V2 datasources
|
||||
datasource.subscribe($datasource => {
|
|
@ -1,16 +1,40 @@
|
|||
import { SortOrder } from "@budibase/types"
|
||||
import {
|
||||
Row,
|
||||
SaveRowRequest,
|
||||
SaveRowResponse,
|
||||
SaveTableRequest,
|
||||
SortOrder,
|
||||
UIDatasource,
|
||||
} from "@budibase/types"
|
||||
import { get } from "svelte/store"
|
||||
import { Store as StoreContext } from ".."
|
||||
|
||||
const SuppressErrors = true
|
||||
|
||||
export const createActions = context => {
|
||||
interface TableActions {
|
||||
table: {
|
||||
actions: {
|
||||
saveDefinition: (newDefinition: SaveTableRequest) => Promise<void>
|
||||
addRow: (row: SaveRowRequest) => Promise<SaveRowResponse>
|
||||
updateRow: (row: SaveRowRequest) => Promise<SaveRowResponse>
|
||||
deleteRows: (rows: (string | Row)[]) => Promise<void>
|
||||
getRow: (id: string) => Promise<Row>
|
||||
isDatasourceValid: (datasource: UIDatasource) => boolean
|
||||
canUseColumn: (name: string) => boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type Store = TableActions
|
||||
|
||||
export const createActions = (context: StoreContext): TableActions => {
|
||||
const { API, datasource, columns } = context
|
||||
|
||||
const saveDefinition = async newDefinition => {
|
||||
const saveDefinition = async (newDefinition: SaveTableRequest) => {
|
||||
await API.saveTable(newDefinition)
|
||||
}
|
||||
|
||||
const saveRow = async row => {
|
||||
const saveRow = async (row: SaveRowRequest) => {
|
||||
row = {
|
||||
...row,
|
||||
tableId: get(datasource)?.tableId,
|
||||
|
@ -18,15 +42,15 @@ export const createActions = context => {
|
|||
return await API.saveRow(row, SuppressErrors)
|
||||
}
|
||||
|
||||
const deleteRows = async rows => {
|
||||
const deleteRows = async (rows: (string | Row)[]) => {
|
||||
await API.deleteRows(get(datasource).tableId, rows)
|
||||
}
|
||||
|
||||
const isDatasourceValid = datasource => {
|
||||
return datasource?.type === "table" && datasource?.tableId
|
||||
const isDatasourceValid = (datasource: UIDatasource) => {
|
||||
return datasource?.type === "table" && !!datasource?.tableId
|
||||
}
|
||||
|
||||
const getRow = async id => {
|
||||
const getRow = async (id: any) => {
|
||||
const res = await API.searchTable(get(datasource).tableId, {
|
||||
limit: 1,
|
||||
query: {
|
||||
|
@ -39,7 +63,7 @@ export const createActions = context => {
|
|||
return res?.rows?.[0]
|
||||
}
|
||||
|
||||
const canUseColumn = name => {
|
||||
const canUseColumn = (name: string) => {
|
||||
return get(columns).some(col => col.name === name)
|
||||
}
|
||||
|
||||
|
@ -58,7 +82,7 @@ export const createActions = context => {
|
|||
}
|
||||
}
|
||||
|
||||
export const initialise = context => {
|
||||
export const initialise = (context: StoreContext) => {
|
||||
const {
|
||||
datasource,
|
||||
fetch,
|
||||
|
@ -74,7 +98,7 @@ export const initialise = context => {
|
|||
|
||||
// Keep a list of subscriptions so that we can clear them when the datasource
|
||||
// config changes
|
||||
let unsubscribers = []
|
||||
let unsubscribers: any[] = []
|
||||
|
||||
// Observe datasource changes and apply logic for table datasources
|
||||
datasource.subscribe($datasource => {
|
|
@ -1,16 +1,39 @@
|
|||
import { get } from "svelte/store"
|
||||
import { SortOrder } from "@budibase/types"
|
||||
import {
|
||||
Row,
|
||||
SaveRowRequest,
|
||||
SortOrder,
|
||||
UIDatasource,
|
||||
UpdateViewRequest,
|
||||
} from "@budibase/types"
|
||||
import { Store as StoreContext } from ".."
|
||||
|
||||
const SuppressErrors = true
|
||||
|
||||
export const createActions = context => {
|
||||
interface ViewActions {
|
||||
viewV2: {
|
||||
actions: {
|
||||
saveDefinition: (newDefinition: UpdateViewRequest) => Promise<void>
|
||||
addRow: (row: SaveRowRequest) => Promise<Row>
|
||||
updateRow: (row: SaveRowRequest) => Promise<Row>
|
||||
deleteRows: (rows: (string | Row)[]) => Promise<void>
|
||||
getRow: (id: string) => Promise<Row>
|
||||
isDatasourceValid: (datasource: UIDatasource) => boolean
|
||||
canUseColumn: (name: string) => boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type Store = ViewActions
|
||||
|
||||
export const createActions = (context: StoreContext): ViewActions => {
|
||||
const { API, datasource, columns } = context
|
||||
|
||||
const saveDefinition = async newDefinition => {
|
||||
const saveDefinition = async (newDefinition: UpdateViewRequest) => {
|
||||
await API.viewV2.update(newDefinition)
|
||||
}
|
||||
|
||||
const saveRow = async row => {
|
||||
const saveRow = async (row: SaveRowRequest) => {
|
||||
const $datasource = get(datasource)
|
||||
row = {
|
||||
...row,
|
||||
|
@ -23,11 +46,11 @@ export const createActions = context => {
|
|||
}
|
||||
}
|
||||
|
||||
const deleteRows = async rows => {
|
||||
const deleteRows = async (rows: (string | Row)[]) => {
|
||||
await API.deleteRows(get(datasource).id, rows)
|
||||
}
|
||||
|
||||
const getRow = async id => {
|
||||
const getRow = async (id: string) => {
|
||||
const res = await API.viewV2.fetch(get(datasource).id, {
|
||||
limit: 1,
|
||||
query: {
|
||||
|
@ -40,13 +63,13 @@ export const createActions = context => {
|
|||
return res?.rows?.[0]
|
||||
}
|
||||
|
||||
const isDatasourceValid = datasource => {
|
||||
const isDatasourceValid = (datasource: UIDatasource) => {
|
||||
return (
|
||||
datasource?.type === "viewV2" && datasource?.id && datasource?.tableId
|
||||
datasource?.type === "viewV2" && !!datasource?.id && !!datasource?.tableId
|
||||
)
|
||||
}
|
||||
|
||||
const canUseColumn = name => {
|
||||
const canUseColumn = (name: string) => {
|
||||
return get(columns).some(col => col.name === name && col.visible)
|
||||
}
|
||||
|
||||
|
@ -65,7 +88,7 @@ export const createActions = context => {
|
|||
}
|
||||
}
|
||||
|
||||
export const initialise = context => {
|
||||
export const initialise = (context: StoreContext) => {
|
||||
const {
|
||||
definition,
|
||||
datasource,
|
||||
|
@ -85,7 +108,7 @@ export const initialise = context => {
|
|||
|
||||
// Keep a list of subscriptions so that we can clear them when the datasource
|
||||
// config changes
|
||||
let unsubscribers = []
|
||||
let unsubscribers: any[] = []
|
||||
|
||||
// Observe datasource changes and apply logic for view V2 datasources
|
||||
datasource.subscribe($datasource => {
|
|
@ -1,3 +1,6 @@
|
|||
import { Writable } from "svelte/store"
|
||||
import type { APIClient } from "../../../api/types"
|
||||
|
||||
import * as Bounds from "./bounds"
|
||||
import * as Columns from "./columns"
|
||||
import * as Menu from "./menu"
|
||||
|
@ -42,31 +45,65 @@ const DependencyOrderedStores = [
|
|||
Users,
|
||||
Menu,
|
||||
Pagination,
|
||||
Config,
|
||||
Config as any,
|
||||
Clipboard,
|
||||
Notifications,
|
||||
Cache,
|
||||
]
|
||||
|
||||
export const attachStores = context => {
|
||||
export interface BaseStore {
|
||||
API: APIClient
|
||||
}
|
||||
|
||||
export type Store = BaseStore &
|
||||
Columns.Store &
|
||||
Table.Store &
|
||||
ViewV2.Store &
|
||||
NonPlus.Store & {
|
||||
// TODO while typing the rest of stores
|
||||
datasource: Writable<any> & { actions: any }
|
||||
definition: Writable<any>
|
||||
enrichedSchema: any
|
||||
fetch: Writable<any>
|
||||
filter: Writable<any>
|
||||
inlineFilters: Writable<any>
|
||||
allFilters: Writable<any>
|
||||
sort: Writable<any>
|
||||
initialFilter: Writable<any>
|
||||
initialSortColumn: Writable<any>
|
||||
initialSortOrder: Writable<any>
|
||||
rows: Writable<any> & { actions: any }
|
||||
subscribe: any
|
||||
config: Writable<any>
|
||||
}
|
||||
|
||||
export const attachStores = (context: Store): Store => {
|
||||
// Atomic store creation
|
||||
for (let store of DependencyOrderedStores) {
|
||||
context = { ...context, ...store.createStores?.(context) }
|
||||
if ("createStores" in store) {
|
||||
context = { ...context, ...store.createStores?.(context) }
|
||||
}
|
||||
}
|
||||
|
||||
// Derived store creation
|
||||
for (let store of DependencyOrderedStores) {
|
||||
context = { ...context, ...store.deriveStores?.(context) }
|
||||
if ("deriveStores" in store) {
|
||||
context = { ...context, ...store.deriveStores?.(context) }
|
||||
}
|
||||
}
|
||||
|
||||
// Action creation
|
||||
for (let store of DependencyOrderedStores) {
|
||||
context = { ...context, ...store.createActions?.(context) }
|
||||
if ("createActions" in store) {
|
||||
context = { ...context, ...store.createActions?.(context) }
|
||||
}
|
||||
}
|
||||
|
||||
// Initialise any store logic
|
||||
for (let store of DependencyOrderedStores) {
|
||||
store.initialise?.(context)
|
||||
if ("initialise" in store) {
|
||||
store.initialise?.(context)
|
||||
}
|
||||
}
|
||||
|
||||
return context
|
|
@ -1 +1 @@
|
|||
Subproject commit 7fc699463b3957eb050351b983edef0d25a531ae
|
||||
Subproject commit ae786121d923449b0ad5fcbd123d0a9fec28f65e
|
|
@ -37,6 +37,7 @@ import { jsonFromCsvString } from "../../../utilities/csv"
|
|||
import { builderSocket } from "../../../websockets"
|
||||
import { cloneDeep } from "lodash"
|
||||
import {
|
||||
canBeDisplayColumn,
|
||||
helpers,
|
||||
PROTECTED_EXTERNAL_COLUMNS,
|
||||
PROTECTED_INTERNAL_COLUMNS,
|
||||
|
@ -67,6 +68,27 @@ function checkDefaultFields(table: Table) {
|
|||
}
|
||||
}
|
||||
|
||||
async function guardTable(table: Table, isCreate: boolean) {
|
||||
checkDefaultFields(table)
|
||||
|
||||
if (
|
||||
table.primaryDisplay &&
|
||||
!canBeDisplayColumn(table.schema[table.primaryDisplay]?.type)
|
||||
) {
|
||||
// Prevent throwing errors from existing badly configured tables. Only throw for new tables or if this setting is being updated
|
||||
if (
|
||||
isCreate ||
|
||||
(await sdk.tables.getTable(table._id!)).primaryDisplay !==
|
||||
table.primaryDisplay
|
||||
) {
|
||||
throw new HTTPError(
|
||||
`Column "${table.primaryDisplay}" cannot be used as a display type.`,
|
||||
400
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// covers both internal and external
|
||||
export async function fetch(ctx: UserCtx<void, FetchTablesResponse>) {
|
||||
const internal = await sdk.tables.getAllInternalTables()
|
||||
|
@ -111,7 +133,7 @@ export async function save(ctx: UserCtx<SaveTableRequest, SaveTableResponse>) {
|
|||
|
||||
const isCreate = !table._id
|
||||
|
||||
checkDefaultFields(table)
|
||||
await guardTable(table, isCreate)
|
||||
|
||||
let savedTable: Table
|
||||
if (isCreate) {
|
||||
|
|
|
@ -3399,7 +3399,7 @@ if (descriptions.length) {
|
|||
type: FieldType.LINK,
|
||||
relationshipType: RelationshipType.MANY_TO_ONE,
|
||||
tableId: toRelateTableId,
|
||||
fieldName: "link",
|
||||
fieldName: "main",
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -3408,7 +3408,7 @@ if (descriptions.length) {
|
|||
)
|
||||
await config.api.table.save({
|
||||
...toRelateTable,
|
||||
primaryDisplay: "link",
|
||||
primaryDisplay: "name",
|
||||
})
|
||||
const relatedRows = await Promise.all([
|
||||
config.api.row.save(toRelateTable._id!, {
|
||||
|
|
|
@ -185,6 +185,62 @@ if (descriptions.length) {
|
|||
)
|
||||
}
|
||||
)
|
||||
|
||||
it("can set primary display", async () => {
|
||||
const columnName = generator.word()
|
||||
const table = await config.api.table.save(
|
||||
tableForDatasource(datasource, {
|
||||
primaryDisplay: columnName,
|
||||
schema: {
|
||||
[columnName]: {
|
||||
name: columnName,
|
||||
type: FieldType.STRING,
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
expect(table.primaryDisplay).toEqual(columnName)
|
||||
|
||||
const res = await config.api.table.get(table._id!)
|
||||
expect(res.primaryDisplay).toEqual(columnName)
|
||||
})
|
||||
|
||||
it("cannot use unexisting columns as primary display", async () => {
|
||||
const columnName = generator.word()
|
||||
await config.api.table.save(
|
||||
tableForDatasource(datasource, {
|
||||
primaryDisplay: columnName,
|
||||
}),
|
||||
{
|
||||
status: 400,
|
||||
body: {
|
||||
message: `Column "${columnName}" cannot be used as a display type.`,
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("cannot use invalid column types as display name", async () => {
|
||||
const columnName = generator.word()
|
||||
|
||||
await config.api.table.save(
|
||||
tableForDatasource(datasource, {
|
||||
primaryDisplay: columnName,
|
||||
schema: {
|
||||
[columnName]: {
|
||||
name: columnName,
|
||||
type: FieldType.BOOLEAN,
|
||||
},
|
||||
},
|
||||
}),
|
||||
{
|
||||
status: 400,
|
||||
body: {
|
||||
message: `Column "${columnName}" cannot be used as a display type.`,
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("permissions", () => {
|
||||
|
@ -603,6 +659,49 @@ if (descriptions.length) {
|
|||
}
|
||||
expect(response).toEqual(expectedResponse)
|
||||
})
|
||||
|
||||
it("cannot use unexisting columns as primary display", async () => {
|
||||
const table = await config.api.table.save(
|
||||
tableForDatasource(datasource)
|
||||
)
|
||||
|
||||
const columnName = generator.word()
|
||||
const tableRequest = {
|
||||
...table,
|
||||
primaryDisplay: columnName,
|
||||
}
|
||||
await config.api.table.save(tableRequest, {
|
||||
status: 400,
|
||||
body: {
|
||||
message: `Column "${columnName}" cannot be used as a display type.`,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("cannot use invalid column types as display name", async () => {
|
||||
const table = await config.api.table.save(
|
||||
tableForDatasource(datasource)
|
||||
)
|
||||
const columnName = generator.word()
|
||||
const tableRequest: SaveTableRequest = {
|
||||
...table,
|
||||
primaryDisplay: columnName,
|
||||
schema: {
|
||||
...table.schema,
|
||||
[columnName]: {
|
||||
name: columnName,
|
||||
type: FieldType.BOOLEAN,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
await config.api.table.save(tableRequest, {
|
||||
status: 400,
|
||||
body: {
|
||||
message: `Column "${columnName}" cannot be used as a display type.`,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("import", () => {
|
||||
|
|
|
@ -12,6 +12,7 @@ interface FirebaseConfig {
|
|||
email: string
|
||||
privateKey: string
|
||||
projectId: string
|
||||
databaseId?: string
|
||||
}
|
||||
|
||||
const SCHEMA: Integration = {
|
||||
|
@ -30,12 +31,21 @@ const SCHEMA: Integration = {
|
|||
},
|
||||
privateKey: {
|
||||
type: DatasourceFieldType.STRING,
|
||||
display: "Private Key",
|
||||
required: true,
|
||||
},
|
||||
projectId: {
|
||||
type: DatasourceFieldType.STRING,
|
||||
display: "Project ID",
|
||||
required: true,
|
||||
},
|
||||
databaseId: {
|
||||
type: DatasourceFieldType.STRING,
|
||||
display: "Database ID",
|
||||
required: false,
|
||||
default: "(default)",
|
||||
placeholder: "(default)",
|
||||
},
|
||||
},
|
||||
query: {
|
||||
create: {
|
||||
|
@ -97,6 +107,7 @@ class FirebaseIntegration implements IntegrationBase {
|
|||
this.config = config
|
||||
this.client = new Firestore({
|
||||
projectId: config.projectId,
|
||||
databaseId: config.databaseId || "(default)",
|
||||
credentials: {
|
||||
client_email: config.email,
|
||||
private_key: config.privateKey?.replace(/\\n/g, "\n"),
|
||||
|
|
|
@ -324,8 +324,8 @@ export async function update(
|
|||
return pickApi(tableId).update(tableId, view)
|
||||
}
|
||||
|
||||
export function isV2(view: View | ViewV2): view is ViewV2 {
|
||||
return (view as ViewV2).version === 2
|
||||
export function isV2(view: View | ViewV2) {
|
||||
return helpers.views.isV2(view)
|
||||
}
|
||||
|
||||
export async function remove(viewId: string): Promise<ViewV2> {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
BasicViewFieldMetadata,
|
||||
View,
|
||||
ViewCalculationFieldMetadata,
|
||||
ViewFieldMetadata,
|
||||
ViewV2,
|
||||
|
@ -43,3 +44,7 @@ export function basicFields(view: UnsavedViewV2, opts?: { visible?: boolean }) {
|
|||
return !isCalculationField(field) && (!visible || isVisible(field))
|
||||
})
|
||||
}
|
||||
|
||||
export function isV2(view: View | ViewV2): view is ViewV2 {
|
||||
return (view as ViewV2).version === 2
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@ const allowDisplayColumnByType: Record<FieldType, boolean> = {
|
|||
[FieldType.AUTO]: true,
|
||||
[FieldType.INTERNAL]: true,
|
||||
[FieldType.BARCODEQR]: true,
|
||||
|
||||
[FieldType.BIGINT]: true,
|
||||
|
||||
[FieldType.BOOLEAN]: false,
|
||||
[FieldType.ARRAY]: false,
|
||||
[FieldType.ATTACHMENTS]: false,
|
||||
|
|
|
@ -119,6 +119,7 @@ interface DatasourceBasicFieldConfig {
|
|||
default?: any
|
||||
deprecated?: boolean
|
||||
hidden?: string
|
||||
placeholder?: string
|
||||
}
|
||||
|
||||
interface DatasourceSelectFieldConfig extends DatasourceBasicFieldConfig {
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import { CalculationType, FieldSchema, FieldType } from "@budibase/types"
|
||||
|
||||
export type UIColumn = FieldSchema & {
|
||||
label: string
|
||||
readonly: boolean
|
||||
conditions: any
|
||||
related?: {
|
||||
field: string
|
||||
subField: string
|
||||
}
|
||||
primaryDisplay?: boolean
|
||||
schema?: {
|
||||
disabled: boolean
|
||||
type: FieldType
|
||||
readonly: boolean
|
||||
autocolumn: boolean
|
||||
}
|
||||
calculationType: CalculationType
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export interface UIDatasource {
|
||||
type: string
|
||||
id: string
|
||||
tableId: string
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./columns"
|
||||
export * from "./datasource"
|
|
@ -1,2 +1,3 @@
|
|||
export * from "./integration"
|
||||
export * from "./automations"
|
||||
export * from "./grid"
|
||||
|
|
Loading…
Reference in New Issue