Merge branch 'develop' of github.com:Budibase/budibase into feature/query-rbac-timeouts
This commit is contained in:
commit
ad68b895e1
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "0.9.180-alpha.1",
|
"version": "0.9.184",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/auth",
|
"name": "@budibase/auth",
|
||||||
"version": "0.9.180-alpha.1",
|
"version": "0.9.184",
|
||||||
"description": "Authentication middlewares for budibase builder and apps",
|
"description": "Authentication middlewares for budibase builder and apps",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/bbui",
|
"name": "@budibase/bbui",
|
||||||
"description": "A UI solution used in the different Budibase projects.",
|
"description": "A UI solution used in the different Budibase projects.",
|
||||||
"version": "0.9.180-alpha.1",
|
"version": "0.9.184",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
|
|
@ -36,5 +36,7 @@
|
||||||
{getOptionLabel}
|
{getOptionLabel}
|
||||||
{getOptionValue}
|
{getOptionValue}
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
|
on:pick
|
||||||
|
on:type
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -40,8 +40,15 @@
|
||||||
open = false
|
open = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChange = e => {
|
const onType = e => {
|
||||||
selectOption(e.target.value)
|
const value = e.target.value
|
||||||
|
dispatch("type", value)
|
||||||
|
selectOption(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPick = value => {
|
||||||
|
dispatch("pick", value)
|
||||||
|
selectOption(value)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -62,7 +69,7 @@
|
||||||
type="text"
|
type="text"
|
||||||
on:focus={() => (focus = true)}
|
on:focus={() => (focus = true)}
|
||||||
on:blur={() => (focus = false)}
|
on:blur={() => (focus = false)}
|
||||||
on:change={onChange}
|
on:change={onType}
|
||||||
value={value || ""}
|
value={value || ""}
|
||||||
placeholder={placeholder || ""}
|
placeholder={placeholder || ""}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
@ -99,7 +106,7 @@
|
||||||
role="option"
|
role="option"
|
||||||
aria-selected="true"
|
aria-selected="true"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
on:click={() => selectOption(getOptionValue(option))}
|
on:click={() => onPick(getOptionValue(option))}
|
||||||
>
|
>
|
||||||
<span class="spectrum-Menu-itemLabel"
|
<span class="spectrum-Menu-itemLabel"
|
||||||
>{getOptionLabel(option)}</span
|
>{getOptionLabel(option)}</span
|
||||||
|
|
|
@ -11,7 +11,8 @@ it("should rename an unpublished application", () => {
|
||||||
renameApp(appRename)
|
renameApp(appRename)
|
||||||
cy.searchForApplication(appRename)
|
cy.searchForApplication(appRename)
|
||||||
cy.get(".appGrid").find(".wrapper").should("have.length", 1)
|
cy.get(".appGrid").find(".wrapper").should("have.length", 1)
|
||||||
})
|
cy.deleteApp(appRename)
|
||||||
|
})
|
||||||
|
|
||||||
xit("Should rename a published application", () => {
|
xit("Should rename a published application", () => {
|
||||||
// It is not possible to rename a published application
|
// It is not possible to rename a published application
|
||||||
|
|
|
@ -43,24 +43,26 @@ Cypress.Commands.add("createApp", name => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("deleteApp", () => {
|
Cypress.Commands.add("deleteApp", appName => {
|
||||||
cy.visit(`localhost:${Cypress.env("PORT")}/builder`)
|
cy.visit(`localhost:${Cypress.env("PORT")}/builder`)
|
||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
cy.request(`localhost:${Cypress.env("PORT")}/api/applications?status=all`)
|
cy.request(`localhost:${Cypress.env("PORT")}/api/applications?status=all`)
|
||||||
.its("body")
|
.its("body")
|
||||||
.then(val => {
|
.then(val => {
|
||||||
console.log(val)
|
|
||||||
if (val.length > 0) {
|
if (val.length > 0) {
|
||||||
cy.get(".title > :nth-child(3) > .spectrum-Icon").click()
|
cy.get(".title > :nth-child(3) > .spectrum-Icon").click()
|
||||||
cy.contains("Delete").click()
|
cy.contains("Delete").click()
|
||||||
cy.get(".spectrum-Button--warning").click()
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
|
cy.get("input").type(appName)
|
||||||
|
cy.get(".spectrum-Button--warning").click()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("createTestApp", () => {
|
Cypress.Commands.add("createTestApp", () => {
|
||||||
const appName = "Cypress Tests"
|
const appName = "Cypress Tests"
|
||||||
cy.deleteApp()
|
cy.deleteApp(appName)
|
||||||
cy.createApp(appName, "This app is used for Cypress testing.")
|
cy.createApp(appName, "This app is used for Cypress testing.")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "0.9.180-alpha.1",
|
"version": "0.9.184",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -65,10 +65,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^0.9.180-alpha.1",
|
"@budibase/bbui": "^0.9.184",
|
||||||
"@budibase/client": "^0.9.180-alpha.1",
|
"@budibase/client": "^0.9.184",
|
||||||
"@budibase/colorpicker": "1.1.2",
|
"@budibase/colorpicker": "1.1.2",
|
||||||
"@budibase/string-templates": "^0.9.180-alpha.1",
|
"@budibase/string-templates": "^0.9.184",
|
||||||
"@sentry/browser": "5.19.1",
|
"@sentry/browser": "5.19.1",
|
||||||
"@spectrum-css/page": "^3.0.1",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
|
|
|
@ -12,7 +12,7 @@ export default class PosthogClient {
|
||||||
|
|
||||||
posthog.init(this.token, {
|
posthog.init(this.token, {
|
||||||
autocapture: false,
|
autocapture: false,
|
||||||
capture_pageview: false,
|
capture_pageview: true,
|
||||||
api_host: this.url,
|
api_host: this.url,
|
||||||
})
|
})
|
||||||
posthog.set_config({ persistence: "cookie" })
|
posthog.set_config({ persistence: "cookie" })
|
||||||
|
|
|
@ -31,11 +31,11 @@ export const getBindableProperties = (asset, componentId) => {
|
||||||
const deviceBindings = getDeviceBindings()
|
const deviceBindings = getDeviceBindings()
|
||||||
const stateBindings = getStateBindings()
|
const stateBindings = getStateBindings()
|
||||||
return [
|
return [
|
||||||
...stateBindings,
|
|
||||||
...deviceBindings,
|
|
||||||
...urlBindings,
|
|
||||||
...contextBindings,
|
...contextBindings,
|
||||||
|
...urlBindings,
|
||||||
|
...stateBindings,
|
||||||
...userBindings,
|
...userBindings,
|
||||||
|
...deviceBindings,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,18 +217,8 @@ const getProviderContextBindings = (asset, dataProviders) => {
|
||||||
keys.forEach(key => {
|
keys.forEach(key => {
|
||||||
const fieldSchema = schema[key]
|
const fieldSchema = schema[key]
|
||||||
|
|
||||||
// Make safe runtime binding and replace certain bindings with a
|
// Make safe runtime binding
|
||||||
// new property to help display components
|
const runtimeBinding = `${safeComponentId}.${makePropSafe(key)}`
|
||||||
let runtimeBoundKey = key
|
|
||||||
if (fieldSchema.type === "link") {
|
|
||||||
runtimeBoundKey = `${key}_text`
|
|
||||||
} else if (fieldSchema.type === "attachment") {
|
|
||||||
runtimeBoundKey = `${key}_first`
|
|
||||||
}
|
|
||||||
|
|
||||||
const runtimeBinding = `${safeComponentId}.${makePropSafe(
|
|
||||||
runtimeBoundKey
|
|
||||||
)}`
|
|
||||||
|
|
||||||
// Optionally use a prefix with readable bindings
|
// Optionally use a prefix with readable bindings
|
||||||
let readableBinding = component._instanceName
|
let readableBinding = component._instanceName
|
||||||
|
@ -267,17 +257,9 @@ const getUserBindings = () => {
|
||||||
const safeUser = makePropSafe("user")
|
const safeUser = makePropSafe("user")
|
||||||
keys.forEach(key => {
|
keys.forEach(key => {
|
||||||
const fieldSchema = schema[key]
|
const fieldSchema = schema[key]
|
||||||
// Replace certain bindings with a new property to help display components
|
|
||||||
let runtimeBoundKey = key
|
|
||||||
if (fieldSchema.type === "link") {
|
|
||||||
runtimeBoundKey = `${key}_text`
|
|
||||||
} else if (fieldSchema.type === "attachment") {
|
|
||||||
runtimeBoundKey = `${key}_first`
|
|
||||||
}
|
|
||||||
|
|
||||||
bindings.push({
|
bindings.push({
|
||||||
type: "context",
|
type: "context",
|
||||||
runtimeBinding: `${safeUser}.${makePropSafe(runtimeBoundKey)}`,
|
runtimeBinding: `${safeUser}.${makePropSafe(key)}`,
|
||||||
readableBinding: `Current User.${key}`,
|
readableBinding: `Current User.${key}`,
|
||||||
// Field schema and provider are required to construct relationship
|
// Field schema and provider are required to construct relationship
|
||||||
// datasource options, based on bindable properties
|
// datasource options, based on bindable properties
|
||||||
|
|
|
@ -45,6 +45,7 @@ const INITIAL_FRONTEND_STATE = {
|
||||||
state: false,
|
state: false,
|
||||||
customThemes: false,
|
customThemes: false,
|
||||||
devicePreview: false,
|
devicePreview: false,
|
||||||
|
messagePassing: false,
|
||||||
},
|
},
|
||||||
currentFrontEndType: "none",
|
currentFrontEndType: "none",
|
||||||
selectedScreenId: "",
|
selectedScreenId: "",
|
||||||
|
|
|
@ -202,7 +202,7 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
.block {
|
.block {
|
||||||
width: 360px;
|
width: 480px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
|
|
|
@ -234,7 +234,8 @@
|
||||||
<Editor
|
<Editor
|
||||||
mode="javascript"
|
mode="javascript"
|
||||||
on:change={e => {
|
on:change={e => {
|
||||||
onChange(e, key)
|
// need to pass without the value inside
|
||||||
|
onChange({ detail: e.detail.value }, key)
|
||||||
inputData[key] = e.detail.value
|
inputData[key] = e.detail.value
|
||||||
}}
|
}}
|
||||||
value={inputData[key]}
|
value={inputData[key]}
|
||||||
|
|
|
@ -18,6 +18,11 @@
|
||||||
FIELDS,
|
FIELDS,
|
||||||
AUTO_COLUMN_SUB_TYPES,
|
AUTO_COLUMN_SUB_TYPES,
|
||||||
RelationshipTypes,
|
RelationshipTypes,
|
||||||
|
ALLOWABLE_STRING_OPTIONS,
|
||||||
|
ALLOWABLE_NUMBER_OPTIONS,
|
||||||
|
ALLOWABLE_STRING_TYPES,
|
||||||
|
ALLOWABLE_NUMBER_TYPES,
|
||||||
|
SWITCHABLE_TYPES,
|
||||||
} from "constants/backend"
|
} from "constants/backend"
|
||||||
import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils"
|
import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
|
@ -92,6 +97,9 @@
|
||||||
opt.type === table.type &&
|
opt.type === table.type &&
|
||||||
table.sourceId === opt.sourceId
|
table.sourceId === opt.sourceId
|
||||||
)
|
)
|
||||||
|
$: typeEnabled =
|
||||||
|
!originalName ||
|
||||||
|
(originalName && SWITCHABLE_TYPES.indexOf(field.type) !== -1)
|
||||||
|
|
||||||
async function saveColumn() {
|
async function saveColumn() {
|
||||||
if (field.type === AUTO_TYPE) {
|
if (field.type === AUTO_TYPE) {
|
||||||
|
@ -204,7 +212,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllowedTypes() {
|
function getAllowedTypes() {
|
||||||
if (!external) {
|
if (originalName && ALLOWABLE_STRING_TYPES.indexOf(field.type) !== -1) {
|
||||||
|
return ALLOWABLE_STRING_OPTIONS
|
||||||
|
} else if (
|
||||||
|
originalName &&
|
||||||
|
ALLOWABLE_NUMBER_TYPES.indexOf(field.type) !== -1
|
||||||
|
) {
|
||||||
|
return ALLOWABLE_NUMBER_OPTIONS
|
||||||
|
} else if (!external) {
|
||||||
return [
|
return [
|
||||||
...Object.values(fieldDefinitions),
|
...Object.values(fieldDefinitions),
|
||||||
{ name: "Auto Column", type: AUTO_TYPE },
|
{ name: "Auto Column", type: AUTO_TYPE },
|
||||||
|
@ -259,7 +274,7 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
disabled={originalName}
|
disabled={!typeEnabled}
|
||||||
label="Type"
|
label="Type"
|
||||||
bind:value={field.type}
|
bind:value={field.type}
|
||||||
on:change={handleTypeChange}
|
on:change={handleTypeChange}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
export let onOk = undefined
|
export let onOk = undefined
|
||||||
export let onCancel = undefined
|
export let onCancel = undefined
|
||||||
export let warning = true
|
export let warning = true
|
||||||
|
export let disabled
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@
|
||||||
confirmText={okText}
|
confirmText={okText}
|
||||||
{cancelText}
|
{cancelText}
|
||||||
{warning}
|
{warning}
|
||||||
|
{disabled}
|
||||||
>
|
>
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
{body}
|
{body}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let options
|
export let options
|
||||||
export let allowJS = true
|
export let allowJS = true
|
||||||
|
export let appendBindingsAsOptions = true
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
let bindingDrawer
|
let bindingDrawer
|
||||||
|
@ -24,15 +25,30 @@
|
||||||
$: readableValue = runtimeToReadableBinding(bindings, value)
|
$: readableValue = runtimeToReadableBinding(bindings, value)
|
||||||
$: tempValue = readableValue
|
$: tempValue = readableValue
|
||||||
$: isJS = isJSBinding(value)
|
$: isJS = isJSBinding(value)
|
||||||
|
$: allOptions = buildOptions(options, bindings, appendBindingsAsOptions)
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
onChange(tempValue)
|
onChange(tempValue)
|
||||||
bindingDrawer.hide()
|
bindingDrawer.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChange = value => {
|
const onChange = (value, optionPicked) => {
|
||||||
|
// Add HBS braces if picking binding
|
||||||
|
if (optionPicked && !options?.includes(value)) {
|
||||||
|
value = `{{ ${value} }}`
|
||||||
|
}
|
||||||
|
|
||||||
dispatch("change", readableToRuntimeBinding(bindings, value))
|
dispatch("change", readableToRuntimeBinding(bindings, value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const buildOptions = (options, bindings, appendBindingsAsOptions) => {
|
||||||
|
if (!appendBindingsAsOptions) {
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
.concat(options || [])
|
||||||
|
.concat(bindings?.map(binding => binding.readableBinding) || [])
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="control">
|
<div class="control">
|
||||||
|
@ -40,12 +56,17 @@
|
||||||
{label}
|
{label}
|
||||||
{disabled}
|
{disabled}
|
||||||
value={isJS ? "(JavaScript function)" : readableValue}
|
value={isJS ? "(JavaScript function)" : readableValue}
|
||||||
on:change={event => onChange(event.detail)}
|
on:type={e => onChange(e.detail, false)}
|
||||||
|
on:pick={e => onChange(e.detail, true)}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
{options}
|
options={allOptions}
|
||||||
/>
|
/>
|
||||||
{#if !disabled}
|
{#if !disabled}
|
||||||
<div class="icon" on:click={bindingDrawer.show}>
|
<div
|
||||||
|
class="icon"
|
||||||
|
on:click={bindingDrawer.show}
|
||||||
|
data-cy="text-binding-button"
|
||||||
|
>
|
||||||
<Icon size="S" name="FlashOn" />
|
<Icon size="S" name="FlashOn" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
<script>
|
<script>
|
||||||
import { Icon, Modal, notifications, ModalContent } from "@budibase/bbui"
|
import {
|
||||||
|
Icon,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
notifications,
|
||||||
|
ModalContent,
|
||||||
|
} from "@budibase/bbui"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
|
|
||||||
let revertModal
|
let revertModal
|
||||||
|
let appName
|
||||||
|
|
||||||
$: appId = $store.appId
|
$: appId = $store.appId
|
||||||
|
|
||||||
|
@ -33,10 +40,17 @@
|
||||||
|
|
||||||
<Icon name="Revert" hoverable on:click={revertModal.show} />
|
<Icon name="Revert" hoverable on:click={revertModal.show} />
|
||||||
<Modal bind:this={revertModal}>
|
<Modal bind:this={revertModal}>
|
||||||
<ModalContent title="Revert Changes" confirmText="Revert" onConfirm={revert}>
|
<ModalContent
|
||||||
|
title="Revert Changes"
|
||||||
|
confirmText="Revert"
|
||||||
|
onConfirm={revert}
|
||||||
|
disabled={appName !== $store.name}
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
>The changes you have made will be deleted and the application reverted
|
>The changes you have made will be deleted and the application reverted
|
||||||
back to its production state.</span
|
back to its production state.</span
|
||||||
>
|
>
|
||||||
|
<span>Please enter your app name to continue.</span>
|
||||||
|
<Input bind:value={appName} />
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -69,6 +69,7 @@
|
||||||
theme: $store.theme,
|
theme: $store.theme,
|
||||||
customTheme: $store.customTheme,
|
customTheme: $store.customTheme,
|
||||||
previewDevice: $store.previewDevice,
|
previewDevice: $store.previewDevice,
|
||||||
|
messagePassing: $store.clientFeatures.messagePassing
|
||||||
}
|
}
|
||||||
|
|
||||||
// Saving pages and screens to the DB causes them to have _revs.
|
// Saving pages and screens to the DB causes them to have _revs.
|
||||||
|
@ -94,10 +95,12 @@
|
||||||
const handlers = {
|
const handlers = {
|
||||||
[MessageTypes.READY]: () => {
|
[MessageTypes.READY]: () => {
|
||||||
// Initialise the app when mounted
|
// Initialise the app when mounted
|
||||||
|
if ($store.clientFeatures.messagePassing) {
|
||||||
|
if (!loading) return
|
||||||
|
}
|
||||||
|
|
||||||
// Display preview immediately if the intelligent loading feature
|
// Display preview immediately if the intelligent loading feature
|
||||||
// is not supported
|
// is not supported
|
||||||
if (!loading) return
|
|
||||||
|
|
||||||
if (!$store.clientFeatures.intelligentLoading) {
|
if (!$store.clientFeatures.intelligentLoading) {
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
|
@ -117,17 +120,34 @@
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
window.addEventListener("message", receiveMessage)
|
window.addEventListener("message", receiveMessage)
|
||||||
|
if (!$store.clientFeatures.messagePassing) {
|
||||||
|
// Legacy - remove in later versions of BB
|
||||||
|
iframe.contentWindow.addEventListener("ready", () => {
|
||||||
|
receiveMessage({ data: { type: MessageTypes.READY }})
|
||||||
|
}, { once: true })
|
||||||
|
iframe.contentWindow.addEventListener("error", event => {
|
||||||
|
receiveMessage({ data: { type: MessageTypes.ERROR, error: event.detail }})
|
||||||
|
}, { once: true })
|
||||||
|
// Add listener for events sent by client library in preview
|
||||||
|
iframe.contentWindow.addEventListener("bb-event", handleBudibaseEvent)
|
||||||
|
iframe.contentWindow.addEventListener("keydown", handleKeydownEvent)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Remove all iframe event listeners on component destroy
|
// Remove all iframe event listeners on component destroy
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
if (iframe.contentWindow) {
|
if (iframe.contentWindow) {
|
||||||
window.removeEventListener("message", receiveMessage) //
|
window.removeEventListener("message", receiveMessage)
|
||||||
|
if (!$store.clientFeatures.messagePassing) {
|
||||||
|
// Legacy - remove in later versions of BB
|
||||||
|
iframe.contentWindow.removeEventListener("bb-event", handleBudibaseEvent)
|
||||||
|
iframe.contentWindow.removeEventListener("keydown", handleKeydownEvent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleBudibaseEvent = event => {
|
const handleBudibaseEvent = event => {
|
||||||
const { type, data } = event.data
|
const { type, data } = event.data || event.detail
|
||||||
if (type === "select-component" && data.id) {
|
if (type === "select-component" && data.id) {
|
||||||
store.actions.components.select({ _id: data.id })
|
store.actions.components.select({ _id: data.id })
|
||||||
} else if (type === "update-prop") {
|
} else if (type === "update-prop") {
|
||||||
|
@ -164,7 +184,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleKeydownEvent = event => {
|
const handleKeydownEvent = event => {
|
||||||
const { key } = event.data
|
const { key } = event.data || event
|
||||||
if (
|
if (
|
||||||
(key === "Delete" || key === "Backspace") &&
|
(key === "Delete" || key === "Backspace") &&
|
||||||
selectedComponentId &&
|
selectedComponentId &&
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
"name": "Blocks",
|
"name": "Blocks",
|
||||||
"icon": "Article",
|
"icon": "Article",
|
||||||
"children": [
|
"children": [
|
||||||
"tablewithsearch",
|
"tableblock",
|
||||||
"cardlistwithsearch"
|
"cardsblock"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"section",
|
"section",
|
||||||
|
|
|
@ -97,6 +97,7 @@ export default `
|
||||||
window.addEventListener("keydown", evt => {
|
window.addEventListener("keydown", evt => {
|
||||||
window.parent.postMessage({ type: "keydown", key: event.key })
|
window.parent.postMessage({ type: "keydown", key: event.key })
|
||||||
})
|
})
|
||||||
|
|
||||||
window.parent.postMessage({ type: "ready" })
|
window.parent.postMessage({ type: "ready" })
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -78,7 +78,6 @@
|
||||||
<DetailSummary name={section.name} collapsible={false}>
|
<DetailSummary name={section.name} collapsible={false}>
|
||||||
{#if idx === 0 && !componentInstance._component.endsWith("/layout")}
|
{#if idx === 0 && !componentInstance._component.endsWith("/layout")}
|
||||||
<PropertyControl
|
<PropertyControl
|
||||||
bindable={false}
|
|
||||||
control={Input}
|
control={Input}
|
||||||
label="Name"
|
label="Name"
|
||||||
key="_instanceName"
|
key="_instanceName"
|
||||||
|
|
|
@ -13,9 +13,10 @@
|
||||||
import { generate } from "shortid"
|
import { generate } from "shortid"
|
||||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
import { OperatorOptions, getValidOperatorsForType } from "constants/lucene"
|
import { OperatorOptions, getValidOperatorsForType } from "constants/lucene"
|
||||||
import { selectedComponent, store } from "builderStore"
|
import { selectedComponent } from "builderStore"
|
||||||
import { getComponentForSettingType } from "./componentSettings"
|
import { getComponentForSettingType } from "./componentSettings"
|
||||||
import PropertyControl from "./PropertyControl.svelte"
|
import PropertyControl from "./PropertyControl.svelte"
|
||||||
|
import { getComponentSettings } from "builderStore/storeUtils"
|
||||||
|
|
||||||
export let conditions = []
|
export let conditions = []
|
||||||
export let bindings = []
|
export let bindings = []
|
||||||
|
@ -55,15 +56,11 @@
|
||||||
]
|
]
|
||||||
|
|
||||||
let dragDisabled = true
|
let dragDisabled = true
|
||||||
$: definition = store.actions.components.getDefinition(
|
$: settings = getComponentSettings($selectedComponent?._component)
|
||||||
$selectedComponent?._component
|
$: settingOptions = settings.map(setting => ({
|
||||||
)
|
label: setting.label,
|
||||||
$: settings = (definition?.settings ?? []).map(setting => {
|
value: setting.key,
|
||||||
return {
|
}))
|
||||||
label: setting.label,
|
|
||||||
value: setting.key,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
$: conditions.forEach(link => {
|
$: conditions.forEach(link => {
|
||||||
if (!link.id) {
|
if (!link.id) {
|
||||||
link.id = generate()
|
link.id = generate()
|
||||||
|
@ -71,9 +68,7 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
const getSettingDefinition = key => {
|
const getSettingDefinition = key => {
|
||||||
return definition?.settings?.find(setting => {
|
return settings.find(setting => setting.key === key)
|
||||||
return setting.key === key
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getComponentForSetting = key => {
|
const getComponentForSetting = key => {
|
||||||
|
@ -175,7 +170,10 @@
|
||||||
bind:value={condition.action}
|
bind:value={condition.action}
|
||||||
/>
|
/>
|
||||||
{#if condition.action === "update"}
|
{#if condition.action === "update"}
|
||||||
<Select options={settings} bind:value={condition.setting} />
|
<Select
|
||||||
|
options={settingOptions}
|
||||||
|
bind:value={condition.setting}
|
||||||
|
/>
|
||||||
<div>TO</div>
|
<div>TO</div>
|
||||||
{#if getSettingDefinition(condition.setting)}
|
{#if getSettingDefinition(condition.setting)}
|
||||||
<PropertyControl
|
<PropertyControl
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
<script>
|
|
||||||
import { Input } from "@budibase/bbui"
|
|
||||||
import { isJSBinding } from "@budibase/string-templates"
|
|
||||||
|
|
||||||
export let value
|
|
||||||
|
|
||||||
$: isJS = isJSBinding(value)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
{...$$props}
|
|
||||||
value={isJS ? "(JavaScript function)" : value}
|
|
||||||
readonly={isJS}
|
|
||||||
on:change
|
|
||||||
/>
|
|
|
@ -1,11 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { Button, Icon, Drawer, Label } from "@budibase/bbui"
|
import { Label } from "@budibase/bbui"
|
||||||
import {
|
import {
|
||||||
readableToRuntimeBinding,
|
readableToRuntimeBinding,
|
||||||
runtimeToReadableBinding,
|
runtimeToReadableBinding,
|
||||||
} from "builderStore/dataBinding"
|
} from "builderStore/dataBinding"
|
||||||
import BindingPanel from "components/common/bindings/BindingPanel.svelte"
|
|
||||||
import { capitalise } from "helpers"
|
|
||||||
|
|
||||||
export let label = ""
|
export let label = ""
|
||||||
export let bindable = true
|
export let bindable = true
|
||||||
|
@ -20,10 +18,6 @@
|
||||||
export let componentBindings = []
|
export let componentBindings = []
|
||||||
export let nested = false
|
export let nested = false
|
||||||
|
|
||||||
let bindingDrawer
|
|
||||||
let anchor
|
|
||||||
let valid
|
|
||||||
|
|
||||||
$: allBindings = getAllBindings(bindings, componentBindings, nested)
|
$: allBindings = getAllBindings(bindings, componentBindings, nested)
|
||||||
$: safeValue = getSafeValue(value, props.defaultValue, allBindings)
|
$: safeValue = getSafeValue(value, props.defaultValue, allBindings)
|
||||||
$: tempValue = safeValue
|
$: tempValue = safeValue
|
||||||
|
@ -33,12 +27,7 @@
|
||||||
if (!nested) {
|
if (!nested) {
|
||||||
return bindings
|
return bindings
|
||||||
}
|
}
|
||||||
return [...(bindings || []), ...(componentBindings || [])]
|
return [...(componentBindings || []), ...(bindings || [])]
|
||||||
}
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
handleChange(tempValue)
|
|
||||||
bindingDrawer.hide()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle a value change of any type
|
// Handle a value change of any type
|
||||||
|
@ -74,7 +63,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="property-control" bind:this={anchor} data-cy={`setting-${key}`}>
|
<div class="property-control" data-cy={`setting-${key}`}>
|
||||||
{#if type !== "boolean" && label}
|
{#if type !== "boolean" && label}
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<Label>{label}</Label>
|
<Label>{label}</Label>
|
||||||
|
@ -94,31 +83,6 @@
|
||||||
{type}
|
{type}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
{#if bindable && !key.startsWith("_") && type === "text"}
|
|
||||||
<div
|
|
||||||
class="icon"
|
|
||||||
data-cy={`${key}-binding-button`}
|
|
||||||
on:click={bindingDrawer.show}
|
|
||||||
>
|
|
||||||
<Icon size="S" name="FlashOn" />
|
|
||||||
</div>
|
|
||||||
<Drawer bind:this={bindingDrawer} title={capitalise(key)}>
|
|
||||||
<svelte:fragment slot="description">
|
|
||||||
Add the objects on the left to enrich your text.
|
|
||||||
</svelte:fragment>
|
|
||||||
<Button cta slot="buttons" disabled={!valid} on:click={handleClose}>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
<BindingPanel
|
|
||||||
slot="body"
|
|
||||||
bind:valid
|
|
||||||
value={safeValue}
|
|
||||||
on:change={e => (tempValue = e.detail)}
|
|
||||||
bindableProperties={allBindings}
|
|
||||||
allowJS
|
|
||||||
/>
|
|
||||||
</Drawer>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -130,40 +94,10 @@
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
padding-bottom: var(--spectrum-global-dimension-size-65);
|
padding-bottom: var(--spectrum-global-dimension-size-65);
|
||||||
}
|
}
|
||||||
|
|
||||||
.control {
|
.control {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
|
||||||
right: 1px;
|
|
||||||
top: 1px;
|
|
||||||
bottom: 1px;
|
|
||||||
position: absolute;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-left: 1px solid var(--spectrum-alias-border-color);
|
|
||||||
border-top-right-radius: var(--spectrum-alias-border-radius-regular);
|
|
||||||
border-bottom-right-radius: var(--spectrum-alias-border-radius-regular);
|
|
||||||
width: 31px;
|
|
||||||
color: var(--spectrum-alias-text-color);
|
|
||||||
background-color: var(--spectrum-global-color-gray-75);
|
|
||||||
transition: background-color
|
|
||||||
var(--spectrum-global-animation-duration-100, 130ms),
|
|
||||||
box-shadow var(--spectrum-global-animation-duration-100, 130ms),
|
|
||||||
border-color var(--spectrum-global-animation-duration-100, 130ms);
|
|
||||||
}
|
|
||||||
.icon:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--spectrum-alias-text-color-hover);
|
|
||||||
background-color: var(--spectrum-global-color-gray-50);
|
|
||||||
border-color: var(--spectrum-alias-border-color-hover);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -10,4 +10,10 @@
|
||||||
.filter(x => x != null)
|
.filter(x => x != null)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DrawerBindableCombobox {value} {bindings} on:change options={urlOptions} />
|
<DrawerBindableCombobox
|
||||||
|
{value}
|
||||||
|
{bindings}
|
||||||
|
on:change
|
||||||
|
options={urlOptions}
|
||||||
|
appendBindingsAsOptions={false}
|
||||||
|
/>
|
||||||
|
|
|
@ -15,10 +15,10 @@ import URLSelect from "./URLSelect.svelte"
|
||||||
import OptionsEditor from "./OptionsEditor/OptionsEditor.svelte"
|
import OptionsEditor from "./OptionsEditor/OptionsEditor.svelte"
|
||||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
import FormFieldSelect from "./FormFieldSelect.svelte"
|
||||||
import ValidationEditor from "./ValidationEditor/ValidationEditor.svelte"
|
import ValidationEditor from "./ValidationEditor/ValidationEditor.svelte"
|
||||||
import Input from "./Input.svelte"
|
import DrawerBindableCombobox from "components/common/bindings/DrawerBindableCombobox.svelte"
|
||||||
|
|
||||||
const componentMap = {
|
const componentMap = {
|
||||||
text: Input,
|
text: DrawerBindableCombobox,
|
||||||
select: Select,
|
select: Select,
|
||||||
dataSource: DataSourceSelect,
|
dataSource: DataSourceSelect,
|
||||||
dataProvider: DataProviderSelect,
|
dataProvider: DataProviderSelect,
|
||||||
|
|
|
@ -54,7 +54,6 @@
|
||||||
<DetailSummary name="Screen" collapsible={false}>
|
<DetailSummary name="Screen" collapsible={false}>
|
||||||
{#each screenSettings as def (`${componentInstance._id}-${def.key}`)}
|
{#each screenSettings as def (`${componentInstance._id}-${def.key}`)}
|
||||||
<PropertyControl
|
<PropertyControl
|
||||||
bindable={false}
|
|
||||||
control={def.control}
|
control={def.control}
|
||||||
label={def.label}
|
label={def.label}
|
||||||
key={def.key}
|
key={def.key}
|
||||||
|
|
|
@ -30,7 +30,6 @@
|
||||||
{#each properties as prop (`${componentInstance._id}-${prop.key}-${prop.label}`)}
|
{#each properties as prop (`${componentInstance._id}-${prop.key}-${prop.label}`)}
|
||||||
<div style="grid-column: {prop.column || 'auto'}">
|
<div style="grid-column: {prop.column || 'auto'}">
|
||||||
<PropertyControl
|
<PropertyControl
|
||||||
bindable={false}
|
|
||||||
label={`${prop.label}${hasPropChanged(style, prop) ? " *" : ""}`}
|
label={`${prop.label}${hasPropChanged(style, prop) ? " *" : ""}`}
|
||||||
control={prop.control}
|
control={prop.control}
|
||||||
key={prop.key}
|
key={prop.key}
|
||||||
|
|
|
@ -9,7 +9,6 @@ export const margin = {
|
||||||
label: "Top",
|
label: "Top",
|
||||||
key: "margin-top",
|
key: "margin-top",
|
||||||
control: Select,
|
control: Select,
|
||||||
bindable: false,
|
|
||||||
placeholder: "None",
|
placeholder: "None",
|
||||||
options: [
|
options: [
|
||||||
{ label: "4px", value: "4px" },
|
{ label: "4px", value: "4px" },
|
||||||
|
@ -30,7 +29,6 @@ export const margin = {
|
||||||
label: "Right",
|
label: "Right",
|
||||||
key: "margin-right",
|
key: "margin-right",
|
||||||
control: Select,
|
control: Select,
|
||||||
bindable: false,
|
|
||||||
placeholder: "None",
|
placeholder: "None",
|
||||||
options: [
|
options: [
|
||||||
{ label: "4px", value: "4px" },
|
{ label: "4px", value: "4px" },
|
||||||
|
@ -51,7 +49,6 @@ export const margin = {
|
||||||
label: "Bottom",
|
label: "Bottom",
|
||||||
key: "margin-bottom",
|
key: "margin-bottom",
|
||||||
control: Select,
|
control: Select,
|
||||||
bindable: false,
|
|
||||||
placeholder: "None",
|
placeholder: "None",
|
||||||
options: [
|
options: [
|
||||||
{ label: "4px", value: "4px" },
|
{ label: "4px", value: "4px" },
|
||||||
|
@ -72,7 +69,6 @@ export const margin = {
|
||||||
label: "Left",
|
label: "Left",
|
||||||
key: "margin-left",
|
key: "margin-left",
|
||||||
control: Select,
|
control: Select,
|
||||||
bindable: false,
|
|
||||||
placeholder: "None",
|
placeholder: "None",
|
||||||
options: [
|
options: [
|
||||||
{ label: "4px", value: "4px" },
|
{ label: "4px", value: "4px" },
|
||||||
|
@ -100,7 +96,6 @@ export const padding = {
|
||||||
label: "Top",
|
label: "Top",
|
||||||
key: "padding-top",
|
key: "padding-top",
|
||||||
control: Select,
|
control: Select,
|
||||||
bindable: false,
|
|
||||||
placeholder: "None",
|
placeholder: "None",
|
||||||
options: [
|
options: [
|
||||||
{ label: "4px", value: "4px" },
|
{ label: "4px", value: "4px" },
|
||||||
|
@ -121,7 +116,6 @@ export const padding = {
|
||||||
label: "Right",
|
label: "Right",
|
||||||
key: "padding-right",
|
key: "padding-right",
|
||||||
control: Select,
|
control: Select,
|
||||||
bindable: false,
|
|
||||||
placeholder: "None",
|
placeholder: "None",
|
||||||
options: [
|
options: [
|
||||||
{ label: "4px", value: "4px" },
|
{ label: "4px", value: "4px" },
|
||||||
|
@ -142,7 +136,6 @@ export const padding = {
|
||||||
label: "Bottom",
|
label: "Bottom",
|
||||||
key: "padding-bottom",
|
key: "padding-bottom",
|
||||||
control: Select,
|
control: Select,
|
||||||
bindable: false,
|
|
||||||
placeholder: "None",
|
placeholder: "None",
|
||||||
options: [
|
options: [
|
||||||
{ label: "4px", value: "4px" },
|
{ label: "4px", value: "4px" },
|
||||||
|
@ -163,7 +156,6 @@ export const padding = {
|
||||||
label: "Left",
|
label: "Left",
|
||||||
key: "padding-left",
|
key: "padding-left",
|
||||||
control: Select,
|
control: Select,
|
||||||
bindable: false,
|
|
||||||
placeholder: "None",
|
placeholder: "None",
|
||||||
options: [
|
options: [
|
||||||
{ label: "4px", value: "4px" },
|
{ label: "4px", value: "4px" },
|
||||||
|
|
|
@ -157,6 +157,11 @@
|
||||||
}
|
}
|
||||||
return title
|
return title
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function onCancel() {
|
||||||
|
template = null
|
||||||
|
await auth.setInitInfo({})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if showTemplateSelection}
|
{#if showTemplateSelection}
|
||||||
|
@ -186,7 +191,7 @@
|
||||||
title={getModalTitle()}
|
title={getModalTitle()}
|
||||||
confirmText={template?.fromFile ? "Import app" : "Create app"}
|
confirmText={template?.fromFile ? "Import app" : "Create app"}
|
||||||
onConfirm={createNewApp}
|
onConfirm={createNewApp}
|
||||||
onCancel={inline ? () => (template = null) : null}
|
onCancel={inline ? onCancel : null}
|
||||||
cancelText={inline ? "Back" : undefined}
|
cancelText={inline ? "Back" : undefined}
|
||||||
showCloseIcon={!inline}
|
showCloseIcon={!inline}
|
||||||
disabled={!valid}
|
disabled={!valid}
|
||||||
|
|
|
@ -37,33 +37,33 @@
|
||||||
<p class="detail">{template?.category?.toUpperCase()}</p>
|
<p class="detail">{template?.category?.toUpperCase()}</p>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<div class="template start-from-scratch" on:click={() => onSelect(null)}>
|
|
||||||
<div
|
|
||||||
class="background-icon"
|
|
||||||
style={`background: rgb(50, 50, 50); color: white;`}
|
|
||||||
>
|
|
||||||
<Icon name="Add" />
|
|
||||||
</div>
|
|
||||||
<Heading size="XS">Start from scratch</Heading>
|
|
||||||
<p class="detail">BLANK</p>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="template import"
|
|
||||||
on:click={() => onSelect(null, { useImport: true })}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="background-icon"
|
|
||||||
style={`background: rgb(50, 50, 50); color: white;`}
|
|
||||||
>
|
|
||||||
<Icon name="Add" />
|
|
||||||
</div>
|
|
||||||
<Heading size="XS">Import an app</Heading>
|
|
||||||
<p class="detail">BLANK</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{:catch err}
|
{:catch err}
|
||||||
<h1 style="color:red">{err}</h1>
|
<h1 style="color:red">{err}</h1>
|
||||||
{/await}
|
{/await}
|
||||||
|
<div class="template start-from-scratch" on:click={() => onSelect(null)}>
|
||||||
|
<div
|
||||||
|
class="background-icon"
|
||||||
|
style={`background: rgb(50, 50, 50); color: white;`}
|
||||||
|
>
|
||||||
|
<Icon name="Add" />
|
||||||
|
</div>
|
||||||
|
<Heading size="XS">Start from scratch</Heading>
|
||||||
|
<p class="detail">BLANK</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="template import"
|
||||||
|
on:click={() => onSelect(null, { useImport: true })}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="background-icon"
|
||||||
|
style={`background: rgb(50, 50, 50); color: white;`}
|
||||||
|
>
|
||||||
|
<Icon name="Add" />
|
||||||
|
</div>
|
||||||
|
<Heading size="XS">Import an app</Heading>
|
||||||
|
<p class="detail">BLANK</p>
|
||||||
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -138,3 +138,19 @@ export const RelationshipTypes = {
|
||||||
ONE_TO_MANY: "one-to-many",
|
ONE_TO_MANY: "one-to-many",
|
||||||
MANY_TO_ONE: "many-to-one",
|
MANY_TO_ONE: "many-to-one",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ALLOWABLE_STRING_OPTIONS = [FIELDS.STRING, FIELDS.OPTIONS]
|
||||||
|
|
||||||
|
export const ALLOWABLE_STRING_TYPES = ALLOWABLE_STRING_OPTIONS.map(
|
||||||
|
opt => opt.type
|
||||||
|
)
|
||||||
|
|
||||||
|
export const ALLOWABLE_NUMBER_OPTIONS = [FIELDS.NUMBER, FIELDS.BOOLEAN]
|
||||||
|
|
||||||
|
export const ALLOWABLE_NUMBER_TYPES = ALLOWABLE_NUMBER_OPTIONS.map(
|
||||||
|
opt => opt.type
|
||||||
|
)
|
||||||
|
|
||||||
|
export const SWITCHABLE_TYPES = ALLOWABLE_NUMBER_TYPES.concat(
|
||||||
|
ALLOWABLE_STRING_TYPES
|
||||||
|
)
|
||||||
|
|
|
@ -28,9 +28,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user && user.tenantId) {
|
if (user && user.tenantId) {
|
||||||
// no tenant in the url - send to account portal to fix this
|
|
||||||
if (!urlTenantId) {
|
if (!urlTenantId) {
|
||||||
window.location.href = $admin.accountPortalUrl
|
// redirect to correct tenantId subdomain
|
||||||
|
if (!window.location.host.includes("localhost")) {
|
||||||
|
let redirectUrl = window.location.href
|
||||||
|
redirectUrl = redirectUrl.replace("://", `://${user.tenantId}.`)
|
||||||
|
window.location.href = redirectUrl
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
ActionButton,
|
ActionButton,
|
||||||
ActionGroup,
|
ActionGroup,
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
|
Input,
|
||||||
Select,
|
Select,
|
||||||
Modal,
|
Modal,
|
||||||
Page,
|
Page,
|
||||||
|
@ -36,6 +37,7 @@
|
||||||
let loaded = false
|
let loaded = false
|
||||||
let searchTerm = ""
|
let searchTerm = ""
|
||||||
let cloud = $admin.cloud
|
let cloud = $admin.cloud
|
||||||
|
let appName = ""
|
||||||
|
|
||||||
$: enrichedApps = enrichApps($apps, $auth.user, sortBy)
|
$: enrichedApps = enrichApps($apps, $auth.user, sortBy)
|
||||||
$: filteredApps = enrichedApps.filter(app =>
|
$: filteredApps = enrichedApps.filter(app =>
|
||||||
|
@ -296,8 +298,12 @@
|
||||||
title="Confirm deletion"
|
title="Confirm deletion"
|
||||||
okText="Delete app"
|
okText="Delete app"
|
||||||
onOk={confirmDeleteApp}
|
onOk={confirmDeleteApp}
|
||||||
|
disabled={appName !== selectedApp?.name}
|
||||||
>
|
>
|
||||||
Are you sure you want to delete the app <b>{selectedApp?.name}</b>?
|
Are you sure you want to delete the app <b>{selectedApp?.name}</b>?
|
||||||
|
|
||||||
|
<p>Please enter the app name below to confirm.</p>
|
||||||
|
<Input bind:value={appName} data-cy="delete-app-confirmation" />
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
bind:this={unpublishModal}
|
bind:this={unpublishModal}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { writable, get } from "svelte/store"
|
||||||
import { views, queries, datasources } from "./"
|
import { views, queries, datasources } from "./"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
|
import { SWITCHABLE_TYPES } from "../../constants/backend"
|
||||||
|
|
||||||
export function createTablesStore() {
|
export function createTablesStore() {
|
||||||
const store = writable({})
|
const store = writable({})
|
||||||
|
@ -47,7 +48,11 @@ export function createTablesStore() {
|
||||||
const field = updatedTable.schema[key]
|
const field = updatedTable.schema[key]
|
||||||
const oldField = oldTable?.schema[key]
|
const oldField = oldTable?.schema[key]
|
||||||
// if the type has changed then revert back to the old field
|
// if the type has changed then revert back to the old field
|
||||||
if (oldField != null && oldField?.type !== field.type) {
|
if (
|
||||||
|
oldField != null &&
|
||||||
|
oldField?.type !== field.type &&
|
||||||
|
SWITCHABLE_TYPES.indexOf(oldField?.type) === -1
|
||||||
|
) {
|
||||||
updatedTable.schema[key] = oldField
|
updatedTable.schema[key] = oldField
|
||||||
}
|
}
|
||||||
// field has been renamed
|
// field has been renamed
|
||||||
|
|
|
@ -57,11 +57,11 @@ export function createAuthStore() {
|
||||||
analytics.showChat({
|
analytics.showChat({
|
||||||
email: user.email,
|
email: user.email,
|
||||||
created_at: (user.createdAt || Date.now()) / 1000,
|
created_at: (user.createdAt || Date.now()) / 1000,
|
||||||
name: user.name,
|
name: user.account?.name,
|
||||||
user_id: user._id,
|
user_id: user._id,
|
||||||
tenant: user.tenantId,
|
tenant: user.tenantId,
|
||||||
"Company size": user.size,
|
"Company size": user.account?.size,
|
||||||
"Job role": user.profession,
|
"Job role": user.account?.profession,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -80,16 +80,30 @@ export function createAuthStore() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function setInitInfo(info) {
|
||||||
|
await api.post(`/api/global/auth/init`, info)
|
||||||
|
auth.update(store => {
|
||||||
|
store.initInfo = info
|
||||||
|
return store
|
||||||
|
})
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getInitInfo() {
|
||||||
|
const response = await api.get(`/api/global/auth/init`)
|
||||||
|
const json = response.json()
|
||||||
|
auth.update(store => {
|
||||||
|
store.initInfo = json
|
||||||
|
return store
|
||||||
|
})
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe: store.subscribe,
|
subscribe: store.subscribe,
|
||||||
setOrganisation: setOrganisation,
|
setOrganisation,
|
||||||
getInitInfo: async () => {
|
getInitInfo,
|
||||||
const response = await api.get(`/api/global/auth/init`)
|
setInitInfo,
|
||||||
return await response.json()
|
|
||||||
},
|
|
||||||
setInitInfo: async info => {
|
|
||||||
await api.post(`/api/global/auth/init`, info)
|
|
||||||
},
|
|
||||||
checkQueryString: async () => {
|
checkQueryString: async () => {
|
||||||
const urlParams = new URLSearchParams(window.location.search)
|
const urlParams = new URLSearchParams(window.location.search)
|
||||||
if (urlParams.has("tenantId")) {
|
if (urlParams.has("tenantId")) {
|
||||||
|
@ -129,6 +143,7 @@ export function createAuthStore() {
|
||||||
throw "Unable to create logout"
|
throw "Unable to create logout"
|
||||||
}
|
}
|
||||||
await response.json()
|
await response.json()
|
||||||
|
await setInitInfo({})
|
||||||
setUser(null)
|
setUser(null)
|
||||||
},
|
},
|
||||||
updateSelf: async fields => {
|
updateSelf: async fields => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "0.9.180-alpha.1",
|
"version": "0.9.184",
|
||||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
"deviceAwareness": true,
|
"deviceAwareness": true,
|
||||||
"state": true,
|
"state": true,
|
||||||
"customThemes": true,
|
"customThemes": true,
|
||||||
"devicePreview": true
|
"devicePreview": true,
|
||||||
|
"messagePassing": true
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
"name": "Layout",
|
"name": "Layout",
|
||||||
|
@ -2595,6 +2596,11 @@
|
||||||
"key": "linkURL",
|
"key": "linkURL",
|
||||||
"label": "Link URL"
|
"label": "Link URL"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"key": "linkPeek",
|
||||||
|
"label": "Open link in modal"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"key": "horizontal",
|
"key": "horizontal",
|
||||||
|
@ -2617,9 +2623,9 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"tablewithsearch": {
|
"tableblock": {
|
||||||
"block": true,
|
"block": true,
|
||||||
"name": "Table with search",
|
"name": "Table block",
|
||||||
"icon": "Table",
|
"icon": "Table",
|
||||||
"styles": ["size"],
|
"styles": ["size"],
|
||||||
"info": "Only the first 3 search columns will be used.",
|
"info": "Only the first 3 search columns will be used.",
|
||||||
|
@ -2730,18 +2736,23 @@
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"key": "showTitleButton",
|
"key": "showTitleButton",
|
||||||
"label": "Show button",
|
"label": "Show link button",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Open link in modal",
|
||||||
|
"key": "titleButtonPeek"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"key": "titleButtonText",
|
"key": "titleButtonText",
|
||||||
"label": "Button text"
|
"label": "Button text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "url",
|
||||||
"label": "Button action",
|
"label": "Button link",
|
||||||
"key": "titleButtonOnClick"
|
"key": "titleButtonURL"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -2759,9 +2770,9 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"cardlistwithsearch": {
|
"cardsblock": {
|
||||||
"block": true,
|
"block": true,
|
||||||
"name": "Card list with search",
|
"name": "Cards block",
|
||||||
"icon": "Table",
|
"icon": "Table",
|
||||||
"styles": ["size"],
|
"styles": ["size"],
|
||||||
"info": "Only the first 3 search columns will be used.",
|
"info": "Only the first 3 search columns will be used.",
|
||||||
|
@ -2838,7 +2849,22 @@
|
||||||
"key": "cardImageURL",
|
"key": "cardImageURL",
|
||||||
"label": "Image URL",
|
"label": "Image URL",
|
||||||
"nested": true
|
"nested": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"key": "linkCardTitle",
|
||||||
|
"label": "Link card title"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"key": "cardPeek",
|
||||||
|
"label": "Open link in modal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "url",
|
||||||
|
"label": "Link screen",
|
||||||
|
"key": "cardURL",
|
||||||
|
"nested": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
@ -2855,7 +2881,6 @@
|
||||||
"key": "cardButtonText",
|
"key": "cardButtonText",
|
||||||
"label": "Button text",
|
"label": "Button text",
|
||||||
"nested": true
|
"nested": true
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "event",
|
||||||
|
@ -2872,7 +2897,12 @@
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"key": "showTitleButton",
|
"key": "showTitleButton",
|
||||||
"label": "Show button"
|
"label": "Show link button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Open link in modal",
|
||||||
|
"key": "titleButtonPeek"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -2880,9 +2910,21 @@
|
||||||
"label": "Button text"
|
"label": "Button text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "url",
|
||||||
"label": "Button action",
|
"label": "Button link",
|
||||||
"key": "titleButtonOnClick"
|
"key": "titleButtonURL"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Advanced",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"label": "ID column for linking (appended to URL)",
|
||||||
|
"key": "linkColumn",
|
||||||
|
"placeholder": "Default"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "0.9.180-alpha.1",
|
"version": "0.9.184",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"module": "dist/budibase-client.js",
|
"module": "dist/budibase-client.js",
|
||||||
"main": "dist/budibase-client.js",
|
"main": "dist/budibase-client.js",
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
"dev:builder": "rollup -cw"
|
"dev:builder": "rollup -cw"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^0.9.180-alpha.1",
|
"@budibase/bbui": "^0.9.184",
|
||||||
"@budibase/standard-components": "^0.9.139",
|
"@budibase/standard-components": "^0.9.139",
|
||||||
"@budibase/string-templates": "^0.9.180-alpha.1",
|
"@budibase/string-templates": "^0.9.184",
|
||||||
"regexparam": "^1.3.0",
|
"regexparam": "^1.3.0",
|
||||||
"shortid": "^2.2.15",
|
"shortid": "^2.2.15",
|
||||||
"svelte-spa-router": "^3.0.5"
|
"svelte-spa-router": "^3.0.5"
|
||||||
|
|
|
@ -108,6 +108,8 @@ export const deleteRows = async ({ tableId, rows }) => {
|
||||||
/**
|
/**
|
||||||
* Enriches rows which contain certain field types so that they can
|
* Enriches rows which contain certain field types so that they can
|
||||||
* be properly displayed.
|
* be properly displayed.
|
||||||
|
* The ability to create these bindings has been removed, but they will still
|
||||||
|
* exist in client apps to support backwards compatibility.
|
||||||
*/
|
*/
|
||||||
export const enrichRows = async (rows, tableId) => {
|
export const enrichRows = async (rows, tableId) => {
|
||||||
if (!Array.isArray(rows)) {
|
if (!Array.isArray(rows)) {
|
||||||
|
|
|
@ -8,15 +8,22 @@
|
||||||
export let description
|
export let description
|
||||||
export let imageURL
|
export let imageURL
|
||||||
export let linkURL
|
export let linkURL
|
||||||
|
export let linkPeek
|
||||||
export let horizontal
|
export let horizontal
|
||||||
export let showButton
|
export let showButton
|
||||||
export let buttonText
|
export let buttonText
|
||||||
export let buttonOnClick
|
export let buttonOnClick
|
||||||
|
|
||||||
const { styleable, linkable } = getContext("sdk")
|
const { styleable, routeStore } = getContext("sdk")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
|
|
||||||
$: external = linkURL && !linkURL.startsWith("/")
|
const handleLink = e => {
|
||||||
|
if (!linkURL) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.preventDefault()
|
||||||
|
routeStore.actions.navigate(linkURL, linkPeek)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -37,16 +44,10 @@
|
||||||
<div class="spectrum-Card-header">
|
<div class="spectrum-Card-header">
|
||||||
<div
|
<div
|
||||||
class="spectrum-Card-title spectrum-Heading spectrum-Heading--sizeXS"
|
class="spectrum-Card-title spectrum-Heading spectrum-Heading--sizeXS"
|
||||||
|
on:click={handleLink}
|
||||||
|
class:link={linkURL}
|
||||||
>
|
>
|
||||||
{#if linkURL}
|
{title || "Card Title"}
|
||||||
{#if external}
|
|
||||||
<a href={linkURL}>{title || "Card Title"}</a>
|
|
||||||
{:else}
|
|
||||||
<a use:linkable href={linkURL}>{title || "Card Title"}</a>
|
|
||||||
{/if}
|
|
||||||
{:else}
|
|
||||||
{title || "Card Title"}
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if subtitle}
|
{#if subtitle}
|
||||||
|
@ -88,11 +89,12 @@
|
||||||
.spectrum-Card-container {
|
.spectrum-Card-container {
|
||||||
padding: var(--spectrum-global-dimension-size-50) 0;
|
padding: var(--spectrum-global-dimension-size-50) 0;
|
||||||
}
|
}
|
||||||
.spectrum-Card-title :global(a) {
|
.spectrum-Card-title.link {
|
||||||
text-overflow: ellipsis;
|
transition: color 130ms ease-in-out;
|
||||||
overflow: hidden;
|
}
|
||||||
white-space: nowrap;
|
.spectrum-Card-title.link:hover {
|
||||||
width: 100%;
|
cursor: pointer;
|
||||||
|
color: var(--spectrum-link-primary-m-text-color-hover);
|
||||||
}
|
}
|
||||||
.spectrum-Card-subtitle {
|
.spectrum-Card-subtitle {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
@ -103,14 +105,6 @@
|
||||||
word-wrap: anywhere;
|
word-wrap: anywhere;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
a {
|
|
||||||
transition: color 130ms ease-in-out;
|
|
||||||
color: var(--spectrum-alias-text-color);
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: var(--spectrum-link-primary-m-text-color-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.horizontal .spectrum-Card-coverPhoto {
|
.horizontal .spectrum-Card-coverPhoto {
|
||||||
flex: 0 0 160px;
|
flex: 0 0 160px;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
|
|
@ -14,15 +14,20 @@
|
||||||
export let limit
|
export let limit
|
||||||
export let showTitleButton
|
export let showTitleButton
|
||||||
export let titleButtonText
|
export let titleButtonText
|
||||||
export let titleButtonOnClick
|
export let titleButtonURL
|
||||||
|
export let titleButtonPeek
|
||||||
export let cardTitle
|
export let cardTitle
|
||||||
export let cardSubtitle
|
export let cardSubtitle
|
||||||
export let cardDescription
|
export let cardDescription
|
||||||
export let cardImageURL
|
export let cardImageURL
|
||||||
|
export let linkCardTitle
|
||||||
|
export let cardURL
|
||||||
|
export let cardPeek
|
||||||
export let cardHorizontal
|
export let cardHorizontal
|
||||||
export let showCardButton
|
export let showCardButton
|
||||||
export let cardButtonText
|
export let cardButtonText
|
||||||
export let cardButtonOnClick
|
export let cardButtonOnClick
|
||||||
|
export let linkColumn
|
||||||
|
|
||||||
const { API, styleable } = getContext("sdk")
|
const { API, styleable } = getContext("sdk")
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
|
@ -37,11 +42,27 @@
|
||||||
|
|
||||||
let formId
|
let formId
|
||||||
let dataProviderId
|
let dataProviderId
|
||||||
|
let repeaterId
|
||||||
let schema
|
let schema
|
||||||
|
|
||||||
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
|
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
|
||||||
$: enrichedFilter = enrichFilter(filter, enrichedSearchColumns, formId)
|
$: enrichedFilter = enrichFilter(filter, enrichedSearchColumns, formId)
|
||||||
$: cardWidth = cardHorizontal ? 420 : 300
|
$: cardWidth = cardHorizontal ? 420 : 300
|
||||||
|
$: fullCardURL = buildFullCardUrl(
|
||||||
|
linkCardTitle,
|
||||||
|
cardURL,
|
||||||
|
repeaterId,
|
||||||
|
linkColumn
|
||||||
|
)
|
||||||
|
$: titleButtonAction = [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Navigate To",
|
||||||
|
parameters: {
|
||||||
|
peek: titleButtonPeek,
|
||||||
|
url: titleButtonURL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
// Enrich the default filter with the specified search fields
|
// Enrich the default filter with the specified search fields
|
||||||
const enrichFilter = (filter, columns, formId) => {
|
const enrichFilter = (filter, columns, formId) => {
|
||||||
|
@ -49,7 +70,7 @@
|
||||||
columns?.forEach(column => {
|
columns?.forEach(column => {
|
||||||
enrichedFilter.push({
|
enrichedFilter.push({
|
||||||
field: column.name,
|
field: column.name,
|
||||||
operator: "equal",
|
operator: column.type === "string" ? "string" : "equal",
|
||||||
type: "string",
|
type: "string",
|
||||||
valueType: "Binding",
|
valueType: "Binding",
|
||||||
value: `{{ [${formId}].[${column.name}] }}`,
|
value: `{{ [${formId}].[${column.name}] }}`,
|
||||||
|
@ -68,12 +89,23 @@
|
||||||
enrichedColumns.push({
|
enrichedColumns.push({
|
||||||
name: column,
|
name: column,
|
||||||
componentType,
|
componentType,
|
||||||
|
type: schemaType,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return enrichedColumns.slice(0, 3)
|
return enrichedColumns.slice(0, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Builds a full details page URL for the card title
|
||||||
|
const buildFullCardUrl = (link, url, repeaterId, linkColumn) => {
|
||||||
|
if (!link || !url || !repeaterId) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const col = linkColumn || "_id"
|
||||||
|
const split = url.split("/:")
|
||||||
|
return `${split[0]}/{{ [${repeaterId}].[${col}] }}`
|
||||||
|
}
|
||||||
|
|
||||||
// Load the datasource schema on mount so we can determine column types
|
// Load the datasource schema on mount so we can determine column types
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (dataSource) {
|
if (dataSource) {
|
||||||
|
@ -113,7 +145,7 @@
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
type="button"
|
type="button"
|
||||||
props={{
|
props={{
|
||||||
onClick: titleButtonOnClick,
|
onClick: titleButtonAction,
|
||||||
text: titleButtonText,
|
text: titleButtonText,
|
||||||
type: "cta",
|
type: "cta",
|
||||||
}}
|
}}
|
||||||
|
@ -136,6 +168,7 @@
|
||||||
>
|
>
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
type="repeater"
|
type="repeater"
|
||||||
|
bind:id={repeaterId}
|
||||||
context="repeater"
|
context="repeater"
|
||||||
props={{
|
props={{
|
||||||
dataProvider: `{{ literal [${dataProviderId}] }}`,
|
dataProvider: `{{ literal [${dataProviderId}] }}`,
|
||||||
|
@ -161,6 +194,8 @@
|
||||||
showButton: showCardButton,
|
showButton: showCardButton,
|
||||||
buttonText: cardButtonText,
|
buttonText: cardButtonText,
|
||||||
buttonOnClick: cardButtonOnClick,
|
buttonOnClick: cardButtonOnClick,
|
||||||
|
linkURL: fullCardURL,
|
||||||
|
linkPeek: cardPeek,
|
||||||
}}
|
}}
|
||||||
styles={{
|
styles={{
|
||||||
width: "auto",
|
width: "auto",
|
|
@ -22,7 +22,8 @@
|
||||||
export let linkPeek
|
export let linkPeek
|
||||||
export let showTitleButton
|
export let showTitleButton
|
||||||
export let titleButtonText
|
export let titleButtonText
|
||||||
export let titleButtonOnClick
|
export let titleButtonURL
|
||||||
|
export let titleButtonPeek
|
||||||
|
|
||||||
const { API, styleable } = getContext("sdk")
|
const { API, styleable } = getContext("sdk")
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
|
@ -41,6 +42,15 @@
|
||||||
|
|
||||||
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
|
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
|
||||||
$: enrichedFilter = enrichFilter(filter, enrichedSearchColumns, formId)
|
$: enrichedFilter = enrichFilter(filter, enrichedSearchColumns, formId)
|
||||||
|
$: titleButtonAction = [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Navigate To",
|
||||||
|
parameters: {
|
||||||
|
peek: titleButtonPeek,
|
||||||
|
url: titleButtonURL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
// Enrich the default filter with the specified search fields
|
// Enrich the default filter with the specified search fields
|
||||||
const enrichFilter = (filter, columns, formId) => {
|
const enrichFilter = (filter, columns, formId) => {
|
||||||
|
@ -48,7 +58,7 @@
|
||||||
columns?.forEach(column => {
|
columns?.forEach(column => {
|
||||||
enrichedFilter.push({
|
enrichedFilter.push({
|
||||||
field: column.name,
|
field: column.name,
|
||||||
operator: "equal",
|
operator: column.type === "string" ? "string" : "equal",
|
||||||
type: "string",
|
type: "string",
|
||||||
valueType: "Binding",
|
valueType: "Binding",
|
||||||
value: `{{ [${formId}].[${column.name}] }}`,
|
value: `{{ [${formId}].[${column.name}] }}`,
|
||||||
|
@ -67,6 +77,7 @@
|
||||||
enrichedColumns.push({
|
enrichedColumns.push({
|
||||||
name: column,
|
name: column,
|
||||||
componentType,
|
componentType,
|
||||||
|
type: schemaType,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -112,7 +123,7 @@
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
type="button"
|
type="button"
|
||||||
props={{
|
props={{
|
||||||
onClick: titleButtonOnClick,
|
onClick: titleButtonAction,
|
||||||
text: titleButtonText,
|
text: titleButtonText,
|
||||||
type: "cta",
|
type: "cta",
|
||||||
}}
|
}}
|
|
@ -1,2 +1,2 @@
|
||||||
export { default as tablewithsearch } from "./TableWithSearch.svelte"
|
export { default as tableblock } from "./TableBlock.svelte"
|
||||||
export { default as cardlistwithsearch } from "./CardListWithSearch.svelte"
|
export { default as cardsblock } from "./CardsBlock.svelte"
|
||||||
|
|
|
@ -4,6 +4,9 @@ import { builderStore } from "stores"
|
||||||
|
|
||||||
export const linkable = (node, href) => {
|
export const linkable = (node, href) => {
|
||||||
if (get(builderStore).inBuilder) {
|
if (get(builderStore).inBuilder) {
|
||||||
|
node.onclick = e => {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
link(node, href)
|
link(node, href)
|
||||||
|
|
|
@ -21,15 +21,18 @@ module PgMock {
|
||||||
function Pool() {
|
function Pool() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const on = jest.fn()
|
||||||
Pool.prototype.query = query
|
Pool.prototype.query = query
|
||||||
Pool.prototype.connect = jest.fn(() => {
|
Pool.prototype.connect = jest.fn(() => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return new Client()
|
return new Client()
|
||||||
})
|
})
|
||||||
|
Pool.prototype.on = on
|
||||||
|
|
||||||
pg.Client = Client
|
pg.Client = Client
|
||||||
pg.Pool = Pool
|
pg.Pool = Pool
|
||||||
pg.queryMock = query
|
pg.queryMock = query
|
||||||
|
pg.on = on
|
||||||
|
|
||||||
module.exports = pg
|
module.exports = pg
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "0.9.180-alpha.1",
|
"version": "0.9.184",
|
||||||
"description": "Budibase Web Server",
|
"description": "Budibase Web Server",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -68,9 +68,9 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/auth": "^0.9.180-alpha.1",
|
"@budibase/auth": "^0.9.184",
|
||||||
"@budibase/client": "^0.9.180-alpha.1",
|
"@budibase/client": "^0.9.184",
|
||||||
"@budibase/string-templates": "^0.9.180-alpha.1",
|
"@budibase/string-templates": "^0.9.184",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
"@koa/router": "8.0.0",
|
"@koa/router": "8.0.0",
|
||||||
"@sendgrid/mail": "7.1.1",
|
"@sendgrid/mail": "7.1.1",
|
||||||
|
|
|
@ -198,7 +198,7 @@ exports.fetchAppPackage = async ctx => {
|
||||||
application,
|
application,
|
||||||
screens,
|
screens,
|
||||||
layouts,
|
layouts,
|
||||||
clientLibPath: clientLibraryPath(ctx.params.appId),
|
clientLibPath: clientLibraryPath(ctx.params.appId, application.version),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,7 +324,7 @@ exports.delete = async ctx => {
|
||||||
ctx.body = result
|
ctx.body = result
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.sync = async ctx => {
|
exports.sync = async (ctx, next) => {
|
||||||
const appId = ctx.params.appId
|
const appId = ctx.params.appId
|
||||||
if (!isDevAppID(appId)) {
|
if (!isDevAppID(appId)) {
|
||||||
ctx.throw(400, "This action cannot be performed for production apps")
|
ctx.throw(400, "This action cannot be performed for production apps")
|
||||||
|
@ -332,6 +332,20 @@ exports.sync = async ctx => {
|
||||||
|
|
||||||
// replicate prod to dev
|
// replicate prod to dev
|
||||||
const prodAppId = getDeployedAppID(appId)
|
const prodAppId = getDeployedAppID(appId)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const prodDb = new CouchDB(prodAppId, { skip_setup: true })
|
||||||
|
const info = await prodDb.info()
|
||||||
|
if (info.error) throw info.error
|
||||||
|
} catch (err) {
|
||||||
|
// the database doesn't exist. Don't replicate
|
||||||
|
ctx.status = 200
|
||||||
|
ctx.body = {
|
||||||
|
message: "App sync not required, app not deployed.",
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
const replication = new Replication({
|
const replication = new Replication({
|
||||||
source: prodAppId,
|
source: prodAppId,
|
||||||
target: appId,
|
target: appId,
|
||||||
|
|
|
@ -82,6 +82,13 @@ exports.revert = async ctx => {
|
||||||
const db = new CouchDB(productionAppId, { skip_setup: true })
|
const db = new CouchDB(productionAppId, { skip_setup: true })
|
||||||
const info = await db.info()
|
const info = await db.info()
|
||||||
if (info.error) throw info.error
|
if (info.error) throw info.error
|
||||||
|
const deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS)
|
||||||
|
if (
|
||||||
|
!deploymentDoc.history ||
|
||||||
|
Object.keys(deploymentDoc.history).length === 0
|
||||||
|
) {
|
||||||
|
throw new Error("No deployments for app")
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return ctx.throw(400, "App has not yet been deployed")
|
return ctx.throw(400, "App has not yet been deployed")
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,7 @@ exports.serveApp = async function (ctx) {
|
||||||
title: appInfo.name,
|
title: appInfo.name,
|
||||||
production: env.isProd(),
|
production: env.isProd(),
|
||||||
appId,
|
appId,
|
||||||
clientLibPath: clientLibraryPath(appId),
|
clientLibPath: clientLibraryPath(appId, appInfo.version),
|
||||||
})
|
})
|
||||||
|
|
||||||
const appHbs = loadHandlebarsFile(`${__dirname}/templates/app.hbs`)
|
const appHbs = loadHandlebarsFile(`${__dirname}/templates/app.hbs`)
|
||||||
|
|
|
@ -8,6 +8,7 @@ const {
|
||||||
generateForeignKey,
|
generateForeignKey,
|
||||||
generateJunctionTableName,
|
generateJunctionTableName,
|
||||||
foreignKeyStructure,
|
foreignKeyStructure,
|
||||||
|
hasTypeChanged,
|
||||||
} = require("./utils")
|
} = require("./utils")
|
||||||
const {
|
const {
|
||||||
DataSourceOperation,
|
DataSourceOperation,
|
||||||
|
@ -172,6 +173,10 @@ exports.save = async function (ctx) {
|
||||||
oldTable = await getTable(appId, ctx.request.body._id)
|
oldTable = await getTable(appId, ctx.request.body._id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasTypeChanged(tableToSave, oldTable)) {
|
||||||
|
ctx.throw(400, "A column type has changed.")
|
||||||
|
}
|
||||||
|
|
||||||
const db = new CouchDB(appId)
|
const db = new CouchDB(appId)
|
||||||
const datasource = await db.get(datasourceId)
|
const datasource = await db.get(datasourceId)
|
||||||
const oldTables = cloneDeep(datasource.entities)
|
const oldTables = cloneDeep(datasource.entities)
|
||||||
|
|
|
@ -2,7 +2,7 @@ const CouchDB = require("../../../db")
|
||||||
const linkRows = require("../../../db/linkedRows")
|
const linkRows = require("../../../db/linkedRows")
|
||||||
const { getRowParams, generateTableID } = require("../../../db/utils")
|
const { getRowParams, generateTableID } = require("../../../db/utils")
|
||||||
const { FieldTypes } = require("../../../constants")
|
const { FieldTypes } = require("../../../constants")
|
||||||
const { TableSaveFunctions } = require("./utils")
|
const { TableSaveFunctions, hasTypeChanged } = require("./utils")
|
||||||
|
|
||||||
exports.save = async function (ctx) {
|
exports.save = async function (ctx) {
|
||||||
const appId = ctx.appId
|
const appId = ctx.appId
|
||||||
|
@ -21,6 +21,10 @@ exports.save = async function (ctx) {
|
||||||
oldTable = await db.get(ctx.request.body._id)
|
oldTable = await db.get(ctx.request.body._id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasTypeChanged(tableToSave, oldTable)) {
|
||||||
|
ctx.throw(400, "A column type has changed.")
|
||||||
|
}
|
||||||
|
|
||||||
// saving a table is a complex operation, involving many different steps, this
|
// saving a table is a complex operation, involving many different steps, this
|
||||||
// has been broken out into a utility to make it more obvious/easier to manipulate
|
// has been broken out into a utility to make it more obvious/easier to manipulate
|
||||||
const tableSaveFunctions = new TableSaveFunctions({
|
const tableSaveFunctions = new TableSaveFunctions({
|
||||||
|
|
|
@ -8,7 +8,7 @@ const {
|
||||||
const { isEqual } = require("lodash/fp")
|
const { isEqual } = require("lodash/fp")
|
||||||
const { AutoFieldSubTypes, FieldTypes } = require("../../../constants")
|
const { AutoFieldSubTypes, FieldTypes } = require("../../../constants")
|
||||||
const { inputProcessing } = require("../../../utilities/rowProcessor")
|
const { inputProcessing } = require("../../../utilities/rowProcessor")
|
||||||
const { USERS_TABLE_SCHEMA } = require("../../../constants")
|
const { USERS_TABLE_SCHEMA, SwitchableTypes } = require("../../../constants")
|
||||||
const {
|
const {
|
||||||
isExternalTable,
|
isExternalTable,
|
||||||
breakExternalTableId,
|
breakExternalTableId,
|
||||||
|
@ -335,4 +335,21 @@ exports.foreignKeyStructure = (keyName, meta = null) => {
|
||||||
return structure
|
return structure
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.hasTypeChanged = (table, oldTable) => {
|
||||||
|
if (!oldTable) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for (let [key, field] of Object.entries(oldTable.schema)) {
|
||||||
|
const oldType = field.type
|
||||||
|
if (!table.schema[key]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const newType = table.schema[key].type
|
||||||
|
if (oldType !== newType && SwitchableTypes.indexOf(oldType) === -1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
exports.TableSaveFunctions = TableSaveFunctions
|
exports.TableSaveFunctions = TableSaveFunctions
|
||||||
|
|
|
@ -45,6 +45,13 @@ exports.FieldTypes = {
|
||||||
INTERNAL: "internal",
|
INTERNAL: "internal",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.SwitchableTypes = [
|
||||||
|
exports.FieldTypes.STRING,
|
||||||
|
exports.FieldTypes.OPTIONS,
|
||||||
|
exports.FieldTypes.NUMBER,
|
||||||
|
exports.FieldTypes.BOOLEAN,
|
||||||
|
]
|
||||||
|
|
||||||
exports.RelationshipTypes = {
|
exports.RelationshipTypes = {
|
||||||
ONE_TO_MANY: "one-to-many",
|
ONE_TO_MANY: "one-to-many",
|
||||||
MANY_TO_ONE: "many-to-one",
|
MANY_TO_ONE: "many-to-one",
|
||||||
|
|
|
@ -30,7 +30,7 @@ function generateSchema(
|
||||||
// skip things that are already correct
|
// skip things that are already correct
|
||||||
const oldColumn = oldTable ? oldTable.schema[key] : null
|
const oldColumn = oldTable ? oldTable.schema[key] : null
|
||||||
if (
|
if (
|
||||||
(oldColumn && oldColumn.type === column.type) ||
|
(oldColumn && oldColumn.type) ||
|
||||||
(primaryKey === key && !isJunction)
|
(primaryKey === key && !isJunction)
|
||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -28,6 +28,7 @@ module PostgresModule {
|
||||||
database: string
|
database: string
|
||||||
user: string
|
user: string
|
||||||
password: string
|
password: string
|
||||||
|
schema: string
|
||||||
ssl?: boolean
|
ssl?: boolean
|
||||||
ca?: string
|
ca?: string
|
||||||
rejectUnauthorized?: boolean
|
rejectUnauthorized?: boolean
|
||||||
|
@ -65,6 +66,11 @@ module PostgresModule {
|
||||||
default: "root",
|
default: "root",
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
schema: {
|
||||||
|
type: DatasourceFieldTypes.STRING,
|
||||||
|
default: "public",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
ssl: {
|
ssl: {
|
||||||
type: DatasourceFieldTypes.BOOLEAN,
|
type: DatasourceFieldTypes.BOOLEAN,
|
||||||
default: false,
|
default: false,
|
||||||
|
@ -124,8 +130,7 @@ module PostgresModule {
|
||||||
public tables: Record<string, Table> = {}
|
public tables: Record<string, Table> = {}
|
||||||
public schemaErrors: Record<string, string> = {}
|
public schemaErrors: Record<string, string> = {}
|
||||||
|
|
||||||
COLUMNS_SQL =
|
COLUMNS_SQL!: string
|
||||||
"select * from information_schema.columns where not table_schema = 'information_schema' and not table_schema = 'pg_catalog'"
|
|
||||||
|
|
||||||
PRIMARY_KEYS_SQL = `
|
PRIMARY_KEYS_SQL = `
|
||||||
select tc.table_schema, tc.table_name, kc.column_name as primary_key
|
select tc.table_schema, tc.table_name, kc.column_name as primary_key
|
||||||
|
@ -155,6 +160,17 @@ module PostgresModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.client = this.pool
|
this.client = this.pool
|
||||||
|
this.setSchema()
|
||||||
|
}
|
||||||
|
|
||||||
|
setSchema() {
|
||||||
|
if (!this.config.schema) {
|
||||||
|
this.config.schema = 'public'
|
||||||
|
}
|
||||||
|
this.client.on('connect', (client: any) => {
|
||||||
|
client.query(`SET search_path TO ${this.config.schema}`);
|
||||||
|
});
|
||||||
|
this.COLUMNS_SQL = `select * from information_schema.columns where table_schema = '${this.config.schema}'`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -15,6 +15,10 @@ describe("Postgres Integration", () => {
|
||||||
config = new TestConfiguration()
|
config = new TestConfiguration()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("calls the connection callback", async () => {
|
||||||
|
expect(pg.on).toHaveBeenCalledWith('connect', expect.anything())
|
||||||
|
})
|
||||||
|
|
||||||
it("calls the create method with the correct params", async () => {
|
it("calls the create method with the correct params", async () => {
|
||||||
const sql = "insert into users (name, age) values ('Joe', 123);"
|
const sql = "insert into users (name, age) values ('Joe', 123);"
|
||||||
await config.integration.create({
|
await config.integration.create({
|
||||||
|
|
|
@ -51,11 +51,16 @@ exports.objectStoreUrl = () => {
|
||||||
* @return {string} The URL to be inserted into appPackage response or server rendered
|
* @return {string} The URL to be inserted into appPackage response or server rendered
|
||||||
* app index file.
|
* app index file.
|
||||||
*/
|
*/
|
||||||
exports.clientLibraryPath = appId => {
|
exports.clientLibraryPath = (appId, version) => {
|
||||||
if (env.isProd()) {
|
if (env.isProd()) {
|
||||||
return `${exports.objectStoreUrl()}/${sanitizeKey(
|
let url = `${exports.objectStoreUrl()}/${sanitizeKey(
|
||||||
appId
|
appId
|
||||||
)}/budibase-client.js`
|
)}/budibase-client.js`
|
||||||
|
// append app version to bust the cache
|
||||||
|
if (version) {
|
||||||
|
url += `?v=${version}`
|
||||||
|
}
|
||||||
|
return url
|
||||||
} else {
|
} else {
|
||||||
return `/api/assets/client`
|
return `/api/assets/client`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/string-templates",
|
"name": "@budibase/string-templates",
|
||||||
"version": "0.9.180-alpha.1",
|
"version": "0.9.184",
|
||||||
"description": "Handlebars wrapper for Budibase templating.",
|
"description": "Handlebars wrapper for Budibase templating.",
|
||||||
"main": "src/index.cjs",
|
"main": "src/index.cjs",
|
||||||
"module": "dist/bundle.mjs",
|
"module": "dist/bundle.mjs",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/worker",
|
"name": "@budibase/worker",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "0.9.180-alpha.1",
|
"version": "0.9.184",
|
||||||
"description": "Budibase background service",
|
"description": "Budibase background service",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -29,8 +29,8 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/auth": "^0.9.180-alpha.1",
|
"@budibase/auth": "^0.9.184",
|
||||||
"@budibase/string-templates": "^0.9.180-alpha.1",
|
"@budibase/string-templates": "^0.9.184",
|
||||||
"@koa/router": "^8.0.0",
|
"@koa/router": "^8.0.0",
|
||||||
"@sentry/node": "^6.0.0",
|
"@sentry/node": "^6.0.0",
|
||||||
"@techpass/passport-openidconnect": "^0.3.0",
|
"@techpass/passport-openidconnect": "^0.3.0",
|
||||||
|
|
|
@ -27,7 +27,7 @@ exports.syncUserInApps = async userId => {
|
||||||
"POST",
|
"POST",
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
if (response.status !== 200) {
|
if (response && response.status !== 200) {
|
||||||
throw "Unable to sync user."
|
throw "Unable to sync user."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue