Merge branch 'master' of github.com:Budibase/budibase into feature/sqs-table-cleanup

This commit is contained in:
mike12345567 2024-05-08 12:36:42 +01:00
commit 2e8b655417
14 changed files with 176 additions and 7 deletions

View File

@ -431,10 +431,28 @@ export class QueryBuilder<T> {
}) })
} }
if (this.#query.empty) { if (this.#query.empty) {
build(this.#query.empty, (key: string) => `(*:* -${key}:["" TO *])`) build(this.#query.empty, (key: string) => {
// Because the structure of an empty filter looks like this:
// { empty: { someKey: null } }
//
// The check inside of `build` does not set `allFiltersEmpty`, which results
// in weird behaviour when the empty filter is the only filter. We get around
// this by setting `allFiltersEmpty` to false here.
allFiltersEmpty = false
return `(*:* -${key}:["" TO *])`
})
} }
if (this.#query.notEmpty) { if (this.#query.notEmpty) {
build(this.#query.notEmpty, (key: string) => `${key}:["" TO *]`) build(this.#query.notEmpty, (key: string) => {
// Because the structure of a notEmpty filter looks like this:
// { notEmpty: { someKey: null } }
//
// The check inside of `build` does not set `allFiltersEmpty`, which results
// in weird behaviour when the empty filter is the only filter. We get around
// this by setting `allFiltersEmpty` to false here.
allFiltersEmpty = false
return `${key}:["" TO *]`
})
} }
if (this.#query.oneOf) { if (this.#query.oneOf) {
build(this.#query.oneOf, oneOf) build(this.#query.oneOf, oneOf)

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 804 KiB

View File

@ -0,0 +1,64 @@
<script>
import { Modal, ModalContent } from "@budibase/bbui"
import FreeTrial from "../../../../assets/FreeTrial.svelte"
import { get } from "svelte/store"
import { auth, licensing } from "stores/portal"
import { API } from "api"
import { PlanType } from "@budibase/types"
let freeTrialModal
$: planType = $licensing?.license?.plan?.type
$: showFreeTrialModal(planType, freeTrialModal)
const showFreeTrialModal = (planType, freeTrialModal) => {
if (
planType === PlanType.ENTERPRISE_BASIC_TRIAL &&
!$auth.user?.freeTrialConfirmedAt
) {
freeTrialModal?.show()
}
}
</script>
<Modal bind:this={freeTrialModal} disableCancel={true}>
<ModalContent
confirmText="Get started"
size="M"
showCancelButton={false}
showCloseIcon={false}
onConfirm={async () => {
if (get(auth).user) {
try {
await API.updateSelf({
freeTrialConfirmedAt: new Date().toISOString(),
})
// Update the cached user
await auth.getSelf()
} finally {
freeTrialModal.hide()
}
}
}}
>
<h1>Experience all of Budibase with a free 14-day trial</h1>
<div class="free-trial-text">
We've upgraded you to a free 14-day trial that allows you to try all our
features before deciding which plan is right for you.
<p>
At the end of your trial, we'll automatically downgrade you to the Free
plan unless you choose to upgrade.
</p>
</div>
<FreeTrial />
</ModalContent>
</Modal>
<style>
h1 {
font-size: 26px;
}
.free-trial-text {
font-size: 16px;
}
</style>

View File

@ -20,6 +20,9 @@ export function getFormattedPlanName(userPlanType) {
case PlanType.ENTERPRISE: case PlanType.ENTERPRISE:
planName = "Enterprise" planName = "Enterprise"
break break
case PlanType.ENTERPRISE_BASIC_TRIAL:
planName = "Trial"
break
default: default:
planName = "Free" // Default to "Free" if the type is not explicitly handled planName = "Free" // Default to "Free" if the type is not explicitly handled
} }

View File

@ -32,6 +32,7 @@
import { UserAvatars } from "@budibase/frontend-core" import { UserAvatars } from "@budibase/frontend-core"
import { TOUR_KEYS } from "components/portal/onboarding/tours.js" import { TOUR_KEYS } from "components/portal/onboarding/tours.js"
import PreviewOverlay from "./_components/PreviewOverlay.svelte" import PreviewOverlay from "./_components/PreviewOverlay.svelte"
import EnterpriseBasicTrialModal from "components/portal/onboarding/EnterpriseBasicTrialModal.svelte"
export let application export let application
@ -192,6 +193,8 @@
<CommandPalette /> <CommandPalette />
</Modal> </Modal>
<EnterpriseBasicTrialModal />
<style> <style>
.back-to-apps { .back-to-apps {
display: contents; display: contents;

View File

@ -103,6 +103,8 @@ export const createLicensingStore = () => {
const isEnterprisePlan = planType === Constants.PlanType.ENTERPRISE const isEnterprisePlan = planType === Constants.PlanType.ENTERPRISE
const isFreePlan = planType === Constants.PlanType.FREE const isFreePlan = planType === Constants.PlanType.FREE
const isBusinessPlan = planType === Constants.PlanType.BUSINESS const isBusinessPlan = planType === Constants.PlanType.BUSINESS
const isEnterpriseTrial =
planType === Constants.PlanType.ENTERPRISE_BASIC_TRIAL
const groupsEnabled = license.features.includes( const groupsEnabled = license.features.includes(
Constants.Features.USER_GROUPS Constants.Features.USER_GROUPS
) )
@ -143,6 +145,7 @@ export const createLicensingStore = () => {
isEnterprisePlan, isEnterprisePlan,
isFreePlan, isFreePlan,
isBusinessPlan, isBusinessPlan,
isEnterpriseTrial,
groupsEnabled, groupsEnabled,
backupsEnabled, backupsEnabled,
brandingEnabled, brandingEnabled,

View File

@ -4220,8 +4220,8 @@
] ]
}, },
"attachmentfield": { "attachmentfield": {
"name": "Attachment list", "name": "Attachment List",
"icon": "Attach", "icon": "DocumentFragmentGroup",
"styles": ["size"], "styles": ["size"],
"requiredAncestors": ["form"], "requiredAncestors": ["form"],
"editable": true, "editable": true,
@ -4318,7 +4318,7 @@
}, },
"attachmentsinglefield": { "attachmentsinglefield": {
"name": "Single Attachment", "name": "Single Attachment",
"icon": "Attach", "icon": "DocumentFragment",
"styles": ["size"], "styles": ["size"],
"requiredAncestors": ["form"], "requiredAncestors": ["form"],
"editable": true, "editable": true,

View File

@ -57,6 +57,7 @@ export const PlanType = {
PRO: "pro", PRO: "pro",
BUSINESS: "business", BUSINESS: "business",
ENTERPRISE: "enterprise", ENTERPRISE: "enterprise",
ENTERPRISE_BASIC_TRIAL: "enterprise_basic_trial",
} }
/** /**
@ -124,8 +125,8 @@ export const TypeIconMap = {
[FieldType.ARRAY]: "Duplicate", [FieldType.ARRAY]: "Duplicate",
[FieldType.NUMBER]: "123", [FieldType.NUMBER]: "123",
[FieldType.BOOLEAN]: "Boolean", [FieldType.BOOLEAN]: "Boolean",
[FieldType.ATTACHMENTS]: "Attach", [FieldType.ATTACHMENTS]: "DocumentFragmentGroup",
[FieldType.ATTACHMENT_SINGLE]: "Attach", [FieldType.ATTACHMENT_SINGLE]: "DocumentFragment",
[FieldType.LINK]: "DataCorrelated", [FieldType.LINK]: "DataCorrelated",
[FieldType.FORMULA]: "Calculator", [FieldType.FORMULA]: "Calculator",
[FieldType.JSON]: "Brackets", [FieldType.JSON]: "Brackets",

View File

@ -252,6 +252,31 @@ describe.each([
}).toFindNothing()) }).toFindNothing())
}) })
describe("empty", () => {
it("finds no empty rows", () =>
expectQuery({ empty: { name: null } }).toFindNothing())
it("should not be affected by when filter empty behaviour", () =>
expectQuery({
empty: { name: null },
onEmptyFilter: EmptyFilterOption.RETURN_ALL,
}).toFindNothing())
})
describe("notEmpty", () => {
it("finds all non-empty rows", () =>
expectQuery({ notEmpty: { name: null } }).toContainExactly([
{ name: "foo" },
{ name: "bar" },
]))
it("should not be affected by when filter empty behaviour", () =>
expectQuery({
notEmpty: { name: null },
onEmptyFilter: EmptyFilterOption.RETURN_NONE,
}).toContainExactly([{ name: "foo" }, { name: "bar" }]))
})
describe("sort", () => { describe("sort", () => {
it("sorts ascending", () => it("sorts ascending", () =>
expectSearch({ expectSearch({

View File

@ -18,6 +18,7 @@ export interface UpdateSelfRequest {
password?: string password?: string
forceResetPassword?: boolean forceResetPassword?: boolean
onboardedAt?: string onboardedAt?: string
freeTrialConfirmedAt?: string
appFavourites?: string[] appFavourites?: string[]
tours?: Record<string, Date> tours?: Record<string, Date>
} }

View File

@ -62,6 +62,7 @@ export interface User extends Document {
dayPassRecordedAt?: string dayPassRecordedAt?: string
userGroups?: string[] userGroups?: string[]
onboardedAt?: string onboardedAt?: string
freeTrialConfirmedAt?: string
tours?: Record<string, Date> tours?: Record<string, Date>
scimInfo?: { isSync: true } & Record<string, any> scimInfo?: { isSync: true } & Record<string, any>
appFavourites?: string[] appFavourites?: string[]

View File

@ -55,6 +55,7 @@ describe("/api/global/self", () => {
const res = await config.api.self const res = await config.api.self
.updateSelf(user, { .updateSelf(user, {
onboardedAt: "2023-03-07T14:10:54.869Z", onboardedAt: "2023-03-07T14:10:54.869Z",
freeTrialConfirmedAt: "2024-03-17T14:10:54.869Z",
}) })
.expect(200) .expect(200)
@ -63,6 +64,7 @@ describe("/api/global/self", () => {
user._rev = dbUser._rev user._rev = dbUser._rev
user.dayPassRecordedAt = mocks.date.MOCK_DATE.toISOString() user.dayPassRecordedAt = mocks.date.MOCK_DATE.toISOString()
expect(dbUser.onboardedAt).toBe("2023-03-07T14:10:54.869Z") expect(dbUser.onboardedAt).toBe("2023-03-07T14:10:54.869Z")
expect(dbUser.freeTrialConfirmedAt).toBe("2024-03-17T14:10:54.869Z")
expect(res.body._id).toBe(user._id) expect(res.body._id).toBe(user._id)
}) })
}) })

View File

@ -26,6 +26,7 @@ export const buildSelfSaveValidation = () => {
firstName: OPTIONAL_STRING, firstName: OPTIONAL_STRING,
lastName: OPTIONAL_STRING, lastName: OPTIONAL_STRING,
onboardedAt: Joi.string().optional(), onboardedAt: Joi.string().optional(),
freeTrialConfirmedAt: Joi.string().optional(),
appFavourites: Joi.array().optional(), appFavourites: Joi.array().optional(),
tours: Joi.object().optional(), tours: Joi.object().optional(),
} }

View File

@ -22,6 +22,7 @@ cd src/main/resources/models
echo "deploy processes..." echo "deploy processes..."
zbctl deploy resource offboarding.bpmn --insecure zbctl deploy resource offboarding.bpmn --insecure
zbctl deploy resource onboarding.bpmn --insecure zbctl deploy resource onboarding.bpmn --insecure
zbctl deploy resource free_trial.bpmn --insecure
cd ../../../../../budibase/packages/account-portal/packages/server cd ../../../../../budibase/packages/account-portal/packages/server