Merge branch 'develop' into account-api-tests

This commit is contained in:
Rory Powell 2023-07-20 11:03:43 +01:00
commit 4e296b8d1f
17 changed files with 210 additions and 185 deletions

View File

@ -98,7 +98,7 @@ jobs:
git fetch git fetch
mkdir sync mkdir sync
echo "Packaging chart to sync dir" echo "Packaging chart to sync dir"
helm package charts/budibase --version 0.0.0-master --app-version v"$RELEASE_VERSION" --destination sync helm package charts/budibase --version 0.0.0-master --app-version "$RELEASE_VERSION" --destination sync
echo "Packaging successful" echo "Packaging successful"
git checkout gh-pages git checkout gh-pages
echo "Indexing helm repo" echo "Indexing helm repo"

View File

@ -43,7 +43,7 @@ jobs:
run: | run: |
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
release_tag=v${{ env.RELEASE_VERSION }} release_tag=${{ env.RELEASE_VERSION }}
# Pull apps and worker images # Pull apps and worker images
docker pull budibase/apps:$release_tag docker pull budibase/apps:$release_tag
@ -108,8 +108,8 @@ jobs:
- name: Perform Github Release - name: Perform Github Release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:
name: v${{ env.RELEASE_VERSION }} name: ${{ env.RELEASE_VERSION }}
tag_name: v${{ env.RELEASE_VERSION }} tag_name: ${{ env.RELEASE_VERSION }}
generate_release_notes: true generate_release_notes: true
files: | files: |
packages/cli/build/cli-win.exe packages/cli/build/cli-win.exe

View File

@ -71,7 +71,7 @@ jobs:
context: . context: .
push: true push: true
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
tags: budibase/budibase,budibase/budibase:v${{ env.RELEASE_VERSION }} tags: budibase/budibase,budibase/budibase:${{ env.RELEASE_VERSION }}
file: ./hosting/single/Dockerfile file: ./hosting/single/Dockerfile
- name: Tag and release Budibase Azure App Service docker image - name: Tag and release Budibase Azure App Service docker image
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
@ -80,5 +80,5 @@ jobs:
push: true push: true
platforms: linux/amd64 platforms: linux/amd64
build-args: TARGETBUILD=aas build-args: TARGETBUILD=aas
tags: budibase/budibase-aas,budibase/budibase-aas:v${{ env.RELEASE_VERSION }} tags: budibase/budibase-aas,budibase/budibase-aas:${{ env.RELEASE_VERSION }}
file: ./hosting/single/Dockerfile file: ./hosting/single/Dockerfile

View File

@ -209,7 +209,7 @@ services:
# Override values in couchDB subchart # Override values in couchDB subchart
couchdb: couchdb:
## clusterSize is the initial size of the CouchDB cluster. ## clusterSize is the initial size of the CouchDB cluster.
clusterSize: 3 clusterSize: 1
allowAdminParty: false allowAdminParty: false
# Secret Management # Secret Management

View File

@ -1,5 +1,5 @@
{ {
"version": "2.8.12-alpha.5", "version": "2.8.16-alpha.0",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View File

@ -15,6 +15,7 @@
Icon, Icon,
Checkbox, Checkbox,
DatePicker, DatePicker,
Detail,
} from "@budibase/bbui" } from "@budibase/bbui"
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte" import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
import { automationStore, selectedAutomation } from "builderStore" import { automationStore, selectedAutomation } from "builderStore"
@ -32,7 +33,7 @@
import CodeEditor from "components/common/CodeEditor/CodeEditor.svelte" import CodeEditor from "components/common/CodeEditor/CodeEditor.svelte"
import { import {
bindingsToCompletions, bindingsToCompletions,
jsAutocomplete, hbAutocomplete,
EditorModes, EditorModes,
} from "components/common/CodeEditor" } from "components/common/CodeEditor"
import FilterDrawer from "components/design/settings/controls/FilterEditor/FilterDrawer.svelte" import FilterDrawer from "components/design/settings/controls/FilterEditor/FilterDrawer.svelte"
@ -55,6 +56,7 @@
let drawer let drawer
let fillWidth = true let fillWidth = true
let inputData let inputData
let codeBindingOpen = false
$: filters = lookForFilters(schemaProperties) || [] $: filters = lookForFilters(schemaProperties) || []
$: tempFilters = filters $: tempFilters = filters
@ -70,6 +72,13 @@
$: queryLimit = tableId?.includes("datasource") ? "∞" : "1000" $: queryLimit = tableId?.includes("datasource") ? "∞" : "1000"
$: isTrigger = block?.type === "TRIGGER" $: isTrigger = block?.type === "TRIGGER"
$: isUpdateRow = stepId === ActionStepID.UPDATE_ROW $: isUpdateRow = stepId === ActionStepID.UPDATE_ROW
$: codeMode =
stepId === "EXECUTE_BASH" ? EditorModes.Handlebars : EditorModes.JS
$: stepCompletions =
codeMode === EditorModes.Handlebars
? [hbAutocomplete([...bindingsToCompletions(bindings, codeMode)])]
: []
/** /**
* TODO - Remove after November 2023 * TODO - Remove after November 2023
@ -489,6 +498,18 @@
/> />
{:else if value.customType === "code"} {:else if value.customType === "code"}
<CodeEditorModal> <CodeEditorModal>
{#if codeMode == EditorModes.JS}
<ActionButton
on:click={() => (codeBindingOpen = !codeBindingOpen)}
quiet
icon={codeBindingOpen ? "ChevronDown" : "ChevronRight"}
>
<Detail size="S">Bindings</Detail>
</ActionButton>
{#if codeBindingOpen}
<pre>{JSON.stringify(bindings, null, 2)}</pre>
{/if}
{/if}
<CodeEditor <CodeEditor
value={inputData[key]} value={inputData[key]}
on:change={e => { on:change={e => {
@ -496,20 +517,23 @@
onChange({ detail: e.detail }, key) onChange({ detail: e.detail }, key)
inputData[key] = e.detail inputData[key] = e.detail
}} }}
completions={[ completions={stepCompletions}
jsAutocomplete([ mode={codeMode}
...bindingsToCompletions(bindings, EditorModes.JS), autocompleteEnabled={codeMode != EditorModes.JS}
]),
]}
mode={EditorModes.JS}
height={500} height={500}
/> />
<div class="messaging"> <div class="messaging">
{#if codeMode == EditorModes.Handlebars}
<Icon name="FlashOn" /> <Icon name="FlashOn" />
<div class="messaging-wrap"> <div class="messaging-wrap">
<div>Add available bindings by typing <strong>$</strong></div> <div>
Add available bindings by typing <strong>
&#125;&#125;
</strong>
</div> </div>
</div> </div>
{/if}
</div>
</CodeEditorModal> </CodeEditorModal>
{:else if value.customType === "loopOption"} {:else if value.customType === "loopOption"}
<Select <Select

View File

@ -51,6 +51,7 @@
export let mode = EditorModes.Handlebars export let mode = EditorModes.Handlebars
export let value = "" export let value = ""
export let placeholder = null export let placeholder = null
export let autocompleteEnabled = true
// Export a function to expose caret position // Export a function to expose caret position
export const getCaretPosition = () => { export const getCaretPosition = () => {
@ -150,12 +151,6 @@
syntaxHighlighting(oneDarkHighlightStyle, { fallback: true }), syntaxHighlighting(oneDarkHighlightStyle, { fallback: true }),
highlightActiveLineGutter(), highlightActiveLineGutter(),
highlightSpecialChars(), highlightSpecialChars(),
autocompletion({
override: [...completions],
closeOnBlur: true,
icons: false,
optionClass: () => "autocomplete-option",
}),
EditorView.lineWrapping, EditorView.lineWrapping,
EditorView.updateListener.of(v => { EditorView.updateListener.of(v => {
const docStr = v.state.doc?.toString() const docStr = v.state.doc?.toString()
@ -178,11 +173,16 @@
const buildExtensions = base => { const buildExtensions = base => {
const complete = [...base] const complete = [...base]
if (mode.name == "javascript") {
complete.push(javascript()) if (autocompleteEnabled) {
complete.push(highlightWhitespace()) complete.push(
complete.push(lineNumbers()) autocompletion({
complete.push(foldGutter()) override: [...completions],
closeOnBlur: true,
icons: false,
optionClass: () => "autocomplete-option",
})
)
complete.push( complete.push(
EditorView.inputHandler.of((view, from, to, insert) => { EditorView.inputHandler.of((view, from, to, insert) => {
if (insert === "$") { if (insert === "$") {
@ -212,6 +212,13 @@
) )
} }
if (mode.name == "javascript") {
complete.push(javascript())
complete.push(highlightWhitespace())
complete.push(lineNumbers())
complete.push(foldGutter())
}
if (placeholder) { if (placeholder) {
complete.push(placeholderFn(placeholder)) complete.push(placeholderFn(placeholder))
} }

View File

@ -1,5 +1,5 @@
<script> <script>
import { Popover, Layout, Heading, Body, Button } from "@budibase/bbui" import { Popover, Layout, Heading, Body, Button, Link } from "@budibase/bbui"
import { store } from "builderStore" import { store } from "builderStore"
import { TOURS } from "./tours.js" import { TOURS } from "./tours.js"
import { goto, layout, isActive } from "@roxi/routify" import { goto, layout, isActive } from "@roxi/routify"
@ -10,17 +10,20 @@
let tourStep let tourStep
let tourStepIdx let tourStepIdx
let lastStep let lastStep
let skipping = false
$: tourNodes = { ...$store.tourNodes } $: tourNodes = { ...$store.tourNodes }
$: tourKey = $store.tourKey $: tourKey = $store.tourKey
$: tourStepKey = $store.tourStepKey $: tourStepKey = $store.tourStepKey
$: tour = TOURS[tourKey]
$: tourOnSkip = tour?.onSkip
const updateTourStep = (targetStepKey, tourKey) => { const updateTourStep = (targetStepKey, tourKey) => {
if (!tourKey) { if (!tourKey) {
return return
} }
if (!tourSteps?.length) { if (!tourSteps?.length) {
tourSteps = [...TOURS[tourKey]] tourSteps = [...tour.steps]
} }
tourStepIdx = getCurrentStepIdx(tourSteps, targetStepKey) tourStepIdx = getCurrentStepIdx(tourSteps, targetStepKey)
lastStep = tourStepIdx + 1 == tourSteps.length lastStep = tourStepIdx + 1 == tourSteps.length
@ -71,23 +74,8 @@
tourStep.onComplete() tourStep.onComplete()
} }
popover.hide() popover.hide()
if (tourStep.endRoute) { if (tour.endRoute) {
$goto(tourStep.endRoute) $goto(tour.endRoute)
}
}
}
const previousStep = async () => {
if (tourStepIdx > 0) {
let target = tourSteps[tourStepIdx - 1]
if (target) {
store.update(state => ({
...state,
tourStepKey: target.id,
}))
navigateStep(target)
} else {
console.log("Could not retrieve step")
} }
} }
} }
@ -132,16 +120,23 @@
</Body> </Body>
<div class="tour-footer"> <div class="tour-footer">
<div class="tour-navigation"> <div class="tour-navigation">
{#if tourStepIdx > 0} {#if typeof tourOnSkip === "function"}
<Button <Link
secondary secondary
on:click={previousStep} quiet
disabled={tourStepIdx == 0} on:click={() => {
skipping = true
tourOnSkip()
if (tour.endRoute) {
$goto(tour.endRoute)
}
}}
disabled={skipping}
> >
<div>Back</div> Skip
</Button> </Link>
{/if} {/if}
<Button cta on:click={nextStep}> <Button cta on:click={nextStep} disabled={skipping}>
<div>{lastStep ? "Finish" : "Next"}</div> <div>{lastStep ? "Finish" : "Next"}</div>
</Button> </Button>
</div> </div>
@ -157,9 +152,10 @@
padding: var(--spacing-xl); padding: var(--spacing-xl);
} }
.tour-navigation { .tour-navigation {
grid-gap: var(--spectrum-alias-grid-baseline); grid-gap: var(--spacing-xl);
display: flex; display: flex;
justify-content: end; justify-content: end;
align-items: center;
} }
.tour-body :global(.feature-list) { .tour-body :global(.feature-list) {
margin-bottom: 0px; margin-bottom: 0px;

View File

@ -13,7 +13,7 @@
const registerTourNode = (tourKey, stepKey) => { const registerTourNode = (tourKey, stepKey) => {
if (ready && !registered && tourKey) { if (ready && !registered && tourKey) {
currentTourStep = TOURS[tourKey].find(step => step.id === stepKey) currentTourStep = TOURS[tourKey].steps.find(step => step.id === stepKey)
if (!currentTourStep) { if (!currentTourStep) {
return return
} }

View File

@ -4,6 +4,7 @@ import { auth } from "stores/portal"
import analytics from "analytics" import analytics from "analytics"
import { OnboardingData, OnboardingDesign, OnboardingPublish } from "./steps" import { OnboardingData, OnboardingDesign, OnboardingPublish } from "./steps"
import { API } from "api" import { API } from "api"
const ONBOARDING_EVENT_PREFIX = "onboarding" const ONBOARDING_EVENT_PREFIX = "onboarding"
export const TOUR_STEP_KEYS = { export const TOUR_STEP_KEYS = {
@ -20,6 +21,37 @@ export const TOUR_KEYS = {
FEATURE_ONBOARDING: "feature-onboarding", FEATURE_ONBOARDING: "feature-onboarding",
} }
const endUserOnboarding = async ({ skipped = false } = {}) => {
// Mark the users onboarding as complete
// Clear all tour related state
if (get(auth).user) {
try {
await API.updateSelf({
onboardedAt: new Date().toISOString(),
})
if (skipped) {
tourEvent("skipped")
}
// Update the cached user
await auth.getSelf()
store.update(state => ({
...state,
tourNodes: undefined,
tourKey: undefined,
tourKeyStep: undefined,
onboarding: false,
}))
} catch (e) {
console.log("Onboarding failed", e)
return false
}
return true
}
}
const tourEvent = eventKey => { const tourEvent = eventKey => {
analytics.captureEvent(`${ONBOARDING_EVENT_PREFIX}:${eventKey}`, { analytics.captureEvent(`${ONBOARDING_EVENT_PREFIX}:${eventKey}`, {
eventSource: EventSource.PORTAL, eventSource: EventSource.PORTAL,
@ -28,7 +60,8 @@ const tourEvent = eventKey => {
const getTours = () => { const getTours = () => {
return { return {
[TOUR_KEYS.TOUR_BUILDER_ONBOARDING]: [ [TOUR_KEYS.TOUR_BUILDER_ONBOARDING]: {
steps: [
{ {
id: TOUR_STEP_KEYS.BUILDER_DATA_SECTION, id: TOUR_STEP_KEYS.BUILDER_DATA_SECTION,
title: "Data", title: "Data",
@ -76,34 +109,20 @@ const getTours = () => {
title: "Publish", title: "Publish",
layout: OnboardingPublish, layout: OnboardingPublish,
route: "/builder/app/:application/design", route: "/builder/app/:application/design",
endRoute: "/builder/app/:application/data",
query: ".toprightnav #builder-app-publish-button", query: ".toprightnav #builder-app-publish-button",
onLoad: () => { onLoad: () => {
tourEvent(TOUR_STEP_KEYS.BUILDER_APP_PUBLISH) tourEvent(TOUR_STEP_KEYS.BUILDER_APP_PUBLISH)
}, },
onComplete: async () => { onComplete: endUserOnboarding,
// Mark the users onboarding as complete
// Clear all tour related state
if (get(auth).user) {
await API.updateSelf({
onboardedAt: new Date().toISOString(),
})
// Update the cached user
await auth.getSelf()
store.update(state => ({
...state,
tourNodes: undefined,
tourKey: undefined,
tourKeyStep: undefined,
onboarding: false,
}))
}
},
}, },
], ],
[TOUR_KEYS.FEATURE_ONBOARDING]: [ onSkip: async () => {
await endUserOnboarding({ skipped: true })
},
endRoute: "/builder/app/:application/data",
},
[TOUR_KEYS.FEATURE_ONBOARDING]: {
steps: [
{ {
id: TOUR_STEP_KEYS.FEATURE_USER_MANAGEMENT, id: TOUR_STEP_KEYS.FEATURE_USER_MANAGEMENT,
title: "Users", title: "Users",
@ -112,27 +131,10 @@ const getTours = () => {
onLoad: () => { onLoad: () => {
tourEvent(TOUR_STEP_KEYS.FEATURE_USER_MANAGEMENT) tourEvent(TOUR_STEP_KEYS.FEATURE_USER_MANAGEMENT)
}, },
onComplete: async () => { onComplete: endUserOnboarding,
// Push the onboarding forward
if (get(auth).user) {
await API.updateSelf({
onboardedAt: new Date().toISOString(),
})
// Update the cached user
await auth.getSelf()
store.update(state => ({
...state,
tourNodes: undefined,
tourKey: undefined,
tourKeyStep: undefined,
onboarding: false,
}))
}
},
}, },
], ],
},
} }
} }

View File

@ -27,7 +27,7 @@
import TourPopover from "components/portal/onboarding/TourPopover.svelte" import TourPopover from "components/portal/onboarding/TourPopover.svelte"
import BuilderSidePanel from "./_components/BuilderSidePanel.svelte" import BuilderSidePanel from "./_components/BuilderSidePanel.svelte"
import { UserAvatars } from "@budibase/frontend-core" import { UserAvatars } from "@budibase/frontend-core"
import { TOUR_KEYS, TOURS } 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"
export let application export let application
@ -87,17 +87,10 @@
// Check if onboarding is enabled. // Check if onboarding is enabled.
if (isEnabled(TENANT_FEATURE_FLAGS.ONBOARDING_TOUR)) { if (isEnabled(TENANT_FEATURE_FLAGS.ONBOARDING_TOUR)) {
if (!$auth.user?.onboardedAt) { if (!$auth.user?.onboardedAt) {
// Determine the correct step
const activeNav = $layout.children.find(c => $isActive(c.path))
const onboardingTour = TOURS[TOUR_KEYS.TOUR_BUILDER_ONBOARDING]
const targetStep = activeNav
? onboardingTour.find(step => step.route === activeNav?.path)
: null
await store.update(state => ({ await store.update(state => ({
...state, ...state,
onboarding: true, onboarding: true,
tourKey: TOUR_KEYS.TOUR_BUILDER_ONBOARDING, tourKey: TOUR_KEYS.TOUR_BUILDER_ONBOARDING,
tourStepKey: targetStep?.id,
})) }))
} else { } else {
// Feature tour date // Feature tour date

View File

@ -8,27 +8,31 @@
let structureLookupMap = {} let structureLookupMap = {}
const registerBlockComponent = (id, order, parentId, instance) => { const registerBlockComponent = (id, parentId, order, instance) => {
// Ensure child map exists // Ensure child map exists
if (!structureLookupMap[parentId]) { if (!structureLookupMap[parentId]) {
structureLookupMap[parentId] = {} structureLookupMap[parentId] = {}
} }
// Add this instance in this order, overwriting any existing instance in // Add this instance in this order, overwriting any existing instance in
// this order in case of repeaters // this order in case of repeaters
structureLookupMap[parentId][order] = instance structureLookupMap[parentId][id] = { order, instance }
} }
const unregisterBlockComponent = (order, parentId) => { const unregisterBlockComponent = (id, parentId) => {
// Ensure child map exists // Ensure child map exists
if (!structureLookupMap[parentId]) { if (!structureLookupMap[parentId]) {
return return
} }
delete structureLookupMap[parentId][order] delete structureLookupMap[parentId][id]
} }
const eject = () => { const eject = () => {
// Start the new structure with the root component // Start the new structure with the root component
let definition = structureLookupMap[$component.id][0] const rootMap = structureLookupMap[$component.id] || {}
let definition = Object.values(rootMap)[0]?.instance
if (!definition) {
return
}
// Copy styles from block to root component // Copy styles from block to root component
definition._styles = { definition._styles = {
@ -49,10 +53,7 @@
const attachChildren = (rootComponent, map) => { const attachChildren = (rootComponent, map) => {
// Transform map into children array // Transform map into children array
let id = rootComponent._id let id = rootComponent._id
const children = Object.entries(map[id] || {}).map(([order, instance]) => ({ const children = Object.values(map[id] || {})
order,
instance,
}))
if (!children.length) { if (!children.length) {
return return
} }

View File

@ -23,6 +23,8 @@
// Create a fake component instance so that we can use the core Component // Create a fake component instance so that we can use the core Component
// to render this part of the block, taking advantage of binding enrichment // to render this part of the block, taking advantage of binding enrichment
$: id = `${block.id}-${context ?? rand}` $: id = `${block.id}-${context ?? rand}`
$: parentId = $component?.id
$: inBuilder = $builderStore.inBuilder
$: instance = { $: instance = {
_component: `@budibase/standard-components/${type}`, _component: `@budibase/standard-components/${type}`,
_id: id, _id: id,
@ -38,14 +40,14 @@
// Register this block component if we're inside the builder so it can be // Register this block component if we're inside the builder so it can be
// ejected later // ejected later
$: { $: {
if ($builderStore.inBuilder) { if (inBuilder) {
block.registerComponent(id, order ?? 0, $component?.id, instance) block.registerComponent(id, parentId, order ?? 0, instance)
} }
} }
onDestroy(() => { onDestroy(() => {
if ($builderStore.inBuilder) { if (inBuilder) {
block.unregisterComponent(order ?? 0, $component?.id) block.unregisterComponent(id, parentId)
} }
}) })
</script> </script>

View File

@ -126,7 +126,7 @@
order={1} order={1}
> >
{#if enrichedSearchColumns?.length} {#if enrichedSearchColumns?.length}
{#each enrichedSearchColumns as column, idx} {#each enrichedSearchColumns as column, idx (column.name)}
<BlockComponent <BlockComponent
type={column.componentType} type={column.componentType}
props={{ props={{

View File

@ -170,7 +170,7 @@
order={1} order={1}
> >
{#if enrichedSearchColumns?.length} {#if enrichedSearchColumns?.length}
{#each enrichedSearchColumns as column, idx} {#each enrichedSearchColumns as column, idx (column.name)}
<BlockComponent <BlockComponent
type={column.componentType} type={column.componentType}
props={{ props={{

View File

@ -478,7 +478,7 @@ export const enrichButtonActions = (actions, context) => {
actions.slice(i + 1), actions.slice(i + 1),
newContext newContext
) )
resolve(await next()) resolve(typeof next === "function" ? await next() : true)
} else { } else {
resolve(false) resolve(false)
} }

View File

@ -13,6 +13,6 @@ node ./bumpVersion.js $1
NEW_VERSION=$(node -p "require('../lerna.json').version") NEW_VERSION=$(node -p "require('../lerna.json').version")
git add ../lerna.json git add ../lerna.json
git commit -m "Bump version to $NEW_VERSION" git commit -m "Bump version to $NEW_VERSION"
git tag v$NEW_VERSION git tag $NEW_VERSION
git push git push
git push --tags git push --tags