Merge branch 'views-v2-frontend' of github.com:Budibase/budibase into views-v2-frontend
This commit is contained in:
commit
a24e1809b6
|
@ -734,9 +734,19 @@ export const getSchemaForDatasource = (asset, datasource, options) => {
|
||||||
// Determine the schema from the backing entity if not already determined
|
// Determine the schema from the backing entity if not already determined
|
||||||
if (table && !schema) {
|
if (table && !schema) {
|
||||||
if (type === "view") {
|
if (type === "view") {
|
||||||
// For views, the schema is pulled from the `views` property of the
|
// Old views
|
||||||
// table
|
|
||||||
schema = cloneDeep(table.views?.[datasource.name]?.schema)
|
schema = cloneDeep(table.views?.[datasource.name]?.schema)
|
||||||
|
} else if (type === "viewV2") {
|
||||||
|
// New views which are DS+
|
||||||
|
const view = table.views?.[datasource.name]
|
||||||
|
schema = cloneDeep(view?.schema)
|
||||||
|
|
||||||
|
// Strip hidden fields
|
||||||
|
Object.keys(schema || {}).forEach(field => {
|
||||||
|
if (!schema[field].visible) {
|
||||||
|
delete schema[field]
|
||||||
|
}
|
||||||
|
})
|
||||||
} else if (
|
} else if (
|
||||||
type === "query" &&
|
type === "query" &&
|
||||||
(options.formSchema || options.searchableSchema)
|
(options.formSchema || options.searchableSchema)
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleGridViewUpdate = async e => {
|
const handleGridViewUpdate = async e => {
|
||||||
viewsV2.replace(id, e.detail)
|
viewsV2.replaceView(id, e.detail)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import { Modal, ActionButton } from "@budibase/bbui"
|
import { Modal, ActionButton } from "@budibase/bbui"
|
||||||
import CreateViewModal from "../../modals/CreateViewModal.svelte"
|
import GridCreateViewModal from "../../modals/grid/GridCreateViewModal.svelte"
|
||||||
|
|
||||||
const { rows, columns } = getContext("grid")
|
const { rows, columns } = getContext("grid")
|
||||||
|
|
||||||
|
@ -14,5 +14,5 @@
|
||||||
Add view
|
Add view
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
<CreateViewModal />
|
<GridCreateViewModal />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -4,9 +4,6 @@
|
||||||
|
|
||||||
const { columns, datasource, filter, definition } = getContext("grid")
|
const { columns, datasource, filter, definition } = getContext("grid")
|
||||||
|
|
||||||
// Wipe filter whenever table ID changes to avoid using stale filters
|
|
||||||
$: $datasource, filter.set([])
|
|
||||||
|
|
||||||
const onFilter = e => {
|
const onFilter = e => {
|
||||||
filter.set(e.detail || [])
|
filter.set(e.detail || [])
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,12 @@
|
||||||
import { Input, notifications, ModalContent } from "@budibase/bbui"
|
import { Input, notifications, ModalContent } from "@budibase/bbui"
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import { viewsV2 } from "stores/backend"
|
import { viewsV2 } from "stores/backend"
|
||||||
import { LuceneUtils } from "@budibase/frontend-core"
|
|
||||||
|
|
||||||
const { filter, sort, table } = getContext("grid")
|
const { filter, sort, definition } = getContext("grid")
|
||||||
|
|
||||||
$: query = LuceneUtils.buildLuceneQuery($filter)
|
|
||||||
|
|
||||||
let name
|
let name
|
||||||
|
|
||||||
$: console.log($table)
|
$: views = Object.keys($definition?.views || {})
|
||||||
$: views = Object.keys($table?.views || {})
|
|
||||||
$: nameExists = views.includes(name?.trim())
|
$: nameExists = views.includes(name?.trim())
|
||||||
|
|
||||||
const saveView = async () => {
|
const saveView = async () => {
|
||||||
|
@ -20,14 +16,14 @@
|
||||||
try {
|
try {
|
||||||
const newView = await viewsV2.create({
|
const newView = await viewsV2.create({
|
||||||
name,
|
name,
|
||||||
tableId: $table._id,
|
tableId: $definition._id,
|
||||||
query,
|
query: $filter,
|
||||||
sort: {
|
sort: {
|
||||||
field: $sort.column,
|
field: $sort.column,
|
||||||
order: $sort.order,
|
order: $sort.order,
|
||||||
},
|
},
|
||||||
schema: $table.schema,
|
schema: $definition.schema,
|
||||||
primaryDisplay: $table.primaryDisplay,
|
primaryDisplay: $definition.primaryDisplay,
|
||||||
})
|
})
|
||||||
notifications.success(`View ${name} created`)
|
notifications.success(`View ${name} created`)
|
||||||
$goto(`../../view/v2/${newView.id}`)
|
$goto(`../../view/v2/${newView.id}`)
|
|
@ -58,7 +58,8 @@
|
||||||
$goto(`./view/v1/${encodeURIComponent(name)}`)
|
$goto(`./view/v1/${encodeURIComponent(name)}`)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
selectedBy={$userSelectedResourceMap[name]}
|
selectedBy={$userSelectedResourceMap[name] ||
|
||||||
|
$userSelectedResourceMap[view.id]}
|
||||||
>
|
>
|
||||||
<EditViewPopover {view} />
|
<EditViewPopover {view} />
|
||||||
</NavItem>
|
</NavItem>
|
||||||
|
|
|
@ -39,15 +39,33 @@
|
||||||
tableId: m._id,
|
tableId: m._id,
|
||||||
type: "table",
|
type: "table",
|
||||||
}))
|
}))
|
||||||
$: views = $tablesStore.list.reduce((acc, cur) => {
|
$: viewsV1 = $tablesStore.list.reduce(
|
||||||
let viewsArr = Object.entries(cur.views || {}).map(([key, value]) => ({
|
(acc, table) => [
|
||||||
label: key,
|
...acc,
|
||||||
name: key,
|
...Object.values(table.views || {})
|
||||||
...value,
|
.filter(view => view.version !== 2)
|
||||||
type: "view",
|
.map(view => ({
|
||||||
}))
|
...view,
|
||||||
return [...acc, ...viewsArr]
|
label: view.name,
|
||||||
}, [])
|
type: "view",
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
$: viewsV2 = $tablesStore.list.reduce(
|
||||||
|
(acc, table) => [
|
||||||
|
...acc,
|
||||||
|
...Object.values(table.views || {})
|
||||||
|
.filter(view => view.version === 2)
|
||||||
|
.map(view => ({
|
||||||
|
...view,
|
||||||
|
label: view.name,
|
||||||
|
type: "viewV2",
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
$: views = [...(viewsV1 || []), ...(viewsV2 || [])]
|
||||||
$: queries = $queriesStore.list
|
$: queries = $queriesStore.list
|
||||||
.filter(q => showAllQueries || q.queryVerb === "read" || q.readable)
|
.filter(q => showAllQueries || q.queryVerb === "read" || q.readable)
|
||||||
.map(query => ({
|
.map(query => ({
|
||||||
|
|
|
@ -7,22 +7,50 @@
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
$: tables = $tablesStore.list.map(m => ({
|
$: tables = $tablesStore.list.map(table => ({
|
||||||
label: m.name,
|
...table,
|
||||||
tableId: m._id,
|
|
||||||
type: "table",
|
type: "table",
|
||||||
|
label: table.name,
|
||||||
|
key: table._id,
|
||||||
}))
|
}))
|
||||||
|
$: views = $tablesStore.list.reduce(
|
||||||
|
(acc, table) => [
|
||||||
|
...acc,
|
||||||
|
...Object.values(table.views || {})
|
||||||
|
.filter(view => view.version === 2)
|
||||||
|
.map(view => ({
|
||||||
|
...view,
|
||||||
|
type: "viewV2",
|
||||||
|
label: view.name,
|
||||||
|
key: view.id,
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
$: options = [...(tables || []), ...(views || [])]
|
||||||
|
$: {
|
||||||
|
// Migrate old table values before "key" existed
|
||||||
|
if (value && !value.key) {
|
||||||
|
console.log("migrate")
|
||||||
|
dispatch(
|
||||||
|
"change",
|
||||||
|
tables.find(x => x.tableId === value.tableId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
const dataSource = tables?.find(x => x.tableId === e.detail)
|
dispatch(
|
||||||
dispatch("change", dataSource)
|
"change",
|
||||||
|
options.find(x => x.key === e.detail)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
value={value?.tableId}
|
value={value?.key}
|
||||||
options={tables}
|
{options}
|
||||||
getOptionValue={x => x.tableId}
|
getOptionValue={x => x.key}
|
||||||
getOptionLabel={x => x.label}
|
getOptionLabel={x => x.label}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { writable, derived } from "svelte/store"
|
import { writable, derived, get } from "svelte/store"
|
||||||
import { tables } from "./"
|
import { tables } from "./"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
|
|
||||||
|
@ -30,44 +30,63 @@ export function createViewsV2Store() {
|
||||||
|
|
||||||
const deleteView = async view => {
|
const deleteView = async view => {
|
||||||
await API.viewV2.delete(view.id)
|
await API.viewV2.delete(view.id)
|
||||||
|
replaceView(view.id, null)
|
||||||
// Update tables
|
|
||||||
tables.update(state => {
|
|
||||||
const table = state.list.find(table => table._id === view.tableId)
|
|
||||||
delete table.views[view.name]
|
|
||||||
return { ...state }
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const create = async view => {
|
const create = async view => {
|
||||||
const savedViewResponse = await API.viewV2.create(view)
|
const savedViewResponse = await API.viewV2.create(view)
|
||||||
const savedView = savedViewResponse.data
|
const savedView = savedViewResponse.data
|
||||||
|
replaceView(savedView.id, savedView)
|
||||||
// Update tables
|
|
||||||
tables.update(state => {
|
|
||||||
const table = state.list.find(table => table._id === view.tableId)
|
|
||||||
table.views[view.name] = savedView
|
|
||||||
return { ...state }
|
|
||||||
})
|
|
||||||
|
|
||||||
return savedView
|
return savedView
|
||||||
}
|
}
|
||||||
|
|
||||||
const save = async view => {
|
const save = async view => {
|
||||||
const res = await API.viewV2.update(view)
|
const res = await API.viewV2.update(view)
|
||||||
const savedView = res?.data
|
const savedView = res?.data
|
||||||
|
replaceView(view.id, savedView)
|
||||||
|
}
|
||||||
|
|
||||||
// Update tables
|
// Handles external updates of tables
|
||||||
tables.update(state => {
|
const replaceView = (viewId, view) => {
|
||||||
const table = state.list.find(table => table._id === view.tableId)
|
if (!viewId) {
|
||||||
if (table) {
|
return
|
||||||
if (view.originalName) {
|
}
|
||||||
delete table.views[view.originalName]
|
const existingView = get(derivedStore).list.find(view => view.id === viewId)
|
||||||
}
|
const tableIndex = get(tables).list.findIndex(table => {
|
||||||
table.views[view.name] = savedView
|
return table._id === view?.tableId || table._id === existingView?.tableId
|
||||||
}
|
|
||||||
return { ...state }
|
|
||||||
})
|
})
|
||||||
|
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 {
|
return {
|
||||||
|
@ -76,6 +95,7 @@ export function createViewsV2Store() {
|
||||||
delete: deleteView,
|
delete: deleteView,
|
||||||
create,
|
create,
|
||||||
save,
|
save,
|
||||||
|
replaceView,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5274,7 +5274,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "table",
|
"type": "table",
|
||||||
"label": "Table",
|
"label": "Data",
|
||||||
"key": "dataSource"
|
"key": "dataSource"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -5549,7 +5549,7 @@
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "table",
|
"type": "table",
|
||||||
"label": "Table",
|
"label": "Data",
|
||||||
"key": "table",
|
"key": "table",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
|
|
@ -38,11 +38,8 @@
|
||||||
class:in-builder={$builderStore.inBuilder}
|
class:in-builder={$builderStore.inBuilder}
|
||||||
>
|
>
|
||||||
<Grid
|
<Grid
|
||||||
tableId={table?.tableId}
|
datasource={table}
|
||||||
{API}
|
{API}
|
||||||
{allowAddRows}
|
|
||||||
{allowEditRows}
|
|
||||||
{allowDeleteRows}
|
|
||||||
{stripeRows}
|
{stripeRows}
|
||||||
{initialFilter}
|
{initialFilter}
|
||||||
{initialSortColumn}
|
{initialSortColumn}
|
||||||
|
@ -50,9 +47,12 @@
|
||||||
{fixedRowHeight}
|
{fixedRowHeight}
|
||||||
{columnWhitelist}
|
{columnWhitelist}
|
||||||
{schemaOverrides}
|
{schemaOverrides}
|
||||||
|
canAddRows={allowAddRows}
|
||||||
|
canEditRows={allowEditRows}
|
||||||
|
canDeleteRows={allowDeleteRows}
|
||||||
|
canExpandRows={false}
|
||||||
|
canSaveSchema={false}
|
||||||
showControls={false}
|
showControls={false}
|
||||||
allowExpandRows={false}
|
|
||||||
allowSchemaChanges={false}
|
|
||||||
notifySuccess={notificationStore.actions.success}
|
notifySuccess={notificationStore.actions.success}
|
||||||
notifyError={notificationStore.actions.error}
|
notifyError={notificationStore.actions.error}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import RelationshipFetch from "@budibase/frontend-core/src/fetch/RelationshipFet
|
||||||
import NestedProviderFetch from "@budibase/frontend-core/src/fetch/NestedProviderFetch.js"
|
import NestedProviderFetch from "@budibase/frontend-core/src/fetch/NestedProviderFetch.js"
|
||||||
import FieldFetch from "@budibase/frontend-core/src/fetch/FieldFetch.js"
|
import FieldFetch from "@budibase/frontend-core/src/fetch/FieldFetch.js"
|
||||||
import JSONArrayFetch from "@budibase/frontend-core/src/fetch/JSONArrayFetch.js"
|
import JSONArrayFetch from "@budibase/frontend-core/src/fetch/JSONArrayFetch.js"
|
||||||
|
import ViewV2Fetch from "@budibase/frontend-core/src/fetch/ViewV2Fetch.js"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the schema of any kind of datasource.
|
* Fetches the schema of any kind of datasource.
|
||||||
|
@ -21,6 +22,7 @@ export const fetchDatasourceSchema = async (
|
||||||
const handler = {
|
const handler = {
|
||||||
table: TableFetch,
|
table: TableFetch,
|
||||||
view: ViewFetch,
|
view: ViewFetch,
|
||||||
|
viewV2: ViewV2Fetch,
|
||||||
query: QueryFetch,
|
query: QueryFetch,
|
||||||
link: RelationshipFetch,
|
link: RelationshipFetch,
|
||||||
provider: NestedProviderFetch,
|
provider: NestedProviderFetch,
|
||||||
|
@ -49,6 +51,15 @@ export const fetchDatasourceSchema = async (
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Strip hidden fields from views
|
||||||
|
if (datasource.type === "viewV2") {
|
||||||
|
Object.keys(schema).forEach(field => {
|
||||||
|
if (!schema[field].visible) {
|
||||||
|
delete schema[field]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Enrich schema with relationships if required
|
// Enrich schema with relationships if required
|
||||||
if (definition?.sql && options?.enrichRelationships) {
|
if (definition?.sql && options?.enrichRelationships) {
|
||||||
const relationshipAdditions = await getRelationshipSchemaAdditions(schema)
|
const relationshipAdditions = await getRelationshipSchemaAdditions(schema)
|
||||||
|
|
|
@ -22,9 +22,33 @@ export const buildViewV2Endpoints = API => ({
|
||||||
/**
|
/**
|
||||||
* Fetches all rows in a view
|
* Fetches all rows in a view
|
||||||
* @param viewId the id of the view
|
* @param viewId the id of the view
|
||||||
|
* @param paginate whether to paginate or not
|
||||||
|
* @param limit page size
|
||||||
|
* @param bookmark pagination cursor
|
||||||
|
* @param sort sort column
|
||||||
|
* @param sortOrder sort order
|
||||||
|
* @param sortType sort type (text or numeric)
|
||||||
*/
|
*/
|
||||||
fetch: async viewId => {
|
fetch: async ({
|
||||||
return await API.get({ url: `/api/v2/views/${viewId}/search` })
|
viewId,
|
||||||
|
paginate,
|
||||||
|
limit,
|
||||||
|
bookmark,
|
||||||
|
sort,
|
||||||
|
sortOrder,
|
||||||
|
sortType,
|
||||||
|
}) => {
|
||||||
|
return await API.post({
|
||||||
|
url: `/api/v2/views/${viewId}/search`,
|
||||||
|
body: {
|
||||||
|
paginate,
|
||||||
|
limit,
|
||||||
|
bookmark,
|
||||||
|
sort,
|
||||||
|
sortOrder,
|
||||||
|
sortType,
|
||||||
|
},
|
||||||
|
})
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Delete a view
|
* Delete a view
|
||||||
|
|
|
@ -80,7 +80,7 @@ export const createActions = context => {
|
||||||
|
|
||||||
// Broadcast change to external state can be updated, as this change
|
// Broadcast change to external state can be updated, as this change
|
||||||
// will not be received by the builder websocket because we caused it ourselves
|
// will not be received by the builder websocket because we caused it ourselves
|
||||||
dispatch("updatedefinition", newDefinition)
|
dispatch("updatedatasource", newDefinition)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a row to the datasource
|
// Adds a row to the datasource
|
||||||
|
@ -98,10 +98,16 @@ export const createActions = context => {
|
||||||
return await getAPI()?.actions.deleteRows(rows)
|
return await getAPI()?.actions.deleteRows(rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gets a single row from a datasource
|
||||||
const getRow = async id => {
|
const getRow = async id => {
|
||||||
return await getAPI()?.actions.getRow(id)
|
return await getAPI()?.actions.getRow(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checks if a certain datasource config is valid
|
||||||
|
const isDatasourceValid = datasource => {
|
||||||
|
return getAPI()?.actions.isDatasourceValid(datasource)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
datasource: {
|
datasource: {
|
||||||
...datasource,
|
...datasource,
|
||||||
|
@ -112,6 +118,7 @@ export const createActions = context => {
|
||||||
updateRow,
|
updateRow,
|
||||||
deleteRows,
|
deleteRows,
|
||||||
getRow,
|
getRow,
|
||||||
|
isDatasourceValid,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,7 +98,7 @@ export const createActions = context => {
|
||||||
loading.set(true)
|
loading.set(true)
|
||||||
|
|
||||||
// Abandon if we don't have a valid datasource
|
// Abandon if we don't have a valid datasource
|
||||||
if (!$datasource) {
|
if (!datasource.actions.isDatasourceValid($datasource)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,10 @@ export const createActions = context => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isDatasourceValid = datasource => {
|
||||||
|
return datasource?.type === "table" && datasource?.tableId
|
||||||
|
}
|
||||||
|
|
||||||
const getRow = async id => {
|
const getRow = async id => {
|
||||||
const res = await API.searchTable({
|
const res = await API.searchTable({
|
||||||
tableId: get(datasource).tableId,
|
tableId: get(datasource).tableId,
|
||||||
|
@ -48,6 +52,7 @@ export const createActions = context => {
|
||||||
updateRow: saveRow,
|
updateRow: saveRow,
|
||||||
deleteRows,
|
deleteRows,
|
||||||
getRow,
|
getRow,
|
||||||
|
isDatasourceValid,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -56,38 +61,49 @@ export const createActions = context => {
|
||||||
export const initialise = context => {
|
export const initialise = context => {
|
||||||
const { datasource, fetch, filter, sort, definition } = context
|
const { datasource, fetch, filter, sort, definition } = context
|
||||||
|
|
||||||
|
// Wipe filter whenever table ID changes to avoid using stale filters
|
||||||
|
definition.subscribe(() => {
|
||||||
|
if (get(datasource)?.type !== "table") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filter.set([])
|
||||||
|
})
|
||||||
|
|
||||||
// Update fetch when filter changes
|
// Update fetch when filter changes
|
||||||
filter.subscribe($filter => {
|
filter.subscribe($filter => {
|
||||||
if (get(datasource)?.type === "table") {
|
if (get(datasource)?.type !== "table") {
|
||||||
get(fetch)?.update({
|
return
|
||||||
filter: $filter,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
get(fetch)?.update({
|
||||||
|
filter: $filter,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Update fetch when sorting changes
|
// Update fetch when sorting changes
|
||||||
sort.subscribe($sort => {
|
sort.subscribe($sort => {
|
||||||
if (get(datasource)?.type === "table") {
|
if (get(datasource)?.type !== "table") {
|
||||||
get(fetch)?.update({
|
return
|
||||||
sortOrder: $sort.order,
|
|
||||||
sortColumn: $sort.column,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
get(fetch)?.update({
|
||||||
|
sortOrder: $sort.order,
|
||||||
|
sortColumn: $sort.column,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Ensure sorting UI reflects the fetch state whenever we reset the fetch,
|
// Ensure sorting UI reflects the fetch state whenever we reset the fetch,
|
||||||
// which triggers a new definition
|
// which triggers a new definition
|
||||||
definition.subscribe(() => {
|
definition.subscribe(() => {
|
||||||
if (get(datasource)?.type === "table") {
|
if (get(datasource)?.type !== "table") {
|
||||||
const $fetch = get(fetch)
|
return
|
||||||
if (!$fetch) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const { sortColumn, sortOrder } = get($fetch)
|
|
||||||
sort.set({
|
|
||||||
column: sortColumn,
|
|
||||||
order: sortOrder,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
const $fetch = get(fetch)
|
||||||
|
if (!$fetch) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const { sortColumn, sortOrder } = get($fetch)
|
||||||
|
sort.set({
|
||||||
|
column: sortColumn,
|
||||||
|
order: sortOrder,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,12 @@ export const createActions = context => {
|
||||||
return res?.rows?.[0]
|
return res?.rows?.[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isDatasourceValid = datasource => {
|
||||||
|
return (
|
||||||
|
datasource?.type === "viewV2" && datasource?.id && datasource?.tableId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
viewV2: {
|
viewV2: {
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -69,15 +75,16 @@ export const createActions = context => {
|
||||||
updateRow,
|
updateRow,
|
||||||
deleteRows,
|
deleteRows,
|
||||||
getRow,
|
getRow,
|
||||||
|
isDatasourceValid,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialise = context => {
|
export const initialise = context => {
|
||||||
const { definition, datasource, sort, rows } = context
|
const { definition, datasource, sort, rows, filter } = context
|
||||||
|
|
||||||
// For views, keep sort state in line with the view definition
|
// Keep sort and filter state in line with the view definition
|
||||||
definition.subscribe($definition => {
|
definition.subscribe($definition => {
|
||||||
if (!$definition || get(datasource)?.type !== "viewV2") {
|
if (!$definition || get(datasource)?.type !== "viewV2") {
|
||||||
return
|
return
|
||||||
|
@ -86,6 +93,7 @@ export const initialise = context => {
|
||||||
column: $definition.sort?.field,
|
column: $definition.sort?.field,
|
||||||
order: $definition.sort?.order,
|
order: $definition.sort?.order,
|
||||||
})
|
})
|
||||||
|
filter.set($definition.query || [])
|
||||||
})
|
})
|
||||||
|
|
||||||
// When sorting changes, ensure view definition is kept up to date
|
// When sorting changes, ensure view definition is kept up to date
|
||||||
|
@ -108,4 +116,19 @@ export const initialise = context => {
|
||||||
await rows.actions.refreshData()
|
await rows.actions.refreshData()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// When filters change, ensure view definition is kept up to date
|
||||||
|
filter.subscribe(async $filter => {
|
||||||
|
const $view = get(definition)
|
||||||
|
if (!$view || get(datasource)?.type !== "viewV2") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (JSON.stringify($filter) !== JSON.stringify($view.query)) {
|
||||||
|
await datasource.actions.saveDefinition({
|
||||||
|
...$view,
|
||||||
|
query: $filter,
|
||||||
|
})
|
||||||
|
await rows.actions.refreshData()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ export default class DataFetch {
|
||||||
async getInitialData() {
|
async getInitialData() {
|
||||||
const { datasource, filter, paginate } = this.options
|
const { datasource, filter, paginate } = this.options
|
||||||
|
|
||||||
// Fetch datasource definition and extract filter and sort if configured
|
// Fetch datasource definition and extract sort properties if configured
|
||||||
const definition = await this.getDefinition(datasource)
|
const definition = await this.getDefinition(datasource)
|
||||||
if (definition?.sort?.field) {
|
if (definition?.sort?.field) {
|
||||||
this.options.sortColumn = definition.sort.field
|
this.options.sortColumn = definition.sort.field
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
import DataFetch from "./DataFetch.js"
|
import DataFetch from "./DataFetch.js"
|
||||||
|
import { get } from "svelte/store"
|
||||||
|
|
||||||
export default class ViewV2Fetch extends DataFetch {
|
export default class ViewV2Fetch extends DataFetch {
|
||||||
determineFeatureFlags() {
|
determineFeatureFlags() {
|
||||||
return {
|
return {
|
||||||
|
// The API does not actually support dynamic filtering, but since views
|
||||||
|
// have filters built in we don't want to perform client side filtering
|
||||||
|
// which would happen if we marked this as false
|
||||||
supportsSearch: true,
|
supportsSearch: true,
|
||||||
supportsSort: true,
|
supportsSort: true,
|
||||||
supportsPagination: true,
|
supportsPagination: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSchema(datasource, definition) {
|
getSchema(datasource, definition) {
|
||||||
return definition?.schema
|
return definition?.schema
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,12 +33,30 @@ export default class ViewV2Fetch extends DataFetch {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getData() {
|
async getData() {
|
||||||
const { datasource } = this.options
|
const { datasource, limit, sortColumn, sortOrder, sortType, paginate } =
|
||||||
|
this.options
|
||||||
|
const { cursor } = get(this.store)
|
||||||
try {
|
try {
|
||||||
const res = await this.API.viewV2.fetch(datasource.id)
|
const res = await this.API.viewV2.fetch({
|
||||||
return { rows: res?.rows || [] }
|
viewId: datasource.id,
|
||||||
|
paginate,
|
||||||
|
limit,
|
||||||
|
bookmark: cursor,
|
||||||
|
sort: sortColumn,
|
||||||
|
sortOrder,
|
||||||
|
sortType,
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
rows: res?.rows || [],
|
||||||
|
hasNextPage: res?.hasNextPage || false,
|
||||||
|
cursor: res?.bookmark || null,
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { rows: [] }
|
return {
|
||||||
|
rows: [],
|
||||||
|
hasNextPage: false,
|
||||||
|
error,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
fixAutoColumnSubType,
|
fixAutoColumnSubType,
|
||||||
} from "../../../utilities/rowProcessor"
|
} from "../../../utilities/rowProcessor"
|
||||||
import { runStaticFormulaChecks } from "./bulkFormula"
|
import { runStaticFormulaChecks } from "./bulkFormula"
|
||||||
import { Table } from "@budibase/types"
|
import { Table, ViewStatisticsSchema } from "@budibase/types"
|
||||||
import { quotas } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
import isEqual from "lodash/isEqual"
|
import isEqual from "lodash/isEqual"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
@ -36,7 +36,9 @@ function checkAutoColumns(table: Table, oldTable?: Table) {
|
||||||
export async function save(ctx: any) {
|
export async function save(ctx: any) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const { rows, ...rest } = ctx.request.body
|
const { rows, ...rest } = ctx.request.body
|
||||||
let tableToSave = {
|
let tableToSave: Table & {
|
||||||
|
_rename?: { old: string; updated: string } | null
|
||||||
|
} = {
|
||||||
type: "table",
|
type: "table",
|
||||||
_id: generateTableID(),
|
_id: generateTableID(),
|
||||||
views: {},
|
views: {},
|
||||||
|
@ -44,7 +46,7 @@ export async function save(ctx: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the table obj had an _id then it will have been retrieved
|
// if the table obj had an _id then it will have been retrieved
|
||||||
let oldTable
|
let oldTable: Table | undefined
|
||||||
if (ctx.request.body && ctx.request.body._id) {
|
if (ctx.request.body && ctx.request.body._id) {
|
||||||
oldTable = await sdk.tables.getTable(ctx.request.body._id)
|
oldTable = await sdk.tables.getTable(ctx.request.body._id)
|
||||||
}
|
}
|
||||||
|
@ -97,7 +99,17 @@ export async function save(ctx: any) {
|
||||||
const tableView = tableToSave.views[view]
|
const tableView = tableToSave.views[view]
|
||||||
if (!tableView) continue
|
if (!tableView) continue
|
||||||
|
|
||||||
if (tableView.schema.group || tableView.schema.field) continue
|
if (sdk.views.isV2(tableView)) {
|
||||||
|
// We don't want to modify views from the tables controller
|
||||||
|
tableToSave.views[view] = oldTable!.views![view]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(tableView.schema as ViewStatisticsSchema).group ||
|
||||||
|
tableView.schema.field
|
||||||
|
)
|
||||||
|
continue
|
||||||
tableView.schema = tableToSave.schema
|
tableView.schema = tableToSave.schema
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
ViewV2,
|
ViewV2,
|
||||||
RequiredKeys,
|
RequiredKeys,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
import { builderSocket } from "../../../websockets"
|
||||||
|
|
||||||
async function parseSchemaUI(ctx: Ctx, view: CreateViewRequest) {
|
async function parseSchemaUI(ctx: Ctx, view: CreateViewRequest) {
|
||||||
if (!view.schema) {
|
if (!view.schema) {
|
||||||
|
@ -86,6 +87,9 @@ export async function create(ctx: Ctx<CreateViewRequest, ViewResponse>) {
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
data: result,
|
data: result,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const table = await sdk.tables.getTable(tableId)
|
||||||
|
builderSocket?.emitTableUpdate(ctx, table)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function update(ctx: Ctx<UpdateViewRequest, ViewResponse>) {
|
export async function update(ctx: Ctx<UpdateViewRequest, ViewResponse>) {
|
||||||
|
@ -118,11 +122,17 @@ export async function update(ctx: Ctx<UpdateViewRequest, ViewResponse>) {
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
data: result,
|
data: result,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const table = await sdk.tables.getTable(tableId)
|
||||||
|
builderSocket?.emitTableUpdate(ctx, table)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(ctx: Ctx) {
|
export async function remove(ctx: Ctx) {
|
||||||
const { viewId } = ctx.params
|
const { viewId } = ctx.params
|
||||||
|
|
||||||
await sdk.views.remove(viewId)
|
const view = await sdk.views.remove(viewId)
|
||||||
ctx.status = 204
|
ctx.status = 204
|
||||||
|
|
||||||
|
const table = await sdk.tables.getTable(view.tableId)
|
||||||
|
builderSocket?.emitTableUpdate(ctx, table)
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ export function isV2(view: View | ViewV2): view is ViewV2 {
|
||||||
return (view as ViewV2).version === 2
|
return (view as ViewV2).version === 2
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(viewId: string): Promise<void> {
|
export async function remove(viewId: string): Promise<ViewV2> {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
|
|
||||||
const view = await get(viewId)
|
const view = await get(viewId)
|
||||||
|
@ -64,6 +64,7 @@ export async function remove(viewId: string): Promise<void> {
|
||||||
|
|
||||||
delete table.views![view?.name]
|
delete table.views![view?.name]
|
||||||
await db.put(table)
|
await db.put(table)
|
||||||
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
export function enrichSchema(view: View | ViewV2, tableSchema: TableSchema) {
|
export function enrichSchema(view: View | ViewV2, tableSchema: TableSchema) {
|
||||||
|
|
Loading…
Reference in New Issue