Merge branch 'master' of github.com:budibase/budibase into mongo-tests

This commit is contained in:
Sam Rose 2024-02-06 10:51:18 +00:00
commit 0a30fb3364
No known key found for this signature in database
21 changed files with 261 additions and 190 deletions

View File

@ -1,5 +1,5 @@
{ {
"version": "2.17.2", "version": "2.17.6",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*", "packages/*",

@ -1 +1 @@
Subproject commit 52f51dcfb96d3fe58c8cc7a905e7d733f7cd84c2 Subproject commit cc12291732ee902dc832bc7d93cf2086ffdf0cff

View File

@ -7,6 +7,9 @@ import {
findHBSBlocks, findHBSBlocks,
} from "@budibase/string-templates" } from "@budibase/string-templates"
import { capitalise } from "helpers" import { capitalise } from "helpers"
import { Constants } from "@budibase/frontend-core"
const { ContextScopes } = Constants
/** /**
* Recursively searches for a specific component ID * Recursively searches for a specific component ID
@ -263,11 +266,59 @@ export const getComponentName = component => {
if (component == null) { if (component == null) {
return "" return ""
} }
const components = get(store)?.components || {} const components = get(store)?.components || {}
const componentDefinition = components[component._component] || {} const componentDefinition = components[component._component] || {}
const name = return componentDefinition.friendlyName || componentDefinition.name || ""
componentDefinition.friendlyName || componentDefinition.name || "" }
return name /**
* Recurses through the component tree and builds a tree of contexts provided
* by components.
*/
export const buildContextTree = (
rootComponent,
tree = { root: [] },
currentBranch = "root"
) => {
// Sanity check
if (!rootComponent) {
return tree
}
// Process this component's contexts
const def = store.actions.components.getDefinition(rootComponent._component)
if (def?.context) {
tree[currentBranch].push(rootComponent._id)
const contexts = Array.isArray(def.context) ? def.context : [def.context]
// If we provide local context, start a new branch for our children
if (contexts.some(context => context.scope === ContextScopes.Local)) {
currentBranch = rootComponent._id
tree[rootComponent._id] = []
}
}
// Process children
if (rootComponent._children) {
rootComponent._children.forEach(child => {
buildContextTree(child, tree, currentBranch)
})
}
return tree
}
/**
* Generates a lookup map of which context branch all components in a component
* tree are inside.
*/
export const buildContextTreeLookupMap = rootComponent => {
const tree = buildContextTree(rootComponent)
let map = {}
Object.entries(tree).forEach(([branch, ids]) => {
ids.forEach(id => {
map[id] = branch
})
})
return map
} }

View File

@ -1,6 +1,7 @@
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { get } from "svelte/store" import { get } from "svelte/store"
import { import {
buildContextTreeLookupMap,
findAllComponents, findAllComponents,
findAllMatchingComponents, findAllMatchingComponents,
findComponent, findComponent,
@ -20,11 +21,13 @@ import {
encodeJSBinding, encodeJSBinding,
} from "@budibase/string-templates" } from "@budibase/string-templates"
import { TableNames } from "../constants" import { TableNames } from "../constants"
import { JSONUtils } from "@budibase/frontend-core" import { JSONUtils, Constants } from "@budibase/frontend-core"
import ActionDefinitions from "components/design/settings/controls/ButtonActionEditor/manifest.json" import ActionDefinitions from "components/design/settings/controls/ButtonActionEditor/manifest.json"
import { environment, licensing } from "stores/portal" import { environment, licensing } from "stores/portal"
import { convertOldFieldFormat } from "components/design/settings/controls/FieldConfiguration/utils" import { convertOldFieldFormat } from "components/design/settings/controls/FieldConfiguration/utils"
const { ContextScopes } = Constants
// Regex to match all instances of template strings // Regex to match all instances of template strings
const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g
const CAPTURE_VAR_INSIDE_JS = /\$\("([^")]+)"\)/g const CAPTURE_VAR_INSIDE_JS = /\$\("([^")]+)"\)/g
@ -214,20 +217,27 @@ export const getComponentContexts = (
return [] return []
} }
let map = {} let map = {}
const componentPath = findComponentPath(asset.props, componentId)
const componentPathIds = componentPath.map(component => component._id)
const contextTreeLookupMap = buildContextTreeLookupMap(asset.props)
// Processes all contexts exposed by a component // Processes all contexts exposed by a component
const processContexts = scope => component => { const processContexts = scope => component => {
const def = store.actions.components.getDefinition(component._component) // Sanity check
const def = store.actions.components.getDefinition(component?._component)
if (!def?.context) { if (!def?.context) {
return return
} }
if (!map[component._id]) {
map[component._id] = { // Filter out global contexts not in the same branch.
component, // Global contexts are only valid if their branch root is an ancestor of
definition: def, // this component.
contexts: [], const branch = contextTreeLookupMap[component._id]
} if (branch !== "root" && !componentPathIds.includes(branch)) {
return
} }
// Process all contexts provided by this component
const contexts = Array.isArray(def.context) ? def.context : [def.context] const contexts = Array.isArray(def.context) ? def.context : [def.context]
contexts.forEach(context => { contexts.forEach(context => {
// Ensure type matches // Ensure type matches
@ -235,7 +245,7 @@ export const getComponentContexts = (
return return
} }
// Ensure scope matches // Ensure scope matches
let contextScope = context.scope || "global" let contextScope = context.scope || ContextScopes.Global
if (contextScope !== scope) { if (contextScope !== scope) {
return return
} }
@ -243,17 +253,23 @@ export const getComponentContexts = (
if (!isContextCompatibleWithComponent(context, component)) { if (!isContextCompatibleWithComponent(context, component)) {
return return
} }
if (!map[component._id]) {
map[component._id] = {
component,
definition: def,
contexts: [],
}
}
map[component._id].contexts.push(context) map[component._id].contexts.push(context)
}) })
} }
// Process all global contexts // Process all global contexts
const allComponents = findAllComponents(asset.props) const allComponents = findAllComponents(asset.props)
allComponents.forEach(processContexts("global")) allComponents.forEach(processContexts(ContextScopes.Global))
// Process all local contexts // Process all local contexts in the immediate tree
const localComponents = findComponentPath(asset.props, componentId) componentPath.forEach(processContexts(ContextScopes.Local))
localComponents.forEach(processContexts("local"))
// Exclude self if required // Exclude self if required
if (!options?.includeSelf) { if (!options?.includeSelf) {

View File

@ -15,7 +15,6 @@
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"
@ -33,6 +32,8 @@
import Editor from "components/integration/QueryEditor.svelte" import Editor from "components/integration/QueryEditor.svelte"
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte" import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
import CodeEditor from "components/common/CodeEditor/CodeEditor.svelte" import CodeEditor from "components/common/CodeEditor/CodeEditor.svelte"
import BindingPicker from "components/common/bindings/BindingPicker.svelte"
import { BindingHelpers } from "components/common/bindings/utils"
import { import {
bindingsToCompletions, bindingsToCompletions,
hbAutocomplete, hbAutocomplete,
@ -56,7 +57,7 @@
let drawer let drawer
let fillWidth = true let fillWidth = true
let inputData let inputData
let codeBindingOpen = false let insertAtPos, getCaretPosition
$: filters = lookForFilters(schemaProperties) || [] $: filters = lookForFilters(schemaProperties) || []
$: tempFilters = filters $: tempFilters = filters
$: stepId = block.stepId $: stepId = block.stepId
@ -75,6 +76,10 @@
$: isUpdateRow = stepId === ActionStepID.UPDATE_ROW $: isUpdateRow = stepId === ActionStepID.UPDATE_ROW
$: codeMode = $: codeMode =
stepId === "EXECUTE_BASH" ? EditorModes.Handlebars : EditorModes.JS stepId === "EXECUTE_BASH" ? EditorModes.Handlebars : EditorModes.JS
$: bindingsHelpers = new BindingHelpers(getCaretPosition, insertAtPos, {
disableWrapping: true,
})
$: editingJs = codeMode === EditorModes.JS
$: stepCompletions = $: stepCompletions =
codeMode === EditorModes.Handlebars codeMode === EditorModes.Handlebars
@ -539,39 +544,51 @@
/> />
{:else if value.customType === "code"} {:else if value.customType === "code"}
<CodeEditorModal> <CodeEditorModal>
{#if codeMode == EditorModes.JS} <div class:js-editor={editingJs}>
<ActionButton <div class:js-code={editingJs} style="width: 100%">
on:click={() => (codeBindingOpen = !codeBindingOpen)} <CodeEditor
quiet value={inputData[key]}
icon={codeBindingOpen ? "ChevronDown" : "ChevronRight"} on:change={e => {
> // need to pass without the value inside
<Detail size="S">Bindings</Detail> onChange({ detail: e.detail }, key)
</ActionButton> inputData[key] = e.detail
{#if codeBindingOpen} }}
<pre>{JSON.stringify(bindings, null, 2)}</pre> completions={stepCompletions}
{/if} mode={codeMode}
{/if} autocompleteEnabled={codeMode !== EditorModes.JS}
<CodeEditor bind:getCaretPosition
value={inputData[key]} bind:insertAtPos
on:change={e => { height={500}
// need to pass without the value inside />
onChange({ detail: e.detail }, key) <div class="messaging">
inputData[key] = e.detail {#if codeMode === EditorModes.Handlebars}
}} <Icon name="FlashOn" />
completions={stepCompletions} <div class="messaging-wrap">
mode={codeMode} <div>
autocompleteEnabled={codeMode != EditorModes.JS} Add available bindings by typing <strong>
height={500} &#125;&#125;
/> </strong>
<div class="messaging"> </div>
{#if codeMode == EditorModes.Handlebars} </div>
<Icon name="FlashOn" /> {/if}
<div class="messaging-wrap"> </div>
<div> </div>
Add available bindings by typing <strong> {#if editingJs}
&#125;&#125; <div class="js-binding-picker">
</strong> <BindingPicker
</div> {bindings}
allowHelpers={false}
addBinding={binding =>
bindingsHelpers.onSelectBinding(
inputData[key],
binding,
{
js: true,
dontDecode: true,
}
)}
mode="javascript"
/>
</div> </div>
{/if} {/if}
</div> </div>
@ -658,4 +675,20 @@
.test :global(.drawer) { .test :global(.drawer) {
width: 10000px !important; width: 10000px !important;
} }
.js-editor {
display: flex;
flex-direction: row;
flex-grow: 1;
width: 100%;
}
.js-code {
flex: 7;
}
.js-binding-picker {
flex: 3;
margin-top: calc((var(--spacing-xl) * -1) + 1px);
}
</style> </style>

View File

@ -54,6 +54,7 @@
export let placeholder = null export let placeholder = null
export let autocompleteEnabled = true export let autocompleteEnabled = true
export let autofocus = false export let autofocus = false
export let jsBindingWrapping = true
// Export a function to expose caret position // Export a function to expose caret position
export const getCaretPosition = () => { export const getCaretPosition = () => {
@ -187,7 +188,7 @@
) )
complete.push( complete.push(
EditorView.inputHandler.of((view, from, to, insert) => { EditorView.inputHandler.of((view, from, to, insert) => {
if (insert === "$") { if (jsBindingWrapping && insert === "$") {
let { text } = view.state.doc.lineAt(from) let { text } = view.state.doc.lineAt(from)
const left = from ? text.substring(0, from) : "" const left = from ? text.substring(0, from) : ""

View File

@ -286,13 +286,14 @@ export const hbInsert = (value, from, to, text) => {
return parsedInsert return parsedInsert
} }
export function jsInsert(value, from, to, text, { helper } = {}) { export function jsInsert(value, from, to, text, { helper, disableWrapping }) {
let parsedInsert = "" let parsedInsert = ""
const left = from ? value.substring(0, from) : "" const left = from ? value.substring(0, from) : ""
const right = to ? value.substring(to) : "" const right = to ? value.substring(to) : ""
if (disableWrapping) {
if (helper) { parsedInsert = text
} else if (helper) {
parsedInsert = `helpers.${text}()` parsedInsert = `helpers.${text}()`
} else if (!left.includes('$("') || !right.includes('")')) { } else if (!left.includes('$("') || !right.includes('")')) {
parsedInsert = `$("${text}")` parsedInsert = `$("${text}")`

View File

@ -29,10 +29,9 @@
hbAutocomplete, hbAutocomplete,
EditorModes, EditorModes,
bindingsToCompletions, bindingsToCompletions,
hbInsert,
jsInsert,
} from "../CodeEditor" } from "../CodeEditor"
import BindingPicker from "./BindingPicker.svelte" import BindingPicker from "./BindingPicker.svelte"
import { BindingHelpers } from "./utils"
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -60,8 +59,10 @@
let targetMode = null let targetMode = null
$: usingJS = mode === "JavaScript" $: usingJS = mode === "JavaScript"
$: editorMode = mode == "JavaScript" ? EditorModes.JS : EditorModes.Handlebars $: editorMode =
mode === "JavaScript" ? EditorModes.JS : EditorModes.Handlebars
$: bindingCompletions = bindingsToCompletions(bindings, editorMode) $: bindingCompletions = bindingsToCompletions(bindings, editorMode)
$: bindingHelpers = new BindingHelpers(getCaretPosition, insertAtPos)
const updateValue = val => { const updateValue = val => {
valid = isValid(readableToRuntimeBinding(bindings, val)) valid = isValid(readableToRuntimeBinding(bindings, val))
@ -70,31 +71,13 @@
} }
} }
// Adds a JS/HBS helper to the expression
const onSelectHelper = (helper, js) => { const onSelectHelper = (helper, js) => {
const pos = getCaretPosition() bindingHelpers.onSelectHelper(js ? jsValue : hbsValue, helper, { js })
const { start, end } = pos
if (js) {
let js = decodeJSBinding(jsValue)
const insertVal = jsInsert(js, start, end, helper.text, { helper: true })
insertAtPos({ start, end, value: insertVal })
} else {
const insertVal = hbInsert(hbsValue, start, end, helper.text)
insertAtPos({ start, end, value: insertVal })
}
} }
// Adds a data binding to the expression
const onSelectBinding = (binding, { forceJS } = {}) => { const onSelectBinding = (binding, { forceJS } = {}) => {
const { start, end } = getCaretPosition() const js = usingJS || forceJS
if (usingJS || forceJS) { bindingHelpers.onSelectBinding(js ? jsValue : hbsValue, binding, { js })
let js = decodeJSBinding(jsValue)
const insertVal = jsInsert(js, start, end, binding.readableBinding)
insertAtPos({ start, end, value: insertVal })
} else {
const insertVal = hbInsert(hbsValue, start, end, binding.readableBinding)
insertAtPos({ start, end, value: insertVal })
}
} }
const onChangeMode = e => { const onChangeMode = e => {

View File

@ -9,6 +9,7 @@
export let bindings export let bindings
export let mode export let mode
export let allowHelpers export let allowHelpers
export let noPaddingTop = false
let search = "" let search = ""
let popover let popover

View File

@ -1,38 +1,41 @@
export function addHBSBinding(value, caretPos, binding) { import { decodeJSBinding } from "@budibase/string-templates"
binding = typeof binding === "string" ? binding : binding.path import { hbInsert, jsInsert } from "components/common/CodeEditor"
value = value == null ? "" : value
const left = caretPos?.start ? value.substring(0, caretPos.start) : "" export class BindingHelpers {
const right = caretPos?.end ? value.substring(caretPos.end) : "" constructor(getCaretPosition, insertAtPos, { disableWrapping } = {}) {
if (!left.includes("{{") || !right.includes("}}")) { this.getCaretPosition = getCaretPosition
binding = `{{ ${binding} }}` this.insertAtPos = insertAtPos
this.disableWrapping = disableWrapping
} }
if (caretPos.start) {
value =
value.substring(0, caretPos.start) +
binding +
value.substring(caretPos.end, value.length)
} else {
value += binding
}
return value
}
export function addJSBinding(value, caretPos, binding, { helper } = {}) { // Adds a JS/HBS helper to the expression
binding = typeof binding === "string" ? binding : binding.path onSelectHelper(value, helper, { js, dontDecode }) {
value = value == null ? "" : value const pos = this.getCaretPosition()
if (!helper) { const { start, end } = pos
binding = `$("${binding}")` if (js) {
} else { const jsVal = dontDecode ? value : decodeJSBinding(value)
binding = `helpers.${binding}()` const insertVal = jsInsert(jsVal, start, end, helper.text, {
helper: true,
})
this.insertAtPos({ start, end, value: insertVal })
} else {
const insertVal = hbInsert(value, start, end, helper.text)
this.insertAtPos({ start, end, value: insertVal })
}
} }
if (caretPos.start) {
value = // Adds a data binding to the expression
value.substring(0, caretPos.start) + onSelectBinding(value, binding, { js, dontDecode }) {
binding + const { start, end } = this.getCaretPosition()
value.substring(caretPos.end, value.length) if (js) {
} else { const jsVal = dontDecode ? value : decodeJSBinding(value)
value += binding const insertVal = jsInsert(jsVal, start, end, binding.readableBinding, {
disableWrapping: this.disableWrapping,
})
this.insertAtPos({ start, end, value: insertVal })
} else {
const insertVal = hbInsert(value, start, end, binding.readableBinding)
this.insertAtPos({ start, end, value: insertVal })
}
} }
return value
} }

View File

@ -4720,7 +4720,8 @@
} }
], ],
"context": { "context": {
"type": "schema" "type": "schema",
"scope": "local"
} }
}, },
"daterangepicker": { "daterangepicker": {
@ -6742,6 +6743,17 @@
"key": "disabled", "key": "disabled",
"defaultValue": false "defaultValue": false
}, },
{
"type": "boolean",
"label": "Read only",
"key": "readonly",
"defaultValue": false,
"dependsOn": {
"setting": "disabled",
"value": true,
"invert": true
}
},
{ {
"type": "select", "type": "select",
"label": "Layout", "label": "Layout",

View File

@ -2,7 +2,9 @@
import { getContext } from "svelte" import { getContext } from "svelte"
import Placeholder from "./Placeholder.svelte" import Placeholder from "./Placeholder.svelte"
import Container from "./Container.svelte" import Container from "./Container.svelte"
import { ContextScopes } from "constants"
const { Provider, ContextScopes } = getContext("sdk")
const component = getContext("component")
export let dataProvider export let dataProvider
export let noRowsMessage export let noRowsMessage
@ -12,9 +14,6 @@
export let gap export let gap
export let scope = ContextScopes.Local export let scope = ContextScopes.Local
const { Provider } = getContext("sdk")
const component = getContext("component")
$: rows = dataProvider?.rows ?? [] $: rows = dataProvider?.rows ?? []
$: loaded = dataProvider?.loaded ?? true $: loaded = dataProvider?.loaded ?? true
</script> </script>

View File

@ -3,9 +3,9 @@
export let row export let row
const { Provider } = getContext("sdk") const { Provider, ContextScopes } = getContext("sdk")
</script> </script>
<Provider data={row}> <Provider data={row} scope={ContextScopes.Local}>
<slot /> <slot />
</Provider> </Provider>

View File

@ -1,9 +1,11 @@
<script> <script>
import { getContext, setContext, onDestroy } from "svelte" import { getContext, setContext, onDestroy } from "svelte"
import { dataSourceStore, createContextStore } from "stores" import { dataSourceStore, createContextStore } from "stores"
import { ActionTypes, ContextScopes } from "constants" import { ActionTypes } from "constants"
import { generate } from "shortid" import { generate } from "shortid"
const { ContextScopes } = getContext("sdk")
export let data export let data
export let actions export let actions
export let key export let key
@ -33,7 +35,7 @@
const provideData = newData => { const provideData = newData => {
const dataKey = JSON.stringify(newData) const dataKey = JSON.stringify(newData)
if (dataKey !== lastDataKey) { if (dataKey !== lastDataKey) {
context.actions.provideData(providerKey, newData, scope) context.actions.provideData(providerKey, newData)
lastDataKey = dataKey lastDataKey = dataKey
} }
} }
@ -43,7 +45,7 @@
if (actionsKey !== lastActionsKey) { if (actionsKey !== lastActionsKey) {
lastActionsKey = actionsKey lastActionsKey = actionsKey
newActions?.forEach(({ type, callback, metadata }) => { newActions?.forEach(({ type, callback, metadata }) => {
context.actions.provideAction(providerKey, type, callback, scope) context.actions.provideAction(providerKey, type, callback)
// Register any "refresh datasource" actions with a singleton store // Register any "refresh datasource" actions with a singleton store
// so we can easily refresh data at all levels for any datasource // so we can easily refresh data at all levels for any datasource

View File

@ -12,10 +12,5 @@ export const ActionTypes = {
ScrollTo: "ScrollTo", ScrollTo: "ScrollTo",
} }
export const ContextScopes = {
Local: "local",
Global: "global",
}
export const DNDPlaceholderID = "dnd-placeholder" export const DNDPlaceholderID = "dnd-placeholder"
export const ScreenslotType = "screenslot" export const ScreenslotType = "screenslot"

View File

@ -23,12 +23,12 @@ import { getAction } from "utils/getAction"
import Provider from "components/context/Provider.svelte" import Provider from "components/context/Provider.svelte"
import Block from "components/Block.svelte" import Block from "components/Block.svelte"
import BlockComponent from "components/BlockComponent.svelte" import BlockComponent from "components/BlockComponent.svelte"
import { ActionTypes, ContextScopes } from "./constants" import { ActionTypes } from "./constants"
import { fetchDatasourceSchema } from "./utils/schema.js" import { fetchDatasourceSchema } from "./utils/schema.js"
import { getAPIKey } from "./utils/api.js" import { getAPIKey } from "./utils/api.js"
import { enrichButtonActions } from "./utils/buttonActions.js" import { enrichButtonActions } from "./utils/buttonActions.js"
import { processStringSync, makePropSafe } from "@budibase/string-templates" import { processStringSync, makePropSafe } from "@budibase/string-templates"
import { fetchData, LuceneUtils } from "@budibase/frontend-core" import { fetchData, LuceneUtils, Constants } from "@budibase/frontend-core"
export default { export default {
API, API,
@ -57,7 +57,7 @@ export default {
fetchDatasourceSchema, fetchDatasourceSchema,
fetchData, fetchData,
LuceneUtils, LuceneUtils,
ContextScopes, ContextScopes: Constants.ContextScopes,
getAPIKey, getAPIKey,
enrichButtonActions, enrichButtonActions,
processStringSync, processStringSync,

View File

@ -1,5 +1,4 @@
import { writable, derived } from "svelte/store" import { writable, derived } from "svelte/store"
import { ContextScopes } from "constants"
export const createContextStore = parentContext => { export const createContextStore = parentContext => {
const context = writable({}) const context = writable({})
@ -20,60 +19,34 @@ export const createContextStore = parentContext => {
} }
// Provide some data in context // Provide some data in context
const provideData = (providerId, data, scope = ContextScopes.Global) => { const provideData = (providerId, data) => {
if (!providerId || data === undefined) { if (!providerId || data === undefined) {
return return
} }
// Proxy message up the chain if we have a parent and are providing global
// context
if (scope === ContextScopes.Global && parentContext) {
parentContext.actions.provideData(providerId, data, scope)
}
// Otherwise this is either the context root, or we're providing a local // Otherwise this is either the context root, or we're providing a local
// context override, so we need to update the local context instead // context override, so we need to update the local context instead
else { context.update(state => {
context.update(state => { state[providerId] = data
state[providerId] = data return state
return state })
}) broadcastChange(providerId)
broadcastChange(providerId)
}
} }
// Provides some action in context // Provides some action in context
const provideAction = ( const provideAction = (providerId, actionType, callback) => {
providerId,
actionType,
callback,
scope = ContextScopes.Global
) => {
if (!providerId || !actionType) { if (!providerId || !actionType) {
return return
} }
// Proxy message up the chain if we have a parent and are providing global
// context
if (scope === ContextScopes.Global && parentContext) {
parentContext.actions.provideAction(
providerId,
actionType,
callback,
scope
)
}
// Otherwise this is either the context root, or we're providing a local // Otherwise this is either the context root, or we're providing a local
// context override, so we need to update the local context instead // context override, so we need to update the local context instead
else { const key = `${providerId}_${actionType}`
const key = `${providerId}_${actionType}` context.update(state => {
context.update(state => { state[key] = callback
state[key] = callback return state
return state })
}) broadcastChange(key)
broadcastChange(key)
}
} }
const observeChanges = callback => { const observeChanges = callback => {

View File

@ -106,3 +106,8 @@ export const Themes = [
export const EventPublishType = { export const EventPublishType = {
ENV_VAR_UPGRADE_PANEL_OPENED: "environment_variable_upgrade_panel_opened", ENV_VAR_UPGRADE_PANEL_OPENED: "environment_variable_upgrade_panel_opened",
} }
export const ContextScopes = {
Local: "local",
Global: "global",
}

@ -1 +1 @@
Subproject commit 4f9616f163039a0eea81319d8e2288340a2ebc79 Subproject commit aaf7101cd1493215155cc8f83124c70d53eb1be4

View File

@ -1,4 +1,4 @@
import fetch from "node-fetch" import { Response, default as fetch } from "node-fetch"
import env from "../environment" import env from "../environment"
import { checkSlashesInUrl } from "./index" import { checkSlashesInUrl } from "./index"
import { import {
@ -40,25 +40,21 @@ export function request(ctx?: Ctx, request?: any) {
} }
async function checkResponse( async function checkResponse(
response: any, response: Response,
errorMsg: string, errorMsg: string,
{ ctx }: { ctx?: Ctx } = {} { ctx }: { ctx?: Ctx } = {}
) { ) {
if (response.status !== 200) { if (response.status >= 300) {
let error let responseErrorMessage
try { if (response.headers.get("content-type")?.includes("json")) {
error = await response.json() const error = await response.json()
if (!error.message) { responseErrorMessage = error.message ?? JSON.stringify(error)
error = JSON.stringify(error) } else {
} responseErrorMessage = await response.text()
} catch (err) {
error = await response.text()
} }
const msg = `Unable to ${errorMsg} - ${ const msg = `Unable to ${errorMsg} - ${responseErrorMessage}`
error.message ? error.message : error
}`
if (ctx) { if (ctx) {
ctx.throw(400, msg) ctx.throw(msg, response.status)
} else { } else {
throw msg throw msg
} }

View File

@ -63,7 +63,7 @@
"koa-useragent": "^4.1.0", "koa-useragent": "^4.1.0",
"lodash": "4.17.21", "lodash": "4.17.21",
"node-fetch": "2.6.7", "node-fetch": "2.6.7",
"nodemailer": "6.7.2", "nodemailer": "6.9.9",
"passport-google-oauth": "2.0.0", "passport-google-oauth": "2.0.0",
"passport-local": "1.0.0", "passport-local": "1.0.0",
"pouchdb": "7.3.0", "pouchdb": "7.3.0",