1
0
Fork 0
mirror of synced 2024-10-04 03:54:37 +13:00

Merge branch 'better-types-in-global-users' of github.com:budibase/budibase into better-types-in-global-users-2

This commit is contained in:
Sam Rose 2023-11-08 13:53:28 +00:00
commit 969508cd1c
No known key found for this signature in database
20 changed files with 220 additions and 138 deletions

View file

@ -2,8 +2,8 @@
echo ${TARGETBUILD} > /buildtarget.txt echo ${TARGETBUILD} > /buildtarget.txt
if [[ "${TARGETBUILD}" = "aas" ]]; then if [[ "${TARGETBUILD}" = "aas" ]]; then
# Azure AppService uses /home for persisent data & SSH on port 2222 # Azure AppService uses /home for persistent data & SSH on port 2222
DATA_DIR=/home DATA_DIR="${DATA_DIR:-/home}"
WEBSITES_ENABLE_APP_SERVICE_STORAGE=true WEBSITES_ENABLE_APP_SERVICE_STORAGE=true
mkdir -p $DATA_DIR/{search,minio,couch} mkdir -p $DATA_DIR/{search,minio,couch}
mkdir -p $DATA_DIR/couch/{dbs,views} mkdir -p $DATA_DIR/couch/{dbs,views}

View file

@ -2,8 +2,8 @@
echo ${TARGETBUILD} > /buildtarget.txt echo ${TARGETBUILD} > /buildtarget.txt
if [[ "${TARGETBUILD}" = "aas" ]]; then if [[ "${TARGETBUILD}" = "aas" ]]; then
# Azure AppService uses /home for persisent data & SSH on port 2222 # Azure AppService uses /home for persistent data & SSH on port 2222
DATA_DIR=/home DATA_DIR="${DATA_DIR:-/home}"
WEBSITES_ENABLE_APP_SERVICE_STORAGE=true WEBSITES_ENABLE_APP_SERVICE_STORAGE=true
mkdir -p $DATA_DIR/{search,minio,couch} mkdir -p $DATA_DIR/{search,minio,couch}
mkdir -p $DATA_DIR/couch/{dbs,views} mkdir -p $DATA_DIR/couch/{dbs,views}

View file

@ -22,7 +22,7 @@ declare -a DOCKER_VARS=("APP_PORT" "APPS_URL" "ARCHITECTURE" "BUDIBASE_ENVIRONME
# Azure App Service customisations # Azure App Service customisations
if [[ "${TARGETBUILD}" = "aas" ]]; then if [[ "${TARGETBUILD}" = "aas" ]]; then
DATA_DIR=/home DATA_DIR="${DATA_DIR:-/home}"
WEBSITES_ENABLE_APP_SERVICE_STORAGE=true WEBSITES_ENABLE_APP_SERVICE_STORAGE=true
/etc/init.d/ssh start /etc/init.d/ssh start
else else

View file

@ -1,5 +1,5 @@
{ {
"version": "2.13.3", "version": "2.13.5",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View file

@ -28,7 +28,7 @@ export enum ViewName {
APP_BACKUP_BY_TRIGGER = "by_trigger", APP_BACKUP_BY_TRIGGER = "by_trigger",
} }
export const DeprecatedViews = { export const DeprecatedViews: Record<string, string[]> = {
[ViewName.USER_BY_EMAIL]: [ [ViewName.USER_BY_EMAIL]: [
// removed due to inaccuracy in view doc filter logic // removed due to inaccuracy in view doc filter logic
"by_email", "by_email",

View file

@ -12,12 +12,14 @@ import {
Database, Database,
DatabaseQueryOpts, DatabaseQueryOpts,
Document, Document,
DesignDocument,
DBView,
} from "@budibase/types" } from "@budibase/types"
import env from "../environment" import env from "../environment"
const DESIGN_DB = "_design/database" const DESIGN_DB = "_design/database"
function DesignDoc() { function DesignDoc(): DesignDocument {
return { return {
_id: DESIGN_DB, _id: DESIGN_DB,
// view collation information, read before writing any complex views: // view collation information, read before writing any complex views:
@ -26,20 +28,14 @@ function DesignDoc() {
} }
} }
interface DesignDocument {
views: any
}
async function removeDeprecated(db: Database, viewName: ViewName) { async function removeDeprecated(db: Database, viewName: ViewName) {
// @ts-ignore
if (!DeprecatedViews[viewName]) { if (!DeprecatedViews[viewName]) {
return return
} }
try { try {
const designDoc = await db.get<DesignDocument>(DESIGN_DB) const designDoc = await db.get<DesignDocument>(DESIGN_DB)
// @ts-ignore
for (let deprecatedNames of DeprecatedViews[viewName]) { for (let deprecatedNames of DeprecatedViews[viewName]) {
delete designDoc.views[deprecatedNames] delete designDoc.views?.[deprecatedNames]
} }
await db.put(designDoc) await db.put(designDoc)
} catch (err) { } catch (err) {
@ -48,18 +44,18 @@ async function removeDeprecated(db: Database, viewName: ViewName) {
} }
export async function createView( export async function createView(
db: any, db: Database,
viewJs: string, viewJs: string,
viewName: string viewName: string
): Promise<void> { ): Promise<void> {
let designDoc let designDoc
try { try {
designDoc = (await db.get(DESIGN_DB)) as DesignDocument designDoc = await db.get<DesignDocument>(DESIGN_DB)
} catch (err) { } catch (err) {
// no design doc, make one // no design doc, make one
designDoc = DesignDoc() designDoc = DesignDoc()
} }
const view = { const view: DBView = {
map: viewJs, map: viewJs,
} }
designDoc.views = { designDoc.views = {

View file

@ -13,13 +13,13 @@
export let idx export let idx
export let addLooping export let addLooping
export let deleteStep export let deleteStep
export let enableNaming = true
let validRegex = /^[A-Za-z0-9_\s]+$/ let validRegex = /^[A-Za-z0-9_\s]+$/
let typing = false let typing = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
$: stepNames = $selectedAutomation.definition.stepNames $: stepNames = $selectedAutomation?.definition.stepNames
$: automationName = stepNames?.[block.id] || block?.name || "" $: automationName = stepNames?.[block.id] || block?.name || ""
$: automationNameError = getAutomationNameError(automationName) $: automationNameError = getAutomationNameError(automationName)
$: status = updateStatus(testResult, isTrigger) $: status = updateStatus(testResult, isTrigger)
@ -32,7 +32,7 @@
)?.[0] )?.[0]
} }
} }
$: loopBlock = $selectedAutomation.definition.steps.find( $: loopBlock = $selectedAutomation?.definition.steps.find(
x => x.blockToLoop === block?.id x => x.blockToLoop === block?.id
) )
@ -126,7 +126,11 @@
<Body size="XS"><b>Step {idx}</b></Body> <Body size="XS"><b>Step {idx}</b></Body>
</div> </div>
{/if} {/if}
{#if enableNaming}
<input <input
class="input-text"
disabled={!enableNaming}
placeholder="Enter some text" placeholder="Enter some text"
name="name" name="name"
autocomplete="off" autocomplete="off"
@ -144,6 +148,11 @@
} }
}} }}
/> />
{:else}
<div class="input-text">
{automationName}
</div>
{/if}
</div> </div>
</div> </div>
<div class="blockTitle"> <div class="blockTitle">
@ -178,10 +187,12 @@
<Icon on:click={addLooping} hoverable name="RotateCW" /> <Icon on:click={addLooping} hoverable name="RotateCW" />
</AbsTooltip> </AbsTooltip>
{/if} {/if}
{#if !isHeaderTrigger}
<AbsTooltip type="negative" text="Delete step"> <AbsTooltip type="negative" text="Delete step">
<Icon on:click={deleteStep} hoverable name="DeleteOutline" /> <Icon on:click={deleteStep} hoverable name="DeleteOutline" />
</AbsTooltip> </AbsTooltip>
{/if} {/if}
{/if}
{#if !showTestStatus} {#if !showTestStatus}
<Icon <Icon
on:click={() => dispatch("toggle")} on:click={() => dispatch("toggle")}
@ -244,18 +255,21 @@
display: none; display: none;
} }
input { input {
font-family: var(--font-sans);
color: var(--ink); color: var(--ink);
background-color: transparent; background-color: transparent;
border: 1px solid transparent; border: 1px solid transparent;
font-size: var(--spectrum-alias-font-size-default);
width: 230px; width: 230px;
box-sizing: border-box; box-sizing: border-box;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.input-text {
font-size: var(--spectrum-alias-font-size-default);
font-family: var(--font-sans);
text-overflow: ellipsis;
}
input:focus { input:focus {
outline: none; outline: none;
} }

View file

@ -48,6 +48,7 @@
<div class="block" style={width ? `width: ${width}` : ""}> <div class="block" style={width ? `width: ${width}` : ""}>
{#if block.stepId !== ActionStepID.LOOP} {#if block.stepId !== ActionStepID.LOOP}
<FlowItemHeader <FlowItemHeader
enableNaming={false}
open={!!openBlocks[block.id]} open={!!openBlocks[block.id]}
on:toggle={() => (openBlocks[block.id] = !openBlocks[block.id])} on:toggle={() => (openBlocks[block.id] = !openBlocks[block.id])}
isTrigger={idx === 0} isTrigger={idx === 0}

View file

@ -56,7 +56,7 @@
{/if} {/if}
{#key history} {#key history}
<div class="history"> <div class="history">
<TestDisplay testResults={history} width="100%" /> <TestDisplay testResults={history} width="320px" />
</div> </div>
{/key} {/key}
</Layout> </Layout>

View file

@ -1,7 +1,7 @@
import { EMPTY_LAYOUT } from "../../constants/layouts" import { EMPTY_LAYOUT } from "../../constants/layouts"
import { generateLayoutID, getScreenParams } from "../../db/utils" import { generateLayoutID, getScreenParams } from "../../db/utils"
import { events, context } from "@budibase/backend-core" import { events, context } from "@budibase/backend-core"
import { BBContext } from "@budibase/types" import { BBContext, Layout } from "@budibase/types"
export async function save(ctx: BBContext) { export async function save(ctx: BBContext) {
const db = context.getAppDB() const db = context.getAppDB()
@ -30,7 +30,7 @@ export async function destroy(ctx: BBContext) {
layoutRev = ctx.params.layoutRev layoutRev = ctx.params.layoutRev
const layoutsUsedByScreens = ( const layoutsUsedByScreens = (
await db.allDocs<any>( await db.allDocs<Layout>(
getScreenParams(null, { getScreenParams(null, {
include_docs: true, include_docs: true,
}) })

View file

@ -333,29 +333,33 @@ export async function checkForViewUpdates(
columnRename?: RenameColumn columnRename?: RenameColumn
) { ) {
const views = await getViews() const views = await getViews()
const tableViews = views.filter(view => view.meta.tableId === table._id) const tableViews = views.filter(view => view.meta?.tableId === table._id)
// Check each table view to see if impacted by this table action // Check each table view to see if impacted by this table action
for (let view of tableViews) { for (let view of tableViews) {
let needsUpdated = false let needsUpdated = false
const viewMetadata = view.meta as any
if (!viewMetadata) {
continue
}
// First check for renames, otherwise check for deletions // First check for renames, otherwise check for deletions
if (columnRename) { if (columnRename) {
// Update calculation field if required // Update calculation field if required
if (view.meta.field === columnRename.old) { if (viewMetadata.field === columnRename.old) {
view.meta.field = columnRename.updated viewMetadata.field = columnRename.updated
needsUpdated = true needsUpdated = true
} }
// Update group by field if required // Update group by field if required
if (view.meta.groupBy === columnRename.old) { if (viewMetadata.groupBy === columnRename.old) {
view.meta.groupBy = columnRename.updated viewMetadata.groupBy = columnRename.updated
needsUpdated = true needsUpdated = true
} }
// Update filters if required // Update filters if required
if (view.meta.filters) { if (viewMetadata.filters) {
view.meta.filters.forEach((filter: any) => { viewMetadata.filters.forEach((filter: any) => {
if (filter.key === columnRename.old) { if (filter.key === columnRename.old) {
filter.key = columnRename.updated filter.key = columnRename.updated
needsUpdated = true needsUpdated = true
@ -365,26 +369,26 @@ export async function checkForViewUpdates(
} else if (deletedColumns) { } else if (deletedColumns) {
deletedColumns.forEach((column: string) => { deletedColumns.forEach((column: string) => {
// Remove calculation statement if required // Remove calculation statement if required
if (view.meta.field === column) { if (viewMetadata.field === column) {
delete view.meta.field delete viewMetadata.field
delete view.meta.calculation delete viewMetadata.calculation
delete view.meta.groupBy delete viewMetadata.groupBy
needsUpdated = true needsUpdated = true
} }
// Remove group by field if required // Remove group by field if required
if (view.meta.groupBy === column) { if (viewMetadata.groupBy === column) {
delete view.meta.groupBy delete viewMetadata.groupBy
needsUpdated = true needsUpdated = true
} }
// Remove filters referencing deleted field if required // Remove filters referencing deleted field if required
if (view.meta.filters && view.meta.filters.length) { if (viewMetadata.filters && viewMetadata.filters.length) {
const initialLength = view.meta.filters.length const initialLength = viewMetadata.filters.length
view.meta.filters = view.meta.filters.filter((filter: any) => { viewMetadata.filters = viewMetadata.filters.filter((filter: any) => {
return filter.key !== column return filter.key !== column
}) })
if (initialLength !== view.meta.filters.length) { if (initialLength !== viewMetadata.filters.length) {
needsUpdated = true needsUpdated = true
} }
} }
@ -397,15 +401,16 @@ export async function checkForViewUpdates(
(field: any) => field.name == view.groupBy (field: any) => field.name == view.groupBy
) )
const newViewTemplate = viewTemplate( const newViewTemplate = viewTemplate(
view.meta, viewMetadata,
groupByField?.type === FieldTypes.ARRAY groupByField?.type === FieldTypes.ARRAY
) )
await saveView(null, view.name, newViewTemplate) const viewName = view.name!
if (!newViewTemplate.meta.schema) { await saveView(null, viewName, newViewTemplate)
newViewTemplate.meta.schema = table.schema if (!newViewTemplate.meta?.schema) {
newViewTemplate.meta!.schema = table.schema
} }
if (table.views?.[view.name]) { if (table.views?.[viewName]) {
table.views[view.name] = newViewTemplate.meta as View table.views[viewName] = newViewTemplate.meta as View
} }
} }
} }

View file

@ -8,13 +8,13 @@ import {
import env from "../../../environment" import env from "../../../environment"
import { context } from "@budibase/backend-core" import { context } from "@budibase/backend-core"
import viewBuilder from "./viewBuilder" import viewBuilder from "./viewBuilder"
import { Database } from "@budibase/types" import { Database, DBView, DesignDocument, InMemoryView } from "@budibase/types"
export async function getView(viewName: string) { export async function getView(viewName: string) {
const db = context.getAppDB() const db = context.getAppDB()
if (env.SELF_HOSTED) { if (env.SELF_HOSTED) {
const designDoc = await db.get<any>("_design/database") const designDoc = await db.get<DesignDocument>("_design/database")
return designDoc.views[viewName] return designDoc.views?.[viewName]
} else { } else {
// This is a table view, don't read the view from the DB // This is a table view, don't read the view from the DB
if (viewName.startsWith(DocumentType.TABLE + SEPARATOR)) { if (viewName.startsWith(DocumentType.TABLE + SEPARATOR)) {
@ -22,7 +22,7 @@ export async function getView(viewName: string) {
} }
try { try {
const viewDoc = await db.get<any>(generateMemoryViewID(viewName)) const viewDoc = await db.get<InMemoryView>(generateMemoryViewID(viewName))
return viewDoc.view return viewDoc.view
} catch (err: any) { } catch (err: any) {
// Return null when PouchDB doesn't found the view // Return null when PouchDB doesn't found the view
@ -35,25 +35,28 @@ export async function getView(viewName: string) {
} }
} }
export async function getViews() { export async function getViews(): Promise<DBView[]> {
const db = context.getAppDB() const db = context.getAppDB()
const response = [] const response: DBView[] = []
if (env.SELF_HOSTED) { if (env.SELF_HOSTED) {
const designDoc = await db.get<any>("_design/database") const designDoc = await db.get<DesignDocument>("_design/database")
for (let name of Object.keys(designDoc.views)) { for (let name of Object.keys(designDoc.views || {})) {
// Only return custom views, not built ins // Only return custom views, not built ins
const viewNames = Object.values(ViewName) as string[] const viewNames = Object.values(ViewName) as string[]
if (viewNames.indexOf(name) !== -1) { if (viewNames.indexOf(name) !== -1) {
continue continue
} }
const view = designDoc.views?.[name]
if (view) {
response.push({ response.push({
name, name,
...designDoc.views[name], ...view,
}) })
} }
}
} else { } else {
const views = ( const views = (
await db.allDocs<any>( await db.allDocs<InMemoryView>(
getMemoryViewParams({ getMemoryViewParams({
include_docs: true, include_docs: true,
}) })
@ -72,11 +75,11 @@ export async function getViews() {
export async function saveView( export async function saveView(
originalName: string | null, originalName: string | null,
viewName: string, viewName: string,
viewTemplate: any viewTemplate: DBView
) { ) {
const db = context.getAppDB() const db = context.getAppDB()
if (env.SELF_HOSTED) { if (env.SELF_HOSTED) {
const designDoc = await db.get<any>("_design/database") const designDoc = await db.get<DesignDocument>("_design/database")
designDoc.views = { designDoc.views = {
...designDoc.views, ...designDoc.views,
[viewName]: viewTemplate, [viewName]: viewTemplate,
@ -89,17 +92,17 @@ export async function saveView(
} else { } else {
const id = generateMemoryViewID(viewName) const id = generateMemoryViewID(viewName)
const originalId = originalName ? generateMemoryViewID(originalName) : null const originalId = originalName ? generateMemoryViewID(originalName) : null
const viewDoc: any = { const viewDoc: InMemoryView = {
_id: id, _id: id,
view: viewTemplate, view: viewTemplate,
name: viewName, name: viewName,
tableId: viewTemplate.meta.tableId, tableId: viewTemplate.meta!.tableId,
} }
try { try {
const old = await db.get<any>(id) const old = await db.get<InMemoryView>(id)
if (originalId) { if (originalId) {
const originalDoc = await db.get<any>(originalId) const originalDoc = await db.get<InMemoryView>(originalId)
await db.remove(originalDoc._id, originalDoc._rev) await db.remove(originalDoc._id!, originalDoc._rev)
} }
if (old && old._rev) { if (old && old._rev) {
viewDoc._rev = old._rev viewDoc._rev = old._rev
@ -114,52 +117,65 @@ export async function saveView(
export async function deleteView(viewName: string) { export async function deleteView(viewName: string) {
const db = context.getAppDB() const db = context.getAppDB()
if (env.SELF_HOSTED) { if (env.SELF_HOSTED) {
const designDoc = await db.get<any>("_design/database") const designDoc = await db.get<DesignDocument>("_design/database")
const view = designDoc.views[viewName] const view = designDoc.views?.[viewName]
delete designDoc.views[viewName] delete designDoc.views?.[viewName]
await db.put(designDoc) await db.put(designDoc)
return view return view
} else { } else {
const id = generateMemoryViewID(viewName) const id = generateMemoryViewID(viewName)
const viewDoc = await db.get<any>(id) const viewDoc = await db.get<InMemoryView>(id)
await db.remove(viewDoc._id, viewDoc._rev) await db.remove(viewDoc._id!, viewDoc._rev)
return viewDoc.view return viewDoc.view
} }
} }
export async function migrateToInMemoryView(db: Database, viewName: string) { export async function migrateToInMemoryView(db: Database, viewName: string) {
// delete the view initially // delete the view initially
const designDoc = await db.get<any>("_design/database") const designDoc = await db.get<DesignDocument>("_design/database")
const meta = designDoc.views?.[viewName].meta
if (!meta) {
throw new Error("Unable to migrate view - no metadata")
}
// run the view back through the view builder to update it // run the view back through the view builder to update it
const view = viewBuilder(designDoc.views[viewName].meta) const view = viewBuilder(meta)
delete designDoc.views[viewName] delete designDoc.views?.[viewName]
await db.put(designDoc) await db.put(designDoc)
await exports.saveView(db, null, viewName, view) await saveView(null, viewName, view)
} }
export async function migrateToDesignView(db: Database, viewName: string) { export async function migrateToDesignView(db: Database, viewName: string) {
let view = await db.get<any>(generateMemoryViewID(viewName)) let view = await db.get<InMemoryView>(generateMemoryViewID(viewName))
const designDoc = await db.get<any>("_design/database") const designDoc = await db.get<DesignDocument>("_design/database")
designDoc.views[viewName] = viewBuilder(view.view.meta) const meta = view.view.meta
if (!meta) {
throw new Error("Unable to migrate view - no metadata")
}
if (!designDoc.views) {
designDoc.views = {}
}
designDoc.views[viewName] = viewBuilder(meta)
await db.put(designDoc) await db.put(designDoc)
await db.remove(view._id, view._rev) await db.remove(view._id!, view._rev)
} }
export async function getFromDesignDoc(db: Database, viewName: string) { export async function getFromDesignDoc(db: Database, viewName: string) {
const designDoc = await db.get<any>("_design/database") const designDoc = await db.get<DesignDocument>("_design/database")
let view = designDoc.views[viewName] let view = designDoc.views?.[viewName]
if (view == null) { if (view == null) {
throw { status: 404, message: "Unable to get view" } throw { status: 404, message: "Unable to get view" }
} }
return view return view
} }
export async function getFromMemoryDoc(db: Database, viewName: string) { export async function getFromMemoryDoc(
let view = await db.get<any>(generateMemoryViewID(viewName)) db: Database,
viewName: string
): Promise<DBView> {
let view = await db.get<InMemoryView>(generateMemoryViewID(viewName))
if (view) { if (view) {
view = view.view return view.view
} else { } else {
throw { status: 404, message: "Unable to get view" } throw { status: 404, message: "Unable to get view" }
} }
return view
} }

View file

@ -1,13 +1,4 @@
import { ViewFilter } from "@budibase/types" import { ViewFilter, ViewTemplateOpts, DBView } from "@budibase/types"
type ViewTemplateOpts = {
field: string
tableId: string
groupBy: string
filters: ViewFilter[]
calculation: string
groupByMulti: boolean
}
const TOKEN_MAP: Record<string, string> = { const TOKEN_MAP: Record<string, string> = {
EQUALS: "===", EQUALS: "===",
@ -146,7 +137,7 @@ function parseEmitExpression(field: string, groupBy: string) {
export default function ( export default function (
{ field, tableId, groupBy, filters = [], calculation }: ViewTemplateOpts, { field, tableId, groupBy, filters = [], calculation }: ViewTemplateOpts,
groupByMulti?: boolean groupByMulti?: boolean
) { ): DBView {
// first filter can't have a conjunction // first filter can't have a conjunction
if (filters && filters.length > 0 && filters[0].conjunction) { if (filters && filters.length > 0 && filters[0].conjunction) {
delete filters[0].conjunction delete filters[0].conjunction

View file

@ -47,8 +47,11 @@ export async function save(ctx: Ctx) {
// add views to table document // add views to table document
if (!table.views) table.views = {} if (!table.views) table.views = {}
if (!view.meta.schema) { if (!view.meta?.schema) {
view.meta.schema = table.schema view.meta = {
...view.meta!,
schema: table.schema,
}
} }
table.views[viewName] = { ...view.meta, name: viewName } table.views[viewName] = { ...view.meta, name: viewName }
if (originalName) { if (originalName) {
@ -125,10 +128,13 @@ export async function destroy(ctx: Ctx) {
const db = context.getAppDB() const db = context.getAppDB()
const viewName = decodeURIComponent(ctx.params.viewName) const viewName = decodeURIComponent(ctx.params.viewName)
const view = await deleteView(viewName) const view = await deleteView(viewName)
if (!view || !view.meta) {
ctx.throw(400, "Unable to delete view - no metadata/view not found.")
}
const table = await sdk.tables.getTable(view.meta.tableId) const table = await sdk.tables.getTable(view.meta.tableId)
delete table.views![viewName] delete table.views![viewName]
await db.put(table) await db.put(table)
await events.view.deleted(view) await events.view.deleted(view as View)
ctx.body = view ctx.body = view
builderSocket?.emitTableUpdate(ctx, table) builderSocket?.emitTableUpdate(ctx, table)
@ -147,7 +153,7 @@ export async function exportView(ctx: Ctx) {
) )
} }
if (view) { if (view && view.meta) {
ctx.params.viewName = viewName ctx.params.viewName = viewName
// Fetch view rows // Fetch view rows
ctx.query = { ctx.query = {

View file

@ -32,6 +32,7 @@ import {
structures, structures,
} from "@budibase/backend-core/tests" } from "@budibase/backend-core/tests"
import _ from "lodash" import _ from "lodash"
import * as uuid from "uuid"
const timestamp = new Date("2023-01-26T11:48:57.597Z").toISOString() const timestamp = new Date("2023-01-26T11:48:57.597Z").toISOString()
tk.freeze(timestamp) tk.freeze(timestamp)
@ -68,7 +69,7 @@ describe.each([
const generateTableConfig: () => SaveTableRequest = () => { const generateTableConfig: () => SaveTableRequest = () => {
return { return {
name: generator.word(), name: uuid.v4(),
type: "table", type: "table",
primary: ["id"], primary: ["id"],
primaryDisplay: "name", primaryDisplay: "name",
@ -481,7 +482,7 @@ describe.each([
}) })
const createViewResponse = await config.createView({ const createViewResponse = await config.createView({
name: generator.word(), name: uuid.v4(),
schema: { schema: {
Country: { Country: {
visible: true, visible: true,
@ -816,7 +817,8 @@ describe.each([
RelationshipType.ONE_TO_MANY, RelationshipType.ONE_TO_MANY,
["link"], ["link"],
{ {
name: generator.word(), // Making sure that the combined table name + column name is within postgres limits
name: uuid.v4().replace(/-/g, "").substring(0, 16),
type: "table", type: "table",
primary: ["id"], primary: ["id"],
primaryDisplay: "id", primaryDisplay: "id",
@ -949,7 +951,7 @@ describe.each([
describe("view 2.0", () => { describe("view 2.0", () => {
async function userTable(): Promise<Table> { async function userTable(): Promise<Table> {
return { return {
name: `users_${generator.word()}`, name: `users_${uuid.v4()}`,
sourceId: INTERNAL_TABLE_SOURCE_ID, sourceId: INTERNAL_TABLE_SOURCE_ID,
sourceType: TableSourceType.INTERNAL, sourceType: TableSourceType.INTERNAL,
type: "table", type: "table",
@ -1133,7 +1135,7 @@ describe.each([
const viewSchema = { age: { visible: true }, name: { visible: true } } const viewSchema = { age: { visible: true }, name: { visible: true } }
async function userTable(): Promise<Table> { async function userTable(): Promise<Table> {
return { return {
name: `users_${generator.word()}`, name: `users_${uuid.v4()}`,
sourceId: INTERNAL_TABLE_SOURCE_ID, sourceId: INTERNAL_TABLE_SOURCE_ID,
sourceType: TableSourceType.INTERNAL, sourceType: TableSourceType.INTERNAL,
type: "table", type: "table",
@ -1630,7 +1632,7 @@ describe.each([
}), }),
(tableId: string) => (tableId: string) =>
config.api.row.save(tableId, { config.api.row.save(tableId, {
name: generator.word(), name: uuid.v4(),
description: generator.paragraph(), description: generator.paragraph(),
tableId, tableId,
}), }),

View file

@ -1,5 +1,5 @@
import newid from "./newid" import newid from "./newid"
import { Row, View, Document } from "@budibase/types" import { Row, Document, DBView } from "@budibase/types"
// bypass the main application db config // bypass the main application db config
// use in memory pouchdb directly // use in memory pouchdb directly
@ -7,7 +7,7 @@ import { db as dbCore } from "@budibase/backend-core"
const Pouch = dbCore.getPouch({ inMemory: true }) const Pouch = dbCore.getPouch({ inMemory: true })
export async function runView( export async function runView(
view: View, view: DBView,
calculation: string, calculation: string,
group: boolean, group: boolean,
data: Row[] data: Row[]

View file

@ -185,8 +185,8 @@ export async function fetchView(
group: !!group, group: !!group,
}) })
} else { } else {
const tableId = viewInfo.meta.tableId const tableId = viewInfo.meta!.tableId
const data = await fetchRaw(tableId) const data = await fetchRaw(tableId!)
response = await inMemoryViews.runView( response = await inMemoryViews.runView(
viewInfo, viewInfo,
calculation as string, calculation as string,
@ -200,7 +200,7 @@ export async function fetchView(
response.rows = response.rows.map(row => row.doc) response.rows = response.rows.map(row => row.doc)
let table: Table let table: Table
try { try {
table = await sdk.tables.getTable(viewInfo.meta.tableId) table = await sdk.tables.getTable(viewInfo.meta!.tableId)
} catch (err) { } catch (err) {
throw new Error("Unable to retrieve view table.") throw new Error("Unable to retrieve view table.")
} }

View file

@ -2,4 +2,5 @@ import { Document } from "../document"
export interface Layout extends Document { export interface Layout extends Document {
props: any props: any
layoutId?: string
} }

View file

@ -1,5 +1,24 @@
import { SearchFilter, SortOrder, SortType } from "../../api" import { SearchFilter, SortOrder, SortType } from "../../api"
import { UIFieldMetadata } from "./table" import { UIFieldMetadata } from "./table"
import { Document } from "../document"
import { DBView } from "../../sdk"
export type ViewTemplateOpts = {
field: string
tableId: string
groupBy: string
filters: ViewFilter[]
schema: any
calculation: string
groupByMulti?: boolean
}
export interface InMemoryView extends Document {
view: DBView
name: string
tableId: string
groupBy?: string
}
export interface View { export interface View {
name?: string name?: string
@ -10,7 +29,7 @@ export interface View {
calculation?: ViewCalculation calculation?: ViewCalculation
map?: string map?: string
reduce?: any reduce?: any
meta?: Record<string, any> meta?: ViewTemplateOpts
} }
export interface ViewV2 { export interface ViewV2 {

View file

@ -1,5 +1,5 @@
import Nano from "@budibase/nano" import Nano from "@budibase/nano"
import { AllDocsResponse, AnyDocument, Document } from "../" import { AllDocsResponse, AnyDocument, Document, ViewTemplateOpts } from "../"
import { Writable } from "stream" import { Writable } from "stream"
export enum SearchIndex { export enum SearchIndex {
@ -20,6 +20,37 @@ export enum SortOption {
DESCENDING = "desc", DESCENDING = "desc",
} }
export type IndexAnalyzer = {
name: string
default?: string
fields?: Record<string, string>
}
export type DBView = {
name?: string
map: string
reduce?: string
meta?: ViewTemplateOpts
groupBy?: string
}
export interface DesignDocument extends Document {
// we use this static reference for all design documents
_id: "_design/database"
language?: string
// CouchDB views
views?: {
[viewName: string]: DBView
}
// Lucene indexes
indexes?: {
[indexName: string]: {
index: string
analyzer?: string | IndexAnalyzer
}
}
}
export type CouchFindOptions = { export type CouchFindOptions = {
selector: PouchDB.Find.Selector selector: PouchDB.Find.Selector
fields?: string[] fields?: string[]