Merge branch 'master' of github.com:Budibase/budibase into feature/sql-attachments
This commit is contained in:
commit
1c41fee91a
|
@ -1,32 +1,34 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import "@spectrum-css/textfield/dist/index-vars.css"
|
import "@spectrum-css/textfield/dist/index-vars.css"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import type { FocusEventHandler } from "svelte/elements"
|
||||||
|
|
||||||
export let value = ""
|
export let value: string | null = null
|
||||||
export let placeholder: string | undefined = undefined
|
export let placeholder: string | null = null
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let id: string | undefined = undefined
|
export let id: string | null = null
|
||||||
export let height: string | number | undefined = undefined
|
export let height: number | null = null
|
||||||
export let minHeight: string | number | undefined = undefined
|
export let minHeight: number | null = null
|
||||||
export const getCaretPosition = () => ({
|
|
||||||
start: textarea.selectionStart,
|
|
||||||
end: textarea.selectionEnd,
|
|
||||||
})
|
|
||||||
export let align = null
|
export let align = null
|
||||||
|
|
||||||
let focus = false
|
let isFocused = false
|
||||||
let textarea: any
|
let textarea: HTMLTextAreaElement
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher<{ change: string }>()
|
||||||
const onChange = (event: any) => {
|
const onChange: FocusEventHandler<HTMLTextAreaElement> = event => {
|
||||||
dispatch("change", event.target.value)
|
dispatch("change", event.currentTarget.value)
|
||||||
focus = false
|
isFocused = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStyleString = (
|
export function focus() {
|
||||||
attribute: string,
|
textarea.focus()
|
||||||
value: string | number | undefined
|
}
|
||||||
) => {
|
|
||||||
|
export function contents() {
|
||||||
|
return textarea.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStyleString = (attribute: string, value: number | null) => {
|
||||||
if (!attribute || value == null) {
|
if (!attribute || value == null) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -44,7 +46,7 @@
|
||||||
style={`${heightString}${minHeightString}`}
|
style={`${heightString}${minHeightString}`}
|
||||||
class="spectrum-Textfield spectrum-Textfield--multiline"
|
class="spectrum-Textfield spectrum-Textfield--multiline"
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
class:is-focused={focus}
|
class:is-focused={isFocused}
|
||||||
>
|
>
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<textarea
|
<textarea
|
||||||
|
@ -55,8 +57,9 @@
|
||||||
{disabled}
|
{disabled}
|
||||||
{readonly}
|
{readonly}
|
||||||
{id}
|
{id}
|
||||||
on:focus={() => (focus = true)}
|
on:focus={() => (isFocused = true)}
|
||||||
on:blur={onChange}
|
on:blur={onChange}
|
||||||
|
on:keypress
|
||||||
>{value || ""}</textarea>
|
>{value || ""}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -5,17 +5,25 @@
|
||||||
|
|
||||||
export let value: string | undefined = undefined
|
export let value: string | undefined = undefined
|
||||||
export let label: string | undefined = undefined
|
export let label: string | undefined = undefined
|
||||||
export let labelPosition: string = "above"
|
export let labelPosition = "above"
|
||||||
export let placeholder: string | undefined = undefined
|
export let placeholder: string | undefined = undefined
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let error: string | undefined = undefined
|
export let error: string | undefined = undefined
|
||||||
export let getCaretPosition: any = undefined
|
export let height: number | undefined = undefined
|
||||||
export let height: string | number | undefined = undefined
|
export let minHeight: number | undefined = undefined
|
||||||
export let minHeight: string | number | undefined = undefined
|
|
||||||
export let helpText: string | undefined = undefined
|
export let helpText: string | undefined = undefined
|
||||||
|
|
||||||
|
let textarea: TextArea
|
||||||
|
export function focus() {
|
||||||
|
textarea.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function contents() {
|
||||||
|
return textarea.contents()
|
||||||
|
}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = (e: any) => {
|
const onChange = (e: CustomEvent<string>) => {
|
||||||
value = e.detail
|
value = e.detail
|
||||||
dispatch("change", e.detail)
|
dispatch("change", e.detail)
|
||||||
}
|
}
|
||||||
|
@ -23,12 +31,13 @@
|
||||||
|
|
||||||
<Field {helpText} {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<TextArea
|
<TextArea
|
||||||
bind:getCaretPosition
|
bind:this={textarea}
|
||||||
{disabled}
|
{disabled}
|
||||||
{value}
|
{value}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
{height}
|
{height}
|
||||||
{minHeight}
|
{minHeight}
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
|
on:keypress
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -56,7 +56,10 @@
|
||||||
memo,
|
memo,
|
||||||
fetchData,
|
fetchData,
|
||||||
} from "@budibase/frontend-core"
|
} from "@budibase/frontend-core"
|
||||||
import { getSchemaForDatasourcePlus } from "@/dataBinding"
|
import {
|
||||||
|
getSchemaForDatasourcePlus,
|
||||||
|
readableToRuntimeBinding,
|
||||||
|
} from "@/dataBinding"
|
||||||
import { TriggerStepID, ActionStepID } from "@/constants/backend/automations"
|
import { TriggerStepID, ActionStepID } from "@/constants/backend/automations"
|
||||||
import { onMount, createEventDispatcher } from "svelte"
|
import { onMount, createEventDispatcher } from "svelte"
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
|
@ -1034,7 +1037,10 @@
|
||||||
{bindings}
|
{bindings}
|
||||||
{schema}
|
{schema}
|
||||||
panel={AutomationBindingPanel}
|
panel={AutomationBindingPanel}
|
||||||
on:change={e => onChange({ [key]: e.detail })}
|
on:change={e =>
|
||||||
|
onChange({
|
||||||
|
[key]: readableToRuntimeBinding(bindings, e.detail),
|
||||||
|
})}
|
||||||
context={$memoContext}
|
context={$memoContext}
|
||||||
value={inputData[key]}
|
value={inputData[key]}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -6,7 +6,13 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Label } from "@budibase/bbui"
|
import {
|
||||||
|
Button,
|
||||||
|
Label,
|
||||||
|
notifications,
|
||||||
|
Popover,
|
||||||
|
TextArea,
|
||||||
|
} from "@budibase/bbui"
|
||||||
import { onMount, createEventDispatcher, onDestroy } from "svelte"
|
import { onMount, createEventDispatcher, onDestroy } from "svelte"
|
||||||
import { FIND_ANY_HBS_REGEX } from "@budibase/string-templates"
|
import { FIND_ANY_HBS_REGEX } from "@budibase/string-templates"
|
||||||
|
|
||||||
|
@ -53,11 +59,14 @@
|
||||||
import { javascript } from "@codemirror/lang-javascript"
|
import { javascript } from "@codemirror/lang-javascript"
|
||||||
import { EditorModes } from "./"
|
import { EditorModes } from "./"
|
||||||
import { themeStore } from "@/stores/portal"
|
import { themeStore } from "@/stores/portal"
|
||||||
import type { EditorMode } from "@budibase/types"
|
import { FeatureFlag, type EditorMode } from "@budibase/types"
|
||||||
import { tooltips } from "@codemirror/view"
|
import { tooltips } from "@codemirror/view"
|
||||||
import type { BindingCompletion, CodeValidator } from "@/types"
|
import type { BindingCompletion, CodeValidator } from "@/types"
|
||||||
import { validateHbsTemplate } from "./validator/hbs"
|
import { validateHbsTemplate } from "./validator/hbs"
|
||||||
import { validateJsTemplate } from "./validator/js"
|
import { validateJsTemplate } from "./validator/js"
|
||||||
|
import { featureFlag } from "@/helpers"
|
||||||
|
import { API } from "@/api"
|
||||||
|
import Spinner from "../Spinner.svelte"
|
||||||
|
|
||||||
export let label: string | undefined = undefined
|
export let label: string | undefined = undefined
|
||||||
export let completions: BindingCompletion[] = []
|
export let completions: BindingCompletion[] = []
|
||||||
|
@ -86,6 +95,13 @@
|
||||||
let isDark = !currentTheme.includes("light")
|
let isDark = !currentTheme.includes("light")
|
||||||
let themeConfig = new Compartment()
|
let themeConfig = new Compartment()
|
||||||
|
|
||||||
|
let popoverAnchor: HTMLElement
|
||||||
|
let popover: Popover
|
||||||
|
let promptInput: TextArea
|
||||||
|
$: aiGenEnabled =
|
||||||
|
featureFlag.isEnabled(FeatureFlag.AI_JS_GENERATION) &&
|
||||||
|
mode.name === "javascript"
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (autofocus && isEditorInitialised) {
|
if (autofocus && isEditorInitialised) {
|
||||||
editor.focus()
|
editor.focus()
|
||||||
|
@ -139,6 +155,57 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: promptLoading = false
|
||||||
|
let popoverWidth = 300
|
||||||
|
let suggestedCode: string | null = null
|
||||||
|
let previousContents: string | null = null
|
||||||
|
const generateJs = async (prompt: string) => {
|
||||||
|
previousContents = editor.state.doc.toString()
|
||||||
|
promptLoading = true
|
||||||
|
popoverWidth = 30
|
||||||
|
let code = ""
|
||||||
|
try {
|
||||||
|
const resp = await API.generateJs({ prompt })
|
||||||
|
code = resp.code
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
notifications.error("Unable to generate code, please try again later.")
|
||||||
|
code = previousContents
|
||||||
|
popoverWidth = 300
|
||||||
|
promptLoading = false
|
||||||
|
popover.hide()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
value = code
|
||||||
|
editor.dispatch({
|
||||||
|
changes: { from: 0, to: editor.state.doc.length, insert: code },
|
||||||
|
})
|
||||||
|
suggestedCode = code
|
||||||
|
popoverWidth = 100
|
||||||
|
promptLoading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const acceptSuggestion = () => {
|
||||||
|
suggestedCode = null
|
||||||
|
previousContents = null
|
||||||
|
resetPopover()
|
||||||
|
}
|
||||||
|
|
||||||
|
const rejectSuggestion = () => {
|
||||||
|
suggestedCode = null
|
||||||
|
value = previousContents || ""
|
||||||
|
editor.dispatch({
|
||||||
|
changes: { from: 0, to: editor.state.doc.length, insert: value },
|
||||||
|
})
|
||||||
|
previousContents = null
|
||||||
|
resetPopover()
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetPopover = () => {
|
||||||
|
popover.hide()
|
||||||
|
popoverWidth = 300
|
||||||
|
}
|
||||||
|
|
||||||
// Export a function to expose caret position
|
// Export a function to expose caret position
|
||||||
export const getCaretPosition = () => {
|
export const getCaretPosition = () => {
|
||||||
const selection_range = editor.state.selection.ranges[0]
|
const selection_range = editor.state.selection.ranges[0]
|
||||||
|
@ -428,6 +495,50 @@
|
||||||
<div tabindex="-1" bind:this={textarea} />
|
<div tabindex="-1" bind:this={textarea} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if aiGenEnabled}
|
||||||
|
<button
|
||||||
|
bind:this={popoverAnchor}
|
||||||
|
class="ai-gen"
|
||||||
|
on:click={() => {
|
||||||
|
popover.show()
|
||||||
|
setTimeout(() => {
|
||||||
|
promptInput.focus()
|
||||||
|
}, 100)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Generate with AI ✨
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<Popover
|
||||||
|
bind:this={popover}
|
||||||
|
minWidth={popoverWidth}
|
||||||
|
anchor={popoverAnchor}
|
||||||
|
align="left-outside"
|
||||||
|
>
|
||||||
|
{#if promptLoading}
|
||||||
|
<div class="prompt-spinner">
|
||||||
|
<Spinner size="20" color="white" />
|
||||||
|
</div>
|
||||||
|
{:else if suggestedCode}
|
||||||
|
<Button on:click={acceptSuggestion}>Accept</Button>
|
||||||
|
<Button on:click={rejectSuggestion}>Reject</Button>
|
||||||
|
{:else}
|
||||||
|
<TextArea
|
||||||
|
bind:this={promptInput}
|
||||||
|
placeholder="Type your prompt then press enter..."
|
||||||
|
on:keypress={event => {
|
||||||
|
if (event.getModifierState("Shift")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
generateJs(promptInput.contents())
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</Popover>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Editor */
|
/* Editor */
|
||||||
.code-editor {
|
.code-editor {
|
||||||
|
@ -633,4 +744,34 @@
|
||||||
text-overflow: ellipsis !important;
|
text-overflow: ellipsis !important;
|
||||||
white-space: nowrap !important;
|
white-space: nowrap !important;
|
||||||
}
|
}
|
||||||
|
.ai-gen {
|
||||||
|
right: 1px;
|
||||||
|
bottom: 1px;
|
||||||
|
position: absolute;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: var(--spacing-s);
|
||||||
|
border-left: 1px solid var(--spectrum-alias-border-color);
|
||||||
|
border-top: 1px solid var(--spectrum-alias-border-color);
|
||||||
|
border-top-left-radius: var(--spectrum-alias-border-radius-regular);
|
||||||
|
color: var(--spectrum-global-color-blue-700);
|
||||||
|
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);
|
||||||
|
height: calc(var(--spectrum-alias-item-height-m) - 2px);
|
||||||
|
}
|
||||||
|
.ai-gen: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);
|
||||||
|
}
|
||||||
|
.prompt-spinner {
|
||||||
|
padding: var(--spacing-m);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
import { Circle } from "svelte-loading-spinners"
|
import { Circle } from "svelte-loading-spinners"
|
||||||
|
|
||||||
export let size = "60"
|
export let size = "60"
|
||||||
|
export let color = "var(--ink)"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="spinner-container">
|
<div class="spinner-container">
|
||||||
<Circle {size} color="var(--ink)" unit="px" />
|
<Circle {size} {color} unit="px" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -37,7 +37,7 @@ 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 = /\$\((["'`])([^"'`]+)\1\)/g
|
||||||
const CAPTURE_HBS_TEMPLATE = /{{[\S\s]*?}}/g
|
const CAPTURE_HBS_TEMPLATE = /{{[\S\s]*?}}/g
|
||||||
|
|
||||||
const UpdateReferenceAction = {
|
const UpdateReferenceAction = {
|
||||||
|
|
|
@ -1,31 +1,36 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { sdk } from "@budibase/shared-core"
|
import { sdk } from "@budibase/shared-core"
|
||||||
import { FieldType } from "@budibase/types"
|
import { FieldType } from "@budibase/types"
|
||||||
import RelationshipField from "./RelationshipField.svelte"
|
import RelationshipField from "./RelationshipField.svelte"
|
||||||
|
|
||||||
export let defaultValue
|
export let defaultValue: string
|
||||||
export let type = FieldType.BB_REFERENCE
|
export let type = FieldType.BB_REFERENCE
|
||||||
|
|
||||||
function updateUserIDs(value) {
|
function updateUserIDs(value: string | string[]) {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return value.map(val => sdk.users.getGlobalUserID(val))
|
return value.map(val => sdk.users.getGlobalUserID(val)!)
|
||||||
} else {
|
} else {
|
||||||
return sdk.users.getGlobalUserID(value)
|
return sdk.users.getGlobalUserID(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateReferences(value) {
|
function updateReferences(value: string) {
|
||||||
if (sdk.users.containsUserID(value)) {
|
if (sdk.users.containsUserID(value)) {
|
||||||
return updateUserIDs(value)
|
return updateUserIDs(value)
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: updatedDefaultValue = updateReferences(defaultValue)
|
||||||
|
|
||||||
|
// This cannot be typed, as svelte does not provide typed inheritance
|
||||||
|
$: allProps = $$props as any
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<RelationshipField
|
<RelationshipField
|
||||||
{...$$props}
|
{...allProps}
|
||||||
{type}
|
{type}
|
||||||
datasourceType={"user"}
|
datasourceType={"user"}
|
||||||
primaryDisplay={"email"}
|
primaryDisplay={"email"}
|
||||||
defaultValue={updateReferences(defaultValue)}
|
defaultValue={updatedDefaultValue}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,43 +1,60 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, onDestroy } from "svelte"
|
import { getContext, onDestroy } from "svelte"
|
||||||
|
import type { Readable } from "svelte/store"
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
import { Icon } from "@budibase/bbui"
|
import { Icon } from "@budibase/bbui"
|
||||||
import { memo } from "@budibase/frontend-core"
|
import { memo } from "@budibase/frontend-core"
|
||||||
import Placeholder from "../Placeholder.svelte"
|
import Placeholder from "../Placeholder.svelte"
|
||||||
import InnerForm from "./InnerForm.svelte"
|
import InnerForm from "./InnerForm.svelte"
|
||||||
import type { FieldApi } from "."
|
import type { FieldSchema, FieldType } from "@budibase/types"
|
||||||
|
import type {
|
||||||
|
FieldApi,
|
||||||
|
FieldState,
|
||||||
|
FieldValidation,
|
||||||
|
FormField,
|
||||||
|
} from "@/types"
|
||||||
|
|
||||||
|
interface FieldInfo {
|
||||||
|
field: string
|
||||||
|
type: FieldType
|
||||||
|
defaultValue: string | undefined
|
||||||
|
disabled: boolean
|
||||||
|
readonly: boolean
|
||||||
|
validation?: FieldValidation
|
||||||
|
formStep: number
|
||||||
|
}
|
||||||
|
|
||||||
export let label: string | undefined = undefined
|
export let label: string | undefined = undefined
|
||||||
export let field: string | undefined = undefined
|
export let field: string | undefined = undefined
|
||||||
export let fieldState: any
|
export let fieldState: FieldState | undefined
|
||||||
export let fieldApi: FieldApi
|
export let fieldApi: FieldApi | undefined
|
||||||
export let fieldSchema: any
|
export let fieldSchema: FieldSchema | undefined
|
||||||
export let defaultValue: string | undefined = undefined
|
export let defaultValue: string | undefined = undefined
|
||||||
export let type: any
|
export let type: FieldType
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let validation: any
|
export let validation: FieldValidation | undefined
|
||||||
export let span = 6
|
export let span = 6
|
||||||
export let helpText: string | undefined = undefined
|
export let helpText: string | undefined = undefined
|
||||||
|
|
||||||
// Get contexts
|
// Get contexts
|
||||||
const formContext: any = getContext("form")
|
const formContext = getContext("form")
|
||||||
const formStepContext: any = getContext("form-step")
|
const formStepContext = getContext("form-step")
|
||||||
const fieldGroupContext: any = getContext("field-group")
|
const fieldGroupContext = getContext("field-group")
|
||||||
const { styleable, builderStore, Provider } = getContext("sdk")
|
const { styleable, builderStore, Provider } = getContext("sdk")
|
||||||
const component: any = getContext("component")
|
const component = getContext("component")
|
||||||
|
|
||||||
// Register field with form
|
// Register field with form
|
||||||
const formApi = formContext?.formApi
|
const formApi = formContext?.formApi
|
||||||
const labelPos = fieldGroupContext?.labelPosition || "above"
|
const labelPos = fieldGroupContext?.labelPosition || "above"
|
||||||
|
|
||||||
let formField: any
|
let formField: Readable<FormField> | undefined
|
||||||
let touched = false
|
let touched = false
|
||||||
let labelNode: any
|
let labelNode: HTMLElement | undefined
|
||||||
|
|
||||||
// Memoize values required to register the field to avoid loops
|
// Memoize values required to register the field to avoid loops
|
||||||
const formStep = formStepContext || writable(1)
|
const formStep = formStepContext || writable(1)
|
||||||
const fieldInfo = memo({
|
const fieldInfo = memo<FieldInfo>({
|
||||||
field: field || $component.name,
|
field: field || $component.name,
|
||||||
type,
|
type,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
|
@ -66,16 +83,22 @@
|
||||||
$: $component.editing && labelNode?.focus()
|
$: $component.editing && labelNode?.focus()
|
||||||
|
|
||||||
// Update form properties in parent component on every store change
|
// Update form properties in parent component on every store change
|
||||||
$: unsubscribe = formField?.subscribe((value: any) => {
|
$: unsubscribe = formField?.subscribe(
|
||||||
fieldState = value?.fieldState
|
(value?: {
|
||||||
fieldApi = value?.fieldApi
|
fieldState: FieldState
|
||||||
fieldSchema = value?.fieldSchema
|
fieldApi: FieldApi
|
||||||
})
|
fieldSchema: FieldSchema
|
||||||
|
}) => {
|
||||||
|
fieldState = value?.fieldState
|
||||||
|
fieldApi = value?.fieldApi
|
||||||
|
fieldSchema = value?.fieldSchema
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// Determine label class from position
|
// Determine label class from position
|
||||||
$: labelClass = labelPos === "above" ? "" : `spectrum-FieldLabel--${labelPos}`
|
$: labelClass = labelPos === "above" ? "" : `spectrum-FieldLabel--${labelPos}`
|
||||||
|
|
||||||
const registerField = (info: any) => {
|
const registerField = (info: FieldInfo) => {
|
||||||
formField = formApi?.registerField(
|
formField = formApi?.registerField(
|
||||||
info.field,
|
info.field,
|
||||||
info.type,
|
info.type,
|
||||||
|
@ -87,10 +110,10 @@
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateLabel = (e: any) => {
|
const updateLabel = (e: Event) => {
|
||||||
if (touched) {
|
if (touched) {
|
||||||
// @ts-expect-error and TODO updateProp isn't recognised - need builder TS conversion
|
const label = e.target as HTMLLabelElement
|
||||||
builderStore.actions.updateProp("label", e.target.textContent)
|
builderStore.actions.updateProp("label", label.textContent)
|
||||||
}
|
}
|
||||||
touched = false
|
touched = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,17 +9,19 @@
|
||||||
RelationshipFieldMetadata,
|
RelationshipFieldMetadata,
|
||||||
Row,
|
Row,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import type { FieldApi, FieldState } from "."
|
import type { FieldApi, FieldState, FieldValidation } from "@/types"
|
||||||
|
|
||||||
|
type ValueType = string | string[]
|
||||||
|
|
||||||
export let field: string | undefined = undefined
|
export let field: string | undefined = undefined
|
||||||
export let label: string | undefined = undefined
|
export let label: string | undefined = undefined
|
||||||
export let placeholder: string | undefined = undefined
|
export let placeholder: string | undefined = undefined
|
||||||
export let disabled: boolean = false
|
export let disabled: boolean = false
|
||||||
export let readonly: boolean = false
|
export let readonly: boolean = false
|
||||||
export let validation: any
|
export let validation: FieldValidation | undefined = undefined
|
||||||
export let autocomplete: boolean = true
|
export let autocomplete: boolean = true
|
||||||
export let defaultValue: string | string[] | undefined = undefined
|
export let defaultValue: ValueType | undefined = undefined
|
||||||
export let onChange: any
|
export let onChange: (_props: { value: ValueType }) => void
|
||||||
export let filter: SearchFilter[]
|
export let filter: SearchFilter[]
|
||||||
export let datasourceType: "table" | "user" = "table"
|
export let datasourceType: "table" | "user" = "table"
|
||||||
export let primaryDisplay: string | undefined = undefined
|
export let primaryDisplay: string | undefined = undefined
|
||||||
|
@ -88,14 +90,14 @@
|
||||||
// Ensure backwards compatibility
|
// Ensure backwards compatibility
|
||||||
$: enrichedDefaultValue = enrichDefaultValue(defaultValue)
|
$: enrichedDefaultValue = enrichDefaultValue(defaultValue)
|
||||||
|
|
||||||
|
$: emptyValue = multiselect ? [] : undefined
|
||||||
// We need to cast value to pass it down, as those components aren't typed
|
// We need to cast value to pass it down, as those components aren't typed
|
||||||
$: emptyValue = multiselect ? [] : null
|
$: displayValue = (missingIDs.length ? emptyValue : selectedValue) as any
|
||||||
$: displayValue = missingIDs.length ? emptyValue : (selectedValue as any)
|
|
||||||
|
|
||||||
// Ensures that we flatten any objects so that only the IDs of the selected
|
// Ensures that we flatten any objects so that only the IDs of the selected
|
||||||
// rows are passed down. Not sure how this can be an object to begin with?
|
// rows are passed down. Not sure how this can be an object to begin with?
|
||||||
const parseSelectedValue = (
|
const parseSelectedValue = (
|
||||||
value: any,
|
value: ValueType | undefined,
|
||||||
multiselect: boolean
|
multiselect: boolean
|
||||||
): undefined | string | string[] => {
|
): undefined | string | string[] => {
|
||||||
return multiselect ? flatten(value) : flatten(value)[0]
|
return multiselect ? flatten(value) : flatten(value)[0]
|
||||||
|
@ -140,7 +142,7 @@
|
||||||
|
|
||||||
// Builds a map of all available options, in a consistent structure
|
// Builds a map of all available options, in a consistent structure
|
||||||
const processOptions = (
|
const processOptions = (
|
||||||
realValue: any | any[],
|
realValue: ValueType | undefined,
|
||||||
rows: Row[],
|
rows: Row[],
|
||||||
primaryDisplay?: string
|
primaryDisplay?: string
|
||||||
) => {
|
) => {
|
||||||
|
@ -171,7 +173,7 @@
|
||||||
|
|
||||||
// Parses a row-like structure into a properly shaped option
|
// Parses a row-like structure into a properly shaped option
|
||||||
const parseOption = (
|
const parseOption = (
|
||||||
option: any | BasicRelatedRow | Row,
|
option: string | BasicRelatedRow | Row,
|
||||||
primaryDisplay?: string
|
primaryDisplay?: string
|
||||||
): BasicRelatedRow | null => {
|
): BasicRelatedRow | null => {
|
||||||
if (!option || typeof option !== "object" || !option?._id) {
|
if (!option || typeof option !== "object" || !option?._id) {
|
||||||
|
|
|
@ -19,15 +19,3 @@ export { default as codescanner } from "./CodeScannerField.svelte"
|
||||||
export { default as signaturesinglefield } from "./SignatureField.svelte"
|
export { default as signaturesinglefield } from "./SignatureField.svelte"
|
||||||
export { default as bbreferencefield } from "./BBReferenceField.svelte"
|
export { default as bbreferencefield } from "./BBReferenceField.svelte"
|
||||||
export { default as bbreferencesinglefield } from "./BBReferenceSingleField.svelte"
|
export { default as bbreferencesinglefield } from "./BBReferenceSingleField.svelte"
|
||||||
|
|
||||||
export interface FieldApi {
|
|
||||||
setValue(value: any): boolean
|
|
||||||
deregister(): void
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FieldState<T> {
|
|
||||||
value: T
|
|
||||||
fieldId: string
|
|
||||||
disabled: boolean
|
|
||||||
readonly: boolean
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import { Component, Context, SDK } from "."
|
import { Writable } from "svelte"
|
||||||
|
import { Component, FieldGroupContext, FormContext, SDK } from "@/types"
|
||||||
|
import { Readable } from "svelte/store"
|
||||||
|
|
||||||
declare module "svelte" {
|
declare module "svelte" {
|
||||||
export function getContext(key: "sdk"): SDK
|
export function getContext(key: "sdk"): SDK
|
||||||
export function getContext(key: "component"): Component
|
export function getContext(key: "component"): Component
|
||||||
export function getContext(key: "context"): Context
|
export function getContext(key: "context"): Readable<Record<string, any>>
|
||||||
|
export function getContext(key: "form"): FormContext | undefined
|
||||||
|
export function getContext(key: "form-step"): Writable<number> | undefined
|
||||||
|
export function getContext(key: "field-group"): FieldGroupContext | undefined
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,6 @@ import {
|
||||||
} from "@/stores"
|
} from "@/stores"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { initWebsocket } from "@/websocket"
|
import { initWebsocket } from "@/websocket"
|
||||||
import { APIClient } from "@budibase/frontend-core"
|
|
||||||
import type { ActionTypes } from "@/constants"
|
|
||||||
import { Readable } from "svelte/store"
|
import { Readable } from "svelte/store"
|
||||||
import {
|
import {
|
||||||
Screen,
|
Screen,
|
||||||
|
@ -39,6 +37,7 @@ window.svelte = svelte
|
||||||
// Initialise spectrum icons
|
// Initialise spectrum icons
|
||||||
// eslint-disable-next-line local-rules/no-budibase-imports
|
// eslint-disable-next-line local-rules/no-budibase-imports
|
||||||
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-vite.js"
|
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-vite.js"
|
||||||
|
|
||||||
loadSpectrumIcons()
|
loadSpectrumIcons()
|
||||||
|
|
||||||
// Extend global window scope
|
// Extend global window scope
|
||||||
|
@ -73,32 +72,6 @@ declare global {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SDK {
|
|
||||||
API: APIClient
|
|
||||||
styleable: any
|
|
||||||
Provider: any
|
|
||||||
ActionTypes: typeof ActionTypes
|
|
||||||
fetchDatasourceSchema: any
|
|
||||||
generateGoldenSample: any
|
|
||||||
builderStore: Readable<{
|
|
||||||
inBuilder: boolean
|
|
||||||
}> & {
|
|
||||||
actions: {
|
|
||||||
highlightSetting: (key: string) => void
|
|
||||||
addParentComponent: (
|
|
||||||
componentId: string,
|
|
||||||
fullAncestorType: string
|
|
||||||
) => void
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Component = Readable<{
|
|
||||||
id: string
|
|
||||||
styles: any
|
|
||||||
errorState: boolean
|
|
||||||
}>
|
|
||||||
|
|
||||||
export type Context = Readable<Record<string, any>>
|
export type Context = Readable<Record<string, any>>
|
||||||
|
|
||||||
let app: ClientApp
|
let app: ClientApp
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Readable } from "svelte/store"
|
||||||
|
|
||||||
|
export type Component = Readable<{
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
styles: any
|
||||||
|
editing: boolean
|
||||||
|
errorState: boolean
|
||||||
|
}>
|
|
@ -0,0 +1,3 @@
|
||||||
|
export interface FieldGroupContext {
|
||||||
|
labelPosition: string
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { Readable } from "svelte/store"
|
||||||
|
import { FieldSchema, FieldType } from "@budibase/types"
|
||||||
|
|
||||||
|
export interface FormContext {
|
||||||
|
formApi?: {
|
||||||
|
registerField: (
|
||||||
|
field: string,
|
||||||
|
type: FieldType,
|
||||||
|
defaultValue: string | undefined,
|
||||||
|
disabled: boolean,
|
||||||
|
readonly: boolean,
|
||||||
|
validation: FieldValidation | undefined,
|
||||||
|
formStep: number
|
||||||
|
) => Readable<FormField>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FieldValidation = () => string | undefined
|
||||||
|
|
||||||
|
export interface FormField {
|
||||||
|
fieldState: FieldState
|
||||||
|
fieldApi: FieldApi
|
||||||
|
fieldSchema: FieldSchema
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FieldApi {
|
||||||
|
setValue(value: any): boolean
|
||||||
|
deregister(): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FieldState<T = any> {
|
||||||
|
value: T
|
||||||
|
fieldId: string
|
||||||
|
disabled: boolean
|
||||||
|
readonly: boolean
|
||||||
|
error?: string
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
export * from "./components"
|
||||||
|
export * from "./fields"
|
||||||
|
export * from "./forms"
|
||||||
|
export * from "./sdk"
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { ActionTypes } from "@/constants"
|
||||||
|
import { APIClient } from "@budibase/frontend-core"
|
||||||
|
import { Readable } from "svelte/store"
|
||||||
|
|
||||||
|
export interface SDK {
|
||||||
|
API: APIClient
|
||||||
|
styleable: any
|
||||||
|
Provider: any
|
||||||
|
ActionTypes: typeof ActionTypes
|
||||||
|
fetchDatasourceSchema: any
|
||||||
|
generateGoldenSample: any
|
||||||
|
builderStore: Readable<{
|
||||||
|
inBuilder: boolean
|
||||||
|
}> & {
|
||||||
|
actions: {
|
||||||
|
highlightSetting: (key: string) => void
|
||||||
|
addParentComponent: (
|
||||||
|
componentId: string,
|
||||||
|
fullAncestorType: string
|
||||||
|
) => void
|
||||||
|
updateProp: (key: string, value: any) => void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,9 @@
|
||||||
|
import { GenerateJsRequest, GenerateJsResponse } from "@budibase/types"
|
||||||
import { BaseAPIClient } from "./types"
|
import { BaseAPIClient } from "./types"
|
||||||
|
|
||||||
export interface AIEndpoints {
|
export interface AIEndpoints {
|
||||||
generateCronExpression: (prompt: string) => Promise<{ message: string }>
|
generateCronExpression: (prompt: string) => Promise<{ message: string }>
|
||||||
|
generateJs: (req: GenerateJsRequest) => Promise<GenerateJsResponse>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const buildAIEndpoints = (API: BaseAPIClient): AIEndpoints => ({
|
export const buildAIEndpoints = (API: BaseAPIClient): AIEndpoints => ({
|
||||||
|
@ -14,4 +16,11 @@ export const buildAIEndpoints = (API: BaseAPIClient): AIEndpoints => ({
|
||||||
body: { prompt },
|
body: { prompt },
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
generateJs: async req => {
|
||||||
|
return await API.post({
|
||||||
|
url: "/api/ai/js",
|
||||||
|
body: req,
|
||||||
|
})
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 2dd06c2fcb3cf10d5f16f5d8fe6cd344c8e905a5
|
Subproject commit bb1ed6fa96ebed30e30659e47b0712567601f3c0
|
|
@ -0,0 +1,28 @@
|
||||||
|
import {
|
||||||
|
CreateOAuth2ConfigRequest,
|
||||||
|
Ctx,
|
||||||
|
FetchOAuth2ConfigsResponse,
|
||||||
|
OAuth2Config,
|
||||||
|
RequiredKeys,
|
||||||
|
} from "@budibase/types"
|
||||||
|
import sdk from "../../sdk"
|
||||||
|
|
||||||
|
export async function fetch(ctx: Ctx<void, FetchOAuth2ConfigsResponse>) {
|
||||||
|
const configs = await sdk.oauth2.fetch()
|
||||||
|
|
||||||
|
const response: FetchOAuth2ConfigsResponse = {
|
||||||
|
configs: (configs || []).map(c => ({
|
||||||
|
name: c.name,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
ctx.body = response
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function create(ctx: Ctx<CreateOAuth2ConfigRequest, void>) {
|
||||||
|
const newConfig: RequiredKeys<OAuth2Config> = {
|
||||||
|
name: ctx.request.body.name,
|
||||||
|
}
|
||||||
|
|
||||||
|
await sdk.oauth2.create(newConfig)
|
||||||
|
ctx.status = 201
|
||||||
|
}
|
|
@ -29,11 +29,12 @@ import debugRoutes from "./debug"
|
||||||
import Router from "@koa/router"
|
import Router from "@koa/router"
|
||||||
import { api as pro } from "@budibase/pro"
|
import { api as pro } from "@budibase/pro"
|
||||||
import rowActionRoutes from "./rowAction"
|
import rowActionRoutes from "./rowAction"
|
||||||
|
import oauth2Routes from "./oauth2"
|
||||||
|
|
||||||
export { default as staticRoutes } from "./static"
|
export { default as staticRoutes } from "./static"
|
||||||
export { default as publicRoutes } from "./public"
|
export { default as publicRoutes } from "./public"
|
||||||
|
|
||||||
const aiRoutes = pro.ai
|
const proAiRoutes = pro.ai
|
||||||
const appBackupRoutes = pro.appBackups
|
const appBackupRoutes = pro.appBackups
|
||||||
const environmentVariableRoutes = pro.environmentVariables
|
const environmentVariableRoutes = pro.environmentVariables
|
||||||
|
|
||||||
|
@ -68,7 +69,8 @@ export const mainRoutes: Router[] = [
|
||||||
debugRoutes,
|
debugRoutes,
|
||||||
environmentVariableRoutes,
|
environmentVariableRoutes,
|
||||||
rowActionRoutes,
|
rowActionRoutes,
|
||||||
aiRoutes,
|
proAiRoutes,
|
||||||
|
oauth2Routes,
|
||||||
// these need to be handled last as they still use /api/:tableId
|
// these need to be handled last as they still use /api/:tableId
|
||||||
// this could be breaking as koa may recognise other routes as this
|
// this could be breaking as koa may recognise other routes as this
|
||||||
tableRoutes,
|
tableRoutes,
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import Router from "@koa/router"
|
||||||
|
import { PermissionType } from "@budibase/types"
|
||||||
|
import authorized from "../../middleware/authorized"
|
||||||
|
|
||||||
|
import * as controller from "../controllers/oauth2"
|
||||||
|
|
||||||
|
const router: Router = new Router()
|
||||||
|
|
||||||
|
router.get("/api/oauth2", authorized(PermissionType.BUILDER), controller.fetch)
|
||||||
|
router.post(
|
||||||
|
"/api/oauth2",
|
||||||
|
authorized(PermissionType.BUILDER),
|
||||||
|
controller.create
|
||||||
|
)
|
||||||
|
|
||||||
|
export default router
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { CreateOAuth2ConfigRequest } from "@budibase/types"
|
||||||
|
import * as setup from "./utilities"
|
||||||
|
import { generator } from "@budibase/backend-core/tests"
|
||||||
|
|
||||||
|
describe("/oauth2", () => {
|
||||||
|
let config = setup.getConfig()
|
||||||
|
|
||||||
|
function makeOAuth2Config(): CreateOAuth2ConfigRequest {
|
||||||
|
return {
|
||||||
|
name: generator.guid(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeAll(async () => await config.init())
|
||||||
|
|
||||||
|
beforeEach(async () => await config.newTenant())
|
||||||
|
|
||||||
|
describe("fetch", () => {
|
||||||
|
it("returns empty when no oauth are created", async () => {
|
||||||
|
const response = await config.api.oauth2.fetch()
|
||||||
|
expect(response).toEqual({
|
||||||
|
configs: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("create", () => {
|
||||||
|
it("can create a new configuration", async () => {
|
||||||
|
const oauth2Config = makeOAuth2Config()
|
||||||
|
await config.api.oauth2.create(oauth2Config, { status: 201 })
|
||||||
|
|
||||||
|
const response = await config.api.oauth2.fetch()
|
||||||
|
expect(response).toEqual({
|
||||||
|
configs: [
|
||||||
|
{
|
||||||
|
name: oauth2Config.name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can create multiple configurations", async () => {
|
||||||
|
const oauth2Config = makeOAuth2Config()
|
||||||
|
const oauth2Config2 = makeOAuth2Config()
|
||||||
|
await config.api.oauth2.create(oauth2Config, { status: 201 })
|
||||||
|
await config.api.oauth2.create(oauth2Config2, { status: 201 })
|
||||||
|
|
||||||
|
const response = await config.api.oauth2.fetch()
|
||||||
|
expect(response.configs).toEqual([
|
||||||
|
{
|
||||||
|
name: oauth2Config.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: oauth2Config2.name,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("cannot create configurations with already existing names", async () => {
|
||||||
|
const oauth2Config = makeOAuth2Config()
|
||||||
|
const oauth2Config2 = { ...makeOAuth2Config(), name: oauth2Config.name }
|
||||||
|
await config.api.oauth2.create(oauth2Config, { status: 201 })
|
||||||
|
await config.api.oauth2.create(oauth2Config2, {
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
message: "Name already used",
|
||||||
|
status: 400,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await config.api.oauth2.fetch()
|
||||||
|
expect(response.configs).toEqual([
|
||||||
|
{
|
||||||
|
name: oauth2Config.name,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { context, HTTPError } from "@budibase/backend-core"
|
||||||
|
import { DocumentType, OAuth2Config, OAuth2Configs } from "@budibase/types"
|
||||||
|
|
||||||
|
export async function fetch(): Promise<OAuth2Config[]> {
|
||||||
|
const db = context.getAppDB()
|
||||||
|
const result = await db.tryGet<OAuth2Configs>(DocumentType.OAUTH2_CONFIG)
|
||||||
|
if (!result) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return Object.values(result.configs)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function create(config: OAuth2Config) {
|
||||||
|
const db = context.getAppDB()
|
||||||
|
const doc: OAuth2Configs = (await db.tryGet<OAuth2Configs>(
|
||||||
|
DocumentType.OAUTH2_CONFIG
|
||||||
|
)) ?? {
|
||||||
|
_id: DocumentType.OAUTH2_CONFIG,
|
||||||
|
configs: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doc.configs[config.name]) {
|
||||||
|
throw new HTTPError("Name already used", 400)
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.configs[config.name] = config
|
||||||
|
await db.put(doc)
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ import * as permissions from "./app/permissions"
|
||||||
import * as rowActions from "./app/rowActions"
|
import * as rowActions from "./app/rowActions"
|
||||||
import * as screens from "./app/screens"
|
import * as screens from "./app/screens"
|
||||||
import * as common from "./app/common"
|
import * as common from "./app/common"
|
||||||
|
import * as oauth2 from "./app/oauth2"
|
||||||
|
|
||||||
const sdk = {
|
const sdk = {
|
||||||
backups,
|
backups,
|
||||||
|
@ -30,6 +31,7 @@ const sdk = {
|
||||||
links,
|
links,
|
||||||
rowActions,
|
rowActions,
|
||||||
common,
|
common,
|
||||||
|
oauth2,
|
||||||
}
|
}
|
||||||
|
|
||||||
// default export for TS
|
// default export for TS
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { WebhookAPI } from "./webhook"
|
||||||
import { EnvironmentAPI } from "./environment"
|
import { EnvironmentAPI } from "./environment"
|
||||||
import { UserPublicAPI } from "./public/user"
|
import { UserPublicAPI } from "./public/user"
|
||||||
import { MiscAPI } from "./misc"
|
import { MiscAPI } from "./misc"
|
||||||
|
import { OAuth2API } from "./oauth2"
|
||||||
|
|
||||||
export default class API {
|
export default class API {
|
||||||
application: ApplicationAPI
|
application: ApplicationAPI
|
||||||
|
@ -30,6 +31,7 @@ export default class API {
|
||||||
environment: EnvironmentAPI
|
environment: EnvironmentAPI
|
||||||
legacyView: LegacyViewAPI
|
legacyView: LegacyViewAPI
|
||||||
misc: MiscAPI
|
misc: MiscAPI
|
||||||
|
oauth2: OAuth2API
|
||||||
permission: PermissionAPI
|
permission: PermissionAPI
|
||||||
plugin: PluginAPI
|
plugin: PluginAPI
|
||||||
query: QueryAPI
|
query: QueryAPI
|
||||||
|
@ -56,6 +58,7 @@ export default class API {
|
||||||
this.environment = new EnvironmentAPI(config)
|
this.environment = new EnvironmentAPI(config)
|
||||||
this.legacyView = new LegacyViewAPI(config)
|
this.legacyView = new LegacyViewAPI(config)
|
||||||
this.misc = new MiscAPI(config)
|
this.misc = new MiscAPI(config)
|
||||||
|
this.oauth2 = new OAuth2API(config)
|
||||||
this.permission = new PermissionAPI(config)
|
this.permission = new PermissionAPI(config)
|
||||||
this.plugin = new PluginAPI(config)
|
this.plugin = new PluginAPI(config)
|
||||||
this.query = new QueryAPI(config)
|
this.query = new QueryAPI(config)
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import {
|
||||||
|
CreateOAuth2ConfigRequest,
|
||||||
|
FetchOAuth2ConfigsResponse,
|
||||||
|
} from "@budibase/types"
|
||||||
|
import { Expectations, TestAPI } from "./base"
|
||||||
|
|
||||||
|
export class OAuth2API extends TestAPI {
|
||||||
|
fetch = async (expectations?: Expectations) => {
|
||||||
|
return await this._get<FetchOAuth2ConfigsResponse>("/api/oauth2", {
|
||||||
|
expectations,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
create = async (
|
||||||
|
body: CreateOAuth2ConfigRequest,
|
||||||
|
expectations?: Expectations
|
||||||
|
) => {
|
||||||
|
return await this._post<CreateOAuth2ConfigRequest>("/api/oauth2", {
|
||||||
|
body,
|
||||||
|
expectations,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface GenerateJsRequest {
|
||||||
|
prompt: string
|
||||||
|
}
|
||||||
|
export interface GenerateJsResponse {
|
||||||
|
code: string
|
||||||
|
}
|
|
@ -1,21 +1,22 @@
|
||||||
export * from "./backup"
|
|
||||||
export * from "./datasource"
|
|
||||||
export * from "./view"
|
|
||||||
export * from "./rows"
|
|
||||||
export * from "./table"
|
|
||||||
export * from "./permission"
|
|
||||||
export * from "./attachment"
|
|
||||||
export * from "./user"
|
|
||||||
export * from "./rowAction"
|
|
||||||
export * from "./automation"
|
|
||||||
export * from "./component"
|
|
||||||
export * from "./integration"
|
|
||||||
export * from "./metadata"
|
|
||||||
export * from "./query"
|
|
||||||
export * from "./screen"
|
|
||||||
export * from "./application"
|
export * from "./application"
|
||||||
export * from "./layout"
|
export * from "./attachment"
|
||||||
|
export * from "./automation"
|
||||||
|
export * from "./backup"
|
||||||
|
export * from "./component"
|
||||||
|
export * from "./datasource"
|
||||||
export * from "./deployment"
|
export * from "./deployment"
|
||||||
|
export * from "./integration"
|
||||||
|
export * from "./layout"
|
||||||
|
export * from "./metadata"
|
||||||
|
export * from "./oauth2"
|
||||||
|
export * from "./permission"
|
||||||
|
export * from "./query"
|
||||||
export * from "./role"
|
export * from "./role"
|
||||||
export * from "./webhook"
|
export * from "./rowAction"
|
||||||
|
export * from "./rows"
|
||||||
|
export * from "./screen"
|
||||||
export * from "./static"
|
export * from "./static"
|
||||||
|
export * from "./table"
|
||||||
|
export * from "./user"
|
||||||
|
export * from "./view"
|
||||||
|
export * from "./webhook"
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
interface OAuth2Config {
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FetchOAuth2ConfigsResponse {
|
||||||
|
configs: OAuth2Config[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateOAuth2ConfigRequest extends OAuth2Config {}
|
|
@ -1,3 +1,4 @@
|
||||||
|
export * from "./ai"
|
||||||
export * from "./analytics"
|
export * from "./analytics"
|
||||||
export * from "./auth"
|
export * from "./auth"
|
||||||
export * from "./user"
|
export * from "./user"
|
||||||
|
|
|
@ -1,22 +1,23 @@
|
||||||
|
export * from "../document"
|
||||||
export * from "./app"
|
export * from "./app"
|
||||||
export * from "./automation"
|
export * from "./automation"
|
||||||
|
export * from "./backup"
|
||||||
|
export * from "./component"
|
||||||
export * from "./datasource"
|
export * from "./datasource"
|
||||||
|
export * from "./deployment"
|
||||||
export * from "./layout"
|
export * from "./layout"
|
||||||
|
export * from "./links"
|
||||||
|
export * from "./metadata"
|
||||||
|
export * from "./oauth2"
|
||||||
export * from "./query"
|
export * from "./query"
|
||||||
export * from "./role"
|
export * from "./role"
|
||||||
export * from "./table"
|
|
||||||
export * from "./screen"
|
|
||||||
export * from "./view"
|
|
||||||
export * from "../document"
|
|
||||||
export * from "./row"
|
export * from "./row"
|
||||||
export * from "./user"
|
|
||||||
export * from "./backup"
|
|
||||||
export * from "./webhook"
|
|
||||||
export * from "./links"
|
|
||||||
export * from "./component"
|
|
||||||
export * from "./sqlite"
|
|
||||||
export * from "./snippet"
|
|
||||||
export * from "./rowAction"
|
export * from "./rowAction"
|
||||||
|
export * from "./screen"
|
||||||
|
export * from "./snippet"
|
||||||
|
export * from "./sqlite"
|
||||||
|
export * from "./table"
|
||||||
export * from "./theme"
|
export * from "./theme"
|
||||||
export * from "./deployment"
|
export * from "./user"
|
||||||
export * from "./metadata"
|
export * from "./view"
|
||||||
|
export * from "./webhook"
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Document } from "../document"
|
||||||
|
|
||||||
|
export interface OAuth2Config {
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OAuth2Configs extends Document {
|
||||||
|
configs: Record<string, OAuth2Config>
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ export enum DocumentType {
|
||||||
APP_MIGRATION_METADATA = "_design/migrations",
|
APP_MIGRATION_METADATA = "_design/migrations",
|
||||||
SCIM_LOG = "scimlog",
|
SCIM_LOG = "scimlog",
|
||||||
ROW_ACTIONS = "ra",
|
ROW_ACTIONS = "ra",
|
||||||
|
OAUTH2_CONFIG = "oauth2",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Because DocumentTypes can overlap, we need to make sure that we search
|
// Because DocumentTypes can overlap, we need to make sure that we search
|
||||||
|
|
|
@ -3,10 +3,13 @@ export enum FeatureFlag {
|
||||||
|
|
||||||
// Account-portal
|
// Account-portal
|
||||||
DIRECT_LOGIN_TO_ACCOUNT_PORTAL = "DIRECT_LOGIN_TO_ACCOUNT_PORTAL",
|
DIRECT_LOGIN_TO_ACCOUNT_PORTAL = "DIRECT_LOGIN_TO_ACCOUNT_PORTAL",
|
||||||
|
|
||||||
|
AI_JS_GENERATION = "AI_JS_GENERATION",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FeatureFlagDefaults = {
|
export const FeatureFlagDefaults: Record<FeatureFlag, boolean> = {
|
||||||
[FeatureFlag.USE_ZOD_VALIDATOR]: false,
|
[FeatureFlag.USE_ZOD_VALIDATOR]: false,
|
||||||
|
[FeatureFlag.AI_JS_GENERATION]: false,
|
||||||
|
|
||||||
// Account-portal
|
// Account-portal
|
||||||
[FeatureFlag.DIRECT_LOGIN_TO_ACCOUNT_PORTAL]: false,
|
[FeatureFlag.DIRECT_LOGIN_TO_ACCOUNT_PORTAL]: false,
|
||||||
|
|
Loading…
Reference in New Issue