Merge branch 'master' into budi-8455-on-screen-load-open-side-panel-issue-side-panel-will-open
This commit is contained in:
commit
c2fda977bd
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||||
"version": "2.29.17",
|
"version": "2.29.20",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import { DEFAULT_TENANT_ID, SEPARATOR, DocumentType } from "../constants"
|
import { DEFAULT_TENANT_ID, SEPARATOR, DocumentType } from "../constants"
|
||||||
import { getTenantId, getGlobalDBName } from "../context"
|
import { getTenantId, getGlobalDBName, isMultiTenant } from "../context"
|
||||||
import { doWithDB, directCouchAllDbs } from "./db"
|
import { doWithDB, directCouchAllDbs } from "./db"
|
||||||
import { AppState, DeletedApp, getAppMetadata } from "../cache/appMetadata"
|
import { AppState, DeletedApp, getAppMetadata } from "../cache/appMetadata"
|
||||||
import { isDevApp, isDevAppID, getProdAppID } from "../docIds/conversions"
|
import { isDevApp, isDevAppID, getProdAppID } from "../docIds/conversions"
|
||||||
|
@ -213,6 +213,11 @@ export function isSqsEnabledForTenant(): boolean {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// single tenant (self host and dev) always enabled if flag set
|
||||||
|
if (!isMultiTenant()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// This is to guard against the situation in tests where tests pass because
|
// This is to guard against the situation in tests where tests pass because
|
||||||
// we're not actually using SQS, we're using Lucene and the tests pass due to
|
// we're not actually using SQS, we're using Lucene and the tests pass due to
|
||||||
// parity.
|
// parity.
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { GenericContainer, StartedTestContainer } from "testcontainers"
|
||||||
import { generator, structures } from "../../../tests"
|
import { generator, structures } from "../../../tests"
|
||||||
import RedisWrapper from "../redis"
|
import RedisWrapper from "../redis"
|
||||||
import { env } from "../.."
|
import { env } from "../.."
|
||||||
|
import { randomUUID } from "crypto"
|
||||||
|
|
||||||
jest.setTimeout(30000)
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
|
@ -52,10 +53,10 @@ describe("redis", () => {
|
||||||
describe("bulkStore", () => {
|
describe("bulkStore", () => {
|
||||||
function createRandomObject(
|
function createRandomObject(
|
||||||
keyLength: number,
|
keyLength: number,
|
||||||
valueGenerator: () => any = () => generator.word()
|
valueGenerator: () => any = () => randomUUID()
|
||||||
) {
|
) {
|
||||||
return generator
|
return generator
|
||||||
.unique(() => generator.word(), keyLength)
|
.unique(() => randomUUID(), keyLength)
|
||||||
.reduce((acc, key) => {
|
.reduce((acc, key) => {
|
||||||
acc[key] = valueGenerator()
|
acc[key] = valueGenerator()
|
||||||
return acc
|
return acc
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select, Label, Checkbox, Input, Body } from "@budibase/bbui"
|
import { Select, Label, Checkbox, Body } from "@budibase/bbui"
|
||||||
import { tables, viewsV2 } from "stores/builder"
|
import { tables, viewsV2 } from "stores/builder"
|
||||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
|
|
||||||
|
@ -46,19 +46,35 @@
|
||||||
|
|
||||||
{#if parameters.confirm}
|
{#if parameters.confirm}
|
||||||
<Label small>Title</Label>
|
<Label small>Title</Label>
|
||||||
<Input placeholder="Delete Row" bind:value={parameters.customTitleText} />
|
<DrawerBindableInput
|
||||||
|
placeholder="Prompt User"
|
||||||
|
value={parameters.customTitleText}
|
||||||
|
on:change={e => (parameters.customTitleText = e.detail)}
|
||||||
|
{bindings}
|
||||||
|
/>
|
||||||
|
|
||||||
<Label small>Text</Label>
|
<Label small>Text</Label>
|
||||||
<Input
|
<DrawerBindableInput
|
||||||
placeholder="Are you sure you want to delete?"
|
placeholder="Are you sure you want to continue?"
|
||||||
bind:value={parameters.confirmText}
|
value={parameters.confirmText}
|
||||||
|
on:change={e => (parameters.confirmText = e.detail)}
|
||||||
|
{bindings}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Label small>Confirm Text</Label>
|
<Label small>Confirm Text</Label>
|
||||||
<Input placeholder="Confirm" bind:value={parameters.confirmButtonText} />
|
<DrawerBindableInput
|
||||||
|
placeholder="Confirm"
|
||||||
|
value={parameters.confirmButtonText}
|
||||||
|
on:change={e => (parameters.confirmButtonText = e.detail)}
|
||||||
|
{bindings}
|
||||||
|
/>
|
||||||
<Label small>Cancel Text</Label>
|
<Label small>Cancel Text</Label>
|
||||||
<Input placeholder="Cancel" bind:value={parameters.cancelButtonText} />
|
<DrawerBindableInput
|
||||||
|
placeholder="Cancel"
|
||||||
|
value={parameters.cancelButtonText}
|
||||||
|
on:change={e => (parameters.cancelButtonText = e.detail)}
|
||||||
|
{bindings}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select, Label, Body, Checkbox, Input } from "@budibase/bbui"
|
import { Select, Label, Body, Checkbox } from "@budibase/bbui"
|
||||||
import {
|
import {
|
||||||
selectedScreen,
|
selectedScreen,
|
||||||
componentStore,
|
componentStore,
|
||||||
tables,
|
tables,
|
||||||
viewsV2,
|
viewsV2,
|
||||||
} from "stores/builder"
|
} from "stores/builder"
|
||||||
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
import { getSchemaForDatasourcePlus } from "dataBinding"
|
import { getSchemaForDatasourcePlus } from "dataBinding"
|
||||||
import SaveFields from "./SaveFields.svelte"
|
import SaveFields from "./SaveFields.svelte"
|
||||||
import { getDatasourceLikeProviders } from "components/design/settings/controls/ButtonActionEditor/actions/utils"
|
import { getDatasourceLikeProviders } from "components/design/settings/controls/ButtonActionEditor/actions/utils"
|
||||||
|
@ -73,22 +74,35 @@
|
||||||
|
|
||||||
{#if parameters.confirm}
|
{#if parameters.confirm}
|
||||||
<Label small>Title</Label>
|
<Label small>Title</Label>
|
||||||
<Input
|
<DrawerBindableInput
|
||||||
placeholder="Duplicate Row"
|
placeholder="Prompt User"
|
||||||
bind:value={parameters.customTitleText}
|
value={parameters.customTitleText}
|
||||||
|
on:change={e => (parameters.customTitleText = e.detail)}
|
||||||
|
{bindings}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Label small>Text</Label>
|
<Label small>Text</Label>
|
||||||
<Input
|
<DrawerBindableInput
|
||||||
placeholder="Are you sure you want to duplicate this row?"
|
placeholder="Are you sure you want to continue?"
|
||||||
bind:value={parameters.confirmText}
|
value={parameters.confirmText}
|
||||||
|
on:change={e => (parameters.confirmText = e.detail)}
|
||||||
|
{bindings}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Label small>Confirm Text</Label>
|
<Label small>Confirm Text</Label>
|
||||||
<Input placeholder="Confirm" bind:value={parameters.confirmButtonText} />
|
<DrawerBindableInput
|
||||||
|
placeholder="Confirm"
|
||||||
|
value={parameters.confirmButtonText}
|
||||||
|
on:change={e => (parameters.confirmButtonText = e.detail)}
|
||||||
|
{bindings}
|
||||||
|
/>
|
||||||
<Label small>Cancel Text</Label>
|
<Label small>Cancel Text</Label>
|
||||||
<Input placeholder="Cancel" bind:value={parameters.cancelButtonText} />
|
<DrawerBindableInput
|
||||||
|
placeholder="Cancel"
|
||||||
|
value={parameters.cancelButtonText}
|
||||||
|
on:change={e => (parameters.cancelButtonText = e.detail)}
|
||||||
|
{bindings}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select, Layout, Input, Checkbox } from "@budibase/bbui"
|
import { Select, Layout, Checkbox } from "@budibase/bbui"
|
||||||
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
import { datasources, integrations, queries } from "stores/builder"
|
import { datasources, integrations, queries } from "stores/builder"
|
||||||
import BindingBuilder from "components/integration/QueryBindingBuilder.svelte"
|
import BindingBuilder from "components/integration/QueryBindingBuilder.svelte"
|
||||||
import IntegrationQueryEditor from "components/integration/index.svelte"
|
import IntegrationQueryEditor from "components/integration/index.svelte"
|
||||||
|
@ -58,37 +59,46 @@
|
||||||
text="Do not display default notification"
|
text="Do not display default notification"
|
||||||
bind:value={parameters.notificationOverride}
|
bind:value={parameters.notificationOverride}
|
||||||
/>
|
/>
|
||||||
<br />
|
|
||||||
{#if parameters.queryId}
|
{#if parameters.queryId}
|
||||||
<Checkbox text="Require confirmation" bind:value={parameters.confirm} />
|
<Checkbox text="Require confirmation" bind:value={parameters.confirm} />
|
||||||
|
|
||||||
{#if parameters.confirm}
|
{#if parameters.confirm}
|
||||||
<Input
|
<div class="params">
|
||||||
|
<DrawerBindableInput
|
||||||
label="Title"
|
label="Title"
|
||||||
placeholder="Execute Query"
|
placeholder="Prompt User"
|
||||||
bind:value={parameters.customTitleText}
|
value={parameters.customTitleText}
|
||||||
|
on:change={e => (parameters.customTitleText = e.detail)}
|
||||||
|
{bindings}
|
||||||
|
/>
|
||||||
|
<DrawerBindableInput
|
||||||
|
label="Message"
|
||||||
|
placeholder="Are you sure you want to continue?"
|
||||||
|
value={parameters.confirmText}
|
||||||
|
on:change={e => (parameters.confirmText = e.detail)}
|
||||||
|
{bindings}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Input
|
<DrawerBindableInput
|
||||||
label="Text"
|
|
||||||
placeholder="Are you sure you want to execute this query?"
|
|
||||||
bind:value={parameters.confirmText}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
label="Confirm Text"
|
label="Confirm Text"
|
||||||
placeholder="Confirm"
|
placeholder="Confirm"
|
||||||
bind:value={parameters.confirmButtonText}
|
value={parameters.confirmButtonText}
|
||||||
|
on:change={e => (parameters.confirmButtonText = e.detail)}
|
||||||
|
{bindings}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Input
|
<DrawerBindableInput
|
||||||
label="Cancel Text"
|
label="Cancel Text"
|
||||||
placeholder="Cancel"
|
placeholder="Cancel"
|
||||||
bind:value={parameters.cancelButtonText}
|
value={parameters.cancelButtonText}
|
||||||
|
on:change={e => (parameters.cancelButtonText = e.detail)}
|
||||||
|
{bindings}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if query?.parameters?.length > 0}
|
{#if query?.parameters?.length > 0}
|
||||||
|
<br />
|
||||||
<div class="params">
|
<div class="params">
|
||||||
<BindingBuilder
|
<BindingBuilder
|
||||||
customParams={parameters.queryParams}
|
customParams={parameters.queryParams}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select, Label, Body, Checkbox, Input } from "@budibase/bbui"
|
import { Select, Label, Body, Checkbox } from "@budibase/bbui"
|
||||||
import {
|
import {
|
||||||
selectedScreen,
|
selectedScreen,
|
||||||
componentStore,
|
componentStore,
|
||||||
tables,
|
tables,
|
||||||
viewsV2,
|
viewsV2,
|
||||||
} from "stores/builder"
|
} from "stores/builder"
|
||||||
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
import { getSchemaForDatasourcePlus } from "dataBinding"
|
import { getSchemaForDatasourcePlus } from "dataBinding"
|
||||||
import SaveFields from "./SaveFields.svelte"
|
import SaveFields from "./SaveFields.svelte"
|
||||||
import { getDatasourceLikeProviders } from "components/design/settings/controls/ButtonActionEditor/actions/utils"
|
import { getDatasourceLikeProviders } from "components/design/settings/controls/ButtonActionEditor/actions/utils"
|
||||||
|
@ -73,19 +74,35 @@
|
||||||
|
|
||||||
{#if parameters.confirm}
|
{#if parameters.confirm}
|
||||||
<Label small>Title</Label>
|
<Label small>Title</Label>
|
||||||
<Input placeholder="Save Row" bind:value={parameters.customTitleText} />
|
<DrawerBindableInput
|
||||||
|
placeholder="Prompt User"
|
||||||
|
value={parameters.customTitleText}
|
||||||
|
on:change={e => (parameters.customTitleText = e.detail)}
|
||||||
|
{bindings}
|
||||||
|
/>
|
||||||
|
|
||||||
<Label small>Text</Label>
|
<Label small>Text</Label>
|
||||||
<Input
|
<DrawerBindableInput
|
||||||
placeholder="Are you sure you want to save this row?"
|
placeholder="Are you sure you want to continue?"
|
||||||
bind:value={parameters.confirmText}
|
value={parameters.confirmText}
|
||||||
|
on:change={e => (parameters.confirmText = e.detail)}
|
||||||
|
{bindings}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Label small>Confirm Text</Label>
|
<Label small>Confirm Text</Label>
|
||||||
<Input placeholder="Confirm" bind:value={parameters.confirmButtonText} />
|
<DrawerBindableInput
|
||||||
|
placeholder="Confirm"
|
||||||
|
value={parameters.confirmButtonText}
|
||||||
|
on:change={e => (parameters.confirmButtonText = e.detail)}
|
||||||
|
{bindings}
|
||||||
|
/>
|
||||||
<Label small>Cancel Text</Label>
|
<Label small>Cancel Text</Label>
|
||||||
<Input placeholder="Cancel" bind:value={parameters.cancelButtonText} />
|
<DrawerBindableInput
|
||||||
|
placeholder="Cancel"
|
||||||
|
value={parameters.cancelButtonText}
|
||||||
|
on:change={e => (parameters.cancelButtonText = e.detail)}
|
||||||
|
{bindings}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -124,7 +124,7 @@
|
||||||
<PropertyControl
|
<PropertyControl
|
||||||
label="Text align"
|
label="Text align"
|
||||||
control={BarButtonList}
|
control={BarButtonList}
|
||||||
onChange={align => nav.syncAppNavigation({ textAlign: align })}
|
onChange={align => update("textAlign", align)}
|
||||||
value={$nav.textAlign}
|
value={$nav.textAlign}
|
||||||
props={{
|
props={{
|
||||||
options: alignmentOptions,
|
options: alignmentOptions,
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import newid from "../../../db/newid"
|
import { context, utils } from "@budibase/backend-core"
|
||||||
import { context } from "@budibase/backend-core"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is used to pass around information about the deployment that is occurring
|
* This is used to pass around information about the deployment that is occurring
|
||||||
|
@ -12,7 +11,7 @@ export default class Deployment {
|
||||||
appUrl?: string
|
appUrl?: string
|
||||||
|
|
||||||
constructor(id = null) {
|
constructor(id = null) {
|
||||||
this._id = id || newid()
|
this._id = id || utils.newid()
|
||||||
}
|
}
|
||||||
|
|
||||||
setVerification(verification: any) {
|
setVerification(verification: any) {
|
||||||
|
|
|
@ -203,7 +203,7 @@ describe("/permission", () => {
|
||||||
// replicate changes before checking permissions
|
// replicate changes before checking permissions
|
||||||
await config.publish()
|
await config.publish()
|
||||||
|
|
||||||
await config.api.viewV2.publicSearch(view.id, undefined, { status: 403 })
|
await config.api.viewV2.publicSearch(view.id, undefined, { status: 401 })
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should ignore the view permissions if the flag is not on", async () => {
|
it("should ignore the view permissions if the flag is not on", async () => {
|
||||||
|
@ -221,7 +221,7 @@ describe("/permission", () => {
|
||||||
await config.publish()
|
await config.publish()
|
||||||
|
|
||||||
await config.api.viewV2.publicSearch(view.id, undefined, {
|
await config.api.viewV2.publicSearch(view.id, undefined, {
|
||||||
status: 403,
|
status: 401,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -250,8 +250,8 @@ describe("/permission", () => {
|
||||||
.send(basicRow(table._id))
|
.send(basicRow(table._id))
|
||||||
.set(config.publicHeaders())
|
.set(config.publicHeaders())
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(403)
|
.expect(401)
|
||||||
expect(res.status).toEqual(403)
|
expect(res.status).toEqual(401)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -819,7 +819,10 @@ describe.each([
|
||||||
const table = await config.api.table.save(tableRequest)
|
const table = await config.api.table.save(tableRequest)
|
||||||
|
|
||||||
const stringValue = generator.word()
|
const stringValue = generator.word()
|
||||||
const naturalValue = generator.integer({ min: 0, max: 1000 })
|
|
||||||
|
// MySQL and MariaDB auto-increment fields have a minimum value of 1. If
|
||||||
|
// you try to save a row with a value of 0 it will use 1 instead.
|
||||||
|
const naturalValue = generator.integer({ min: 1, max: 1000 })
|
||||||
|
|
||||||
const existing = await config.api.row.save(table._id!, {
|
const existing = await config.api.row.save(table._id!, {
|
||||||
string: stringValue,
|
string: stringValue,
|
||||||
|
@ -1457,17 +1460,12 @@ describe.each([
|
||||||
delete tableRequest.schema.id
|
delete tableRequest.schema.id
|
||||||
|
|
||||||
const table = await config.api.table.save(tableRequest)
|
const table = await config.api.table.save(tableRequest)
|
||||||
|
const toCreate = generator
|
||||||
|
.unique(() => generator.integer({ min: 0, max: 10000 }), 10)
|
||||||
|
.map(number => ({ number, string: generator.word({ length: 30 }) }))
|
||||||
|
|
||||||
const rows = await Promise.all(
|
const rows = await Promise.all(
|
||||||
generator
|
toCreate.map(d => config.api.row.save(table._id!, d))
|
||||||
.unique(
|
|
||||||
() => ({
|
|
||||||
string: generator.word({ length: 30 }),
|
|
||||||
number: generator.integer({ min: 0, max: 10000 }),
|
|
||||||
}),
|
|
||||||
10
|
|
||||||
)
|
|
||||||
.map(d => config.api.row.save(table._id!, d))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const res = await config.api.row.exportRows(table._id!, {
|
const res = await config.api.row.exportRows(table._id!, {
|
||||||
|
|
|
@ -4,7 +4,12 @@ import {
|
||||||
getDatasource,
|
getDatasource,
|
||||||
knexClient,
|
knexClient,
|
||||||
} from "../../../integrations/tests/utils"
|
} from "../../../integrations/tests/utils"
|
||||||
import { db as dbCore, utils } from "@budibase/backend-core"
|
import {
|
||||||
|
db as dbCore,
|
||||||
|
MAX_VALID_DATE,
|
||||||
|
MIN_VALID_DATE,
|
||||||
|
utils,
|
||||||
|
} from "@budibase/backend-core"
|
||||||
|
|
||||||
import * as setup from "./utilities"
|
import * as setup from "./utilities"
|
||||||
import {
|
import {
|
||||||
|
@ -1098,19 +1103,35 @@ describe.each([
|
||||||
}).toFindNothing()
|
}).toFindNothing()
|
||||||
})
|
})
|
||||||
|
|
||||||
// We never implemented half-open ranges in Lucene.
|
it("greater than equal to", async () => {
|
||||||
!isLucene &&
|
|
||||||
it("can search using just a low value", async () => {
|
|
||||||
await expectQuery({
|
await expectQuery({
|
||||||
range: { age: { low: 5 } },
|
range: {
|
||||||
|
age: { low: 10, high: Number.MAX_SAFE_INTEGER },
|
||||||
|
},
|
||||||
}).toContainExactly([{ age: 10 }])
|
}).toContainExactly([{ age: 10 }])
|
||||||
})
|
})
|
||||||
|
|
||||||
// We never implemented half-open ranges in Lucene.
|
it("greater than", async () => {
|
||||||
!isLucene &&
|
|
||||||
it("can search using just a high value", async () => {
|
|
||||||
await expectQuery({
|
await expectQuery({
|
||||||
range: { age: { high: 5 } },
|
range: {
|
||||||
|
age: { low: 5, high: Number.MAX_SAFE_INTEGER },
|
||||||
|
},
|
||||||
|
}).toContainExactly([{ age: 10 }])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("less than equal to", async () => {
|
||||||
|
await expectQuery({
|
||||||
|
range: {
|
||||||
|
age: { high: 1, low: Number.MIN_SAFE_INTEGER },
|
||||||
|
},
|
||||||
|
}).toContainExactly([{ age: 1 }])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("less than", async () => {
|
||||||
|
await expectQuery({
|
||||||
|
range: {
|
||||||
|
age: { high: 5, low: Number.MIN_SAFE_INTEGER },
|
||||||
|
},
|
||||||
}).toContainExactly([{ age: 1 }])
|
}).toContainExactly([{ age: 1 }])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1232,19 +1253,27 @@ describe.each([
|
||||||
}).toFindNothing()
|
}).toFindNothing()
|
||||||
})
|
})
|
||||||
|
|
||||||
// We never implemented half-open ranges in Lucene.
|
it("greater than equal to", async () => {
|
||||||
!isLucene &&
|
|
||||||
it("can search using just a low value", async () => {
|
|
||||||
await expectQuery({
|
await expectQuery({
|
||||||
range: { dob: { low: JAN_5TH } },
|
range: { dob: { low: JAN_10TH, high: MAX_VALID_DATE.toISOString() } },
|
||||||
}).toContainExactly([{ dob: JAN_10TH }])
|
}).toContainExactly([{ dob: JAN_10TH }])
|
||||||
})
|
})
|
||||||
|
|
||||||
// We never implemented half-open ranges in Lucene.
|
it("greater than", async () => {
|
||||||
!isLucene &&
|
|
||||||
it("can search using just a high value", async () => {
|
|
||||||
await expectQuery({
|
await expectQuery({
|
||||||
range: { dob: { high: JAN_5TH } },
|
range: { dob: { low: JAN_5TH, high: MAX_VALID_DATE.toISOString() } },
|
||||||
|
}).toContainExactly([{ dob: JAN_10TH }])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("less than equal to", async () => {
|
||||||
|
await expectQuery({
|
||||||
|
range: { dob: { high: JAN_1ST, low: MIN_VALID_DATE.toISOString() } },
|
||||||
|
}).toContainExactly([{ dob: JAN_1ST }])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("less than", async () => {
|
||||||
|
await expectQuery({
|
||||||
|
range: { dob: { high: JAN_5TH, low: MIN_VALID_DATE.toISOString() } },
|
||||||
}).toContainExactly([{ dob: JAN_1ST }])
|
}).toContainExactly([{ dob: JAN_1ST }])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -151,7 +151,7 @@ export const checkPermissionsEndpoint = async ({
|
||||||
await exports
|
await exports
|
||||||
.createRequest(config.request, method, url, body)
|
.createRequest(config.request, method, url, body)
|
||||||
.set(failHeader)
|
.set(failHeader)
|
||||||
.expect(403)
|
.expect(401)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getDB = () => {
|
export const getDB = () => {
|
||||||
|
|
|
@ -1490,7 +1490,7 @@ describe.each([
|
||||||
it("does not allow public users to fetch by default", async () => {
|
it("does not allow public users to fetch by default", async () => {
|
||||||
await config.publish()
|
await config.publish()
|
||||||
await config.api.viewV2.publicSearch(view.id, undefined, {
|
await config.api.viewV2.publicSearch(view.id, undefined, {
|
||||||
status: 403,
|
status: 401,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1534,7 +1534,7 @@ describe.each([
|
||||||
await config.publish()
|
await config.publish()
|
||||||
|
|
||||||
await config.api.viewV2.publicSearch(view.id, undefined, {
|
await config.api.viewV2.publicSearch(view.id, undefined, {
|
||||||
status: 403,
|
status: 401,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { Thread, ThreadType } from "../threads"
|
import { Thread, ThreadType } from "../threads"
|
||||||
import { definitions } from "./triggerInfo"
|
import { definitions } from "./triggerInfo"
|
||||||
import { automationQueue } from "./bullboard"
|
import { automationQueue } from "./bullboard"
|
||||||
import newid from "../db/newid"
|
|
||||||
import { updateEntityMetadata } from "../utilities"
|
import { updateEntityMetadata } from "../utilities"
|
||||||
import { MetadataTypes } from "../constants"
|
import { MetadataTypes } from "../constants"
|
||||||
import { db as dbCore, context } from "@budibase/backend-core"
|
import { db as dbCore, context, utils } from "@budibase/backend-core"
|
||||||
import { getAutomationMetadataParams } from "../db/utils"
|
import { getAutomationMetadataParams } from "../db/utils"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { quotas } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
|
@ -207,7 +206,7 @@ export async function enableCronTrigger(appId: any, automation: Automation) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// make a job id rather than letting Bull decide, makes it easier to handle on way out
|
// make a job id rather than letting Bull decide, makes it easier to handle on way out
|
||||||
const jobId = `${appId}_cron_${newid()}`
|
const jobId = `${appId}_cron_${utils.newid()}`
|
||||||
const job: any = await automationQueue.add(
|
const job: any = await automationQueue.add(
|
||||||
{
|
{
|
||||||
automation,
|
automation,
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import newid from "./newid"
|
|
||||||
import { Row, Document, DBView } 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
|
||||||
import { db as dbCore } from "@budibase/backend-core"
|
import { db as dbCore, utils } from "@budibase/backend-core"
|
||||||
|
|
||||||
const Pouch = dbCore.getPouch({ inMemory: true })
|
const Pouch = dbCore.getPouch({ inMemory: true })
|
||||||
|
|
||||||
|
@ -16,7 +15,7 @@ export async function runView(
|
||||||
// use a different ID each time for the DB, make sure they
|
// use a different ID each time for the DB, make sure they
|
||||||
// are always unique for each query, don't want overlap
|
// are always unique for each query, don't want overlap
|
||||||
// which could cause 409s
|
// which could cause 409s
|
||||||
const db = new Pouch(newid())
|
const db = new Pouch(utils.newid())
|
||||||
try {
|
try {
|
||||||
// write all the docs to the in memory Pouch (remove revs)
|
// write all the docs to the in memory Pouch (remove revs)
|
||||||
await db.bulkDocs(
|
await db.bulkDocs(
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
import { v4 } from "uuid"
|
|
||||||
|
|
||||||
export default function (): string {
|
|
||||||
return v4().replace(/-/g, "")
|
|
||||||
}
|
|
|
@ -1,5 +1,4 @@
|
||||||
import newid from "./newid"
|
import { context, db as dbCore, utils } from "@budibase/backend-core"
|
||||||
import { context, db as dbCore } from "@budibase/backend-core"
|
|
||||||
import {
|
import {
|
||||||
DatabaseQueryOpts,
|
DatabaseQueryOpts,
|
||||||
Datasource,
|
Datasource,
|
||||||
|
@ -15,6 +14,8 @@ import {
|
||||||
|
|
||||||
export { DocumentType, VirtualDocumentType } from "@budibase/types"
|
export { DocumentType, VirtualDocumentType } from "@budibase/types"
|
||||||
|
|
||||||
|
const newid = utils.newid
|
||||||
|
|
||||||
type Optional = string | null
|
type Optional = string | null
|
||||||
|
|
||||||
export const enum AppStatus {
|
export const enum AppStatus {
|
||||||
|
|
|
@ -96,7 +96,7 @@ const authorized =
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ctx.user) {
|
if (!ctx.user) {
|
||||||
return ctx.throw(403, "No user info found")
|
return ctx.throw(401, "No user info found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the resource roles
|
// get the resource roles
|
||||||
|
@ -148,7 +148,7 @@ const authorized =
|
||||||
|
|
||||||
// check authenticated
|
// check authenticated
|
||||||
if (!ctx.isAuthenticated) {
|
if (!ctx.isAuthenticated) {
|
||||||
return ctx.throw(403, "Session not authenticated")
|
return ctx.throw(401, "Session not authenticated")
|
||||||
}
|
}
|
||||||
|
|
||||||
// check general builder stuff, this middleware is a good way
|
// check general builder stuff, this middleware is a good way
|
||||||
|
|
|
@ -105,7 +105,7 @@ describe("Authorization middleware", () => {
|
||||||
it("throws when no user data is present in context", async () => {
|
it("throws when no user data is present in context", async () => {
|
||||||
await config.executeMiddleware()
|
await config.executeMiddleware()
|
||||||
|
|
||||||
expect(config.throw).toHaveBeenCalledWith(403, "No user info found")
|
expect(config.throw).toHaveBeenCalledWith(401, "No user info found")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("passes on to next() middleware if user is an admin", async () => {
|
it("passes on to next() middleware if user is an admin", async () => {
|
||||||
|
@ -157,7 +157,7 @@ describe("Authorization middleware", () => {
|
||||||
|
|
||||||
await config.executeMiddleware()
|
await config.executeMiddleware()
|
||||||
expect(config.throw).toHaveBeenCalledWith(
|
expect(config.throw).toHaveBeenCalledWith(
|
||||||
403,
|
401,
|
||||||
"Session not authenticated"
|
"Session not authenticated"
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -45,13 +45,10 @@ import {
|
||||||
getTableIDList,
|
getTableIDList,
|
||||||
} from "./filters"
|
} from "./filters"
|
||||||
import { dataFilters } from "@budibase/shared-core"
|
import { dataFilters } from "@budibase/shared-core"
|
||||||
import { DEFAULT_TABLE_IDS } from "../../../../constants"
|
|
||||||
|
|
||||||
const builder = new sql.Sql(SqlClient.SQL_LITE)
|
const builder = new sql.Sql(SqlClient.SQL_LITE)
|
||||||
const MISSING_COLUMN_REGEX = new RegExp(`no such column: .+`)
|
const MISSING_COLUMN_REGEX = new RegExp(`no such column: .+`)
|
||||||
const USER_COLUMN_PREFIX_REGEX = new RegExp(
|
const MISSING_TABLE_REGX = new RegExp(`no such table: .+`)
|
||||||
`no such column: .+${USER_COLUMN_PREFIX}`
|
|
||||||
)
|
|
||||||
|
|
||||||
function buildInternalFieldList(
|
function buildInternalFieldList(
|
||||||
table: Table,
|
table: Table,
|
||||||
|
@ -240,10 +237,10 @@ async function runSqlQuery(
|
||||||
function resyncDefinitionsRequired(status: number, message: string) {
|
function resyncDefinitionsRequired(status: number, message: string) {
|
||||||
// pre data_ prefix on column names, need to resync
|
// pre data_ prefix on column names, need to resync
|
||||||
return (
|
return (
|
||||||
(status === 400 && message?.match(USER_COLUMN_PREFIX_REGEX)) ||
|
// there are tables missing - try a resync
|
||||||
// default tables aren't included in definition
|
(status === 400 && message.match(MISSING_TABLE_REGX)) ||
|
||||||
(status === 400 &&
|
// there are columns missing - try a resync
|
||||||
DEFAULT_TABLE_IDS.find(tableId => message?.includes(tableId))) ||
|
(status === 400 && message.match(MISSING_COLUMN_REGEX)) ||
|
||||||
// no design document found, needs a full sync
|
// no design document found, needs a full sync
|
||||||
(status === 404 && message?.includes(SQLITE_DESIGN_DOC_ID))
|
(status === 404 && message?.includes(SQLITE_DESIGN_DOC_ID))
|
||||||
)
|
)
|
||||||
|
@ -251,7 +248,8 @@ function resyncDefinitionsRequired(status: number, message: string) {
|
||||||
|
|
||||||
export async function search(
|
export async function search(
|
||||||
options: RowSearchParams,
|
options: RowSearchParams,
|
||||||
table: Table
|
table: Table,
|
||||||
|
opts?: { retrying?: boolean }
|
||||||
): Promise<SearchResponse<Row>> {
|
): Promise<SearchResponse<Row>> {
|
||||||
let { paginate, query, ...params } = options
|
let { paginate, query, ...params } = options
|
||||||
|
|
||||||
|
@ -376,9 +374,9 @@ export async function search(
|
||||||
return response
|
return response
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const msg = typeof err === "string" ? err : err.message
|
const msg = typeof err === "string" ? err : err.message
|
||||||
if (resyncDefinitionsRequired(err.status, msg)) {
|
if (!opts?.retrying && resyncDefinitionsRequired(err.status, msg)) {
|
||||||
await sdk.tables.sqs.syncDefinition()
|
await sdk.tables.sqs.syncDefinition()
|
||||||
return search(options, table)
|
return search(options, table, { retrying: true })
|
||||||
}
|
}
|
||||||
// previously the internal table didn't error when a column didn't exist in search
|
// previously the internal table didn't error when a column didn't exist in search
|
||||||
if (err.status === 400 && msg?.match(MISSING_COLUMN_REGEX)) {
|
if (err.status === 400 && msg?.match(MISSING_COLUMN_REGEX)) {
|
||||||
|
|
|
@ -127,9 +127,14 @@ function mapTable(table: Table): SQLiteTables {
|
||||||
// nothing exists, need to iterate though existing tables
|
// nothing exists, need to iterate though existing tables
|
||||||
async function buildBaseDefinition(): Promise<PreSaveSQLiteDefinition> {
|
async function buildBaseDefinition(): Promise<PreSaveSQLiteDefinition> {
|
||||||
const tables = await tablesSdk.getAllInternalTables()
|
const tables = await tablesSdk.getAllInternalTables()
|
||||||
const defaultTables = DEFAULT_TABLES
|
for (const defaultTable of DEFAULT_TABLES) {
|
||||||
|
// the default table doesn't exist in Couch, use the in-memory representation
|
||||||
|
if (!tables.find(table => table._id === defaultTable._id)) {
|
||||||
|
tables.push(defaultTable)
|
||||||
|
}
|
||||||
|
}
|
||||||
const definition = sql.designDoc.base("tableId")
|
const definition = sql.designDoc.base("tableId")
|
||||||
for (let table of tables.concat(defaultTables)) {
|
for (let table of tables) {
|
||||||
definition.sql.tables = {
|
definition.sql.tables = {
|
||||||
...definition.sql.tables,
|
...definition.sql.tables,
|
||||||
...mapTable(table),
|
...mapTable(table),
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {
|
||||||
roles,
|
roles,
|
||||||
sessions,
|
sessions,
|
||||||
tenancy,
|
tenancy,
|
||||||
|
utils,
|
||||||
} from "@budibase/backend-core"
|
} from "@budibase/backend-core"
|
||||||
import {
|
import {
|
||||||
app as appController,
|
app as appController,
|
||||||
|
@ -40,7 +41,6 @@ import {
|
||||||
} from "./controllers"
|
} from "./controllers"
|
||||||
|
|
||||||
import { cleanup } from "../../utilities/fileSystem"
|
import { cleanup } from "../../utilities/fileSystem"
|
||||||
import newid from "../../db/newid"
|
|
||||||
import { generateUserMetadataID } from "../../db/utils"
|
import { generateUserMetadataID } from "../../db/utils"
|
||||||
import { startup } from "../../startup"
|
import { startup } from "../../startup"
|
||||||
import supertest from "supertest"
|
import supertest from "supertest"
|
||||||
|
@ -74,6 +74,8 @@ import { cloneDeep } from "lodash"
|
||||||
import jwt, { Secret } from "jsonwebtoken"
|
import jwt, { Secret } from "jsonwebtoken"
|
||||||
import { Server } from "http"
|
import { Server } from "http"
|
||||||
|
|
||||||
|
const newid = utils.newid
|
||||||
|
|
||||||
mocks.licenses.init(pro)
|
mocks.licenses.init(pro)
|
||||||
|
|
||||||
// use unlimited license by default
|
// use unlimited license by default
|
||||||
|
|
|
@ -106,7 +106,7 @@ export const NoEmptyFilterStrings = [
|
||||||
OperatorOptions.NotEquals.value,
|
OperatorOptions.NotEquals.value,
|
||||||
OperatorOptions.Contains.value,
|
OperatorOptions.Contains.value,
|
||||||
OperatorOptions.NotContains.value,
|
OperatorOptions.NotContains.value,
|
||||||
OperatorOptions.NotContains.value,
|
OperatorOptions.ContainsAny.value,
|
||||||
OperatorOptions.In.value,
|
OperatorOptions.In.value,
|
||||||
] as (keyof SearchQueryFields)[]
|
] as (keyof SearchQueryFields)[]
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
const nodemailer = require("nodemailer")
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
port: 587,
|
||||||
|
host: "smtp.ethereal.email",
|
||||||
|
secure: false,
|
||||||
|
auth: {
|
||||||
|
user: "seamus99@ethereal.email",
|
||||||
|
pass: "5ghVteZAqj6jkKJF9R",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const transporter = nodemailer.createTransport(options)
|
||||||
|
transporter.verify(function (error) {
|
||||||
|
if (error) {
|
||||||
|
console.log(error)
|
||||||
|
} else {
|
||||||
|
console.log("Ethereal server is ready to take our messages")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const message = {
|
||||||
|
from: "from@example.com",
|
||||||
|
to: "to@example.com",
|
||||||
|
subject: "Did this email arrive?",
|
||||||
|
html: "Hello World!",
|
||||||
|
}
|
||||||
|
|
||||||
|
transporter.sendMail(message).then(response => {
|
||||||
|
console.log("Test email URL: " + nodemailer.getTestMessageUrl(response))
|
||||||
|
})
|
Loading…
Reference in New Issue