Merge branch 'better-types-in-global-users' of github.com:budibase/budibase into better-types-in-global-users-2
This commit is contained in:
commit
969508cd1c
|
@ -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}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.13.3",
|
"version": "2.13.5",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
})
|
})
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -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[]
|
||||||
|
|
|
@ -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.")
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,4 +2,5 @@ import { Document } from "../document"
|
||||||
|
|
||||||
export interface Layout extends Document {
|
export interface Layout extends Document {
|
||||||
props: any
|
props: any
|
||||||
|
layoutId?: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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[]
|
||||||
|
|
Loading…
Reference in New Issue