Merge remote-tracking branch 'origin/master' into feature/automation-sidebar
This commit is contained in:
commit
3610eaf158
|
@ -19,7 +19,7 @@
|
|||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let focus = false
|
||||
let isFocused = false
|
||||
let textarea: HTMLTextAreaElement
|
||||
let scrollable = false
|
||||
|
||||
|
@ -27,8 +27,16 @@
|
|||
$: minHeightString = getStyleString("min-height", minHeight)
|
||||
$: dispatch("scrollable", scrollable)
|
||||
|
||||
export function focus() {
|
||||
textarea.focus()
|
||||
}
|
||||
|
||||
export function contents() {
|
||||
return textarea.value
|
||||
}
|
||||
|
||||
const onBlur = () => {
|
||||
focus = false
|
||||
isFocused = false
|
||||
updateValue()
|
||||
}
|
||||
|
||||
|
@ -65,7 +73,7 @@
|
|||
style={`${heightString}${minHeightString}`}
|
||||
class="spectrum-Textfield spectrum-Textfield--multiline"
|
||||
class:is-disabled={disabled}
|
||||
class:is-focused={focus}
|
||||
class:is-focused={isFocused}
|
||||
>
|
||||
<!-- prettier-ignore -->
|
||||
<textarea
|
||||
|
@ -77,7 +85,7 @@
|
|||
{readonly}
|
||||
{id}
|
||||
on:input={onChange}
|
||||
on:focus={() => (focus = true)}
|
||||
on:focus={() => (isFocused = true)}
|
||||
on:blur={onBlur}
|
||||
on:blur
|
||||
on:keypress
|
||||
|
|
|
@ -1059,6 +1059,7 @@
|
|||
inputData[key] = e.detail
|
||||
}}
|
||||
completions={stepCompletions}
|
||||
{bindings}
|
||||
mode={codeMode}
|
||||
autocompleteEnabled={codeMode !== EditorModes.JS}
|
||||
bind:getCaretPosition
|
||||
|
|
|
@ -152,6 +152,7 @@
|
|||
<div class="field-wrap json-field">
|
||||
<CodeEditor
|
||||
value={readableValue}
|
||||
{bindings}
|
||||
on:blur={e => {
|
||||
onChange({
|
||||
row: {
|
||||
|
|
|
@ -59,7 +59,11 @@
|
|||
import { javascript } from "@codemirror/lang-javascript"
|
||||
import { EditorModes } from "./"
|
||||
import { themeStore } from "@/stores/portal"
|
||||
import { FeatureFlag, type EditorMode } from "@budibase/types"
|
||||
import {
|
||||
type EnrichedBinding,
|
||||
FeatureFlag,
|
||||
type EditorMode,
|
||||
} from "@budibase/types"
|
||||
import { tooltips } from "@codemirror/view"
|
||||
import type { BindingCompletion, CodeValidator } from "@/types"
|
||||
import { validateHbsTemplate } from "./validator/hbs"
|
||||
|
@ -80,6 +84,7 @@
|
|||
export let readonly = false
|
||||
export let readonlyLineNumbers = false
|
||||
export let dropdown = DropdownPosition.Relative
|
||||
export let bindings: EnrichedBinding[] = []
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
|
@ -100,7 +105,8 @@
|
|||
let promptInput: TextArea
|
||||
$: aiGenEnabled =
|
||||
featureFlag.isEnabled(FeatureFlag.AI_JS_GENERATION) &&
|
||||
mode.name === "javascript"
|
||||
mode.name === "javascript" &&
|
||||
!readonly
|
||||
|
||||
$: {
|
||||
if (autofocus && isEditorInitialised) {
|
||||
|
@ -165,15 +171,24 @@
|
|||
popoverWidth = 30
|
||||
let code = ""
|
||||
try {
|
||||
const resp = await API.generateJs({ prompt })
|
||||
const resp = await API.generateJs({ prompt, bindings })
|
||||
code = resp.code
|
||||
|
||||
if (code === "") {
|
||||
throw new Error(
|
||||
"we didn't understand your prompt, please phrase your request in another way"
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
notifications.error("Unable to generate code, please try again later.")
|
||||
if (e instanceof Error) {
|
||||
notifications.error(`Unable to generate code: ${e.message}`)
|
||||
} else {
|
||||
notifications.error("Unable to generate code, please try again later.")
|
||||
}
|
||||
code = previousContents
|
||||
popoverWidth = 300
|
||||
promptLoading = false
|
||||
popover.hide()
|
||||
resetPopover()
|
||||
return
|
||||
}
|
||||
value = code
|
||||
|
@ -189,6 +204,8 @@
|
|||
suggestedCode = null
|
||||
previousContents = null
|
||||
resetPopover()
|
||||
dispatch("change", editor.state.doc.toString())
|
||||
dispatch("blur", editor.state.doc.toString())
|
||||
}
|
||||
|
||||
const rejectSuggestion = () => {
|
||||
|
@ -513,13 +530,18 @@
|
|||
bind:this={popover}
|
||||
minWidth={popoverWidth}
|
||||
anchor={popoverAnchor}
|
||||
on:close={() => {
|
||||
if (suggestedCode) {
|
||||
acceptSuggestion()
|
||||
}
|
||||
}}
|
||||
align="left-outside"
|
||||
>
|
||||
{#if promptLoading}
|
||||
<div class="prompt-spinner">
|
||||
<Spinner size="20" color="white" />
|
||||
</div>
|
||||
{:else if suggestedCode}
|
||||
{:else if suggestedCode !== null}
|
||||
<Button on:click={acceptSuggestion}>Accept</Button>
|
||||
<Button on:click={rejectSuggestion}>Reject</Button>
|
||||
{:else}
|
||||
|
|
|
@ -359,6 +359,7 @@
|
|||
bind:getCaretPosition
|
||||
bind:insertAtPos
|
||||
{completions}
|
||||
{bindings}
|
||||
{validations}
|
||||
autofocus={autofocusEditor}
|
||||
placeholder={placeholder ||
|
||||
|
@ -372,6 +373,7 @@
|
|||
value={jsValue ? decodeJSBinding(jsValue) : ""}
|
||||
on:change={onChangeJSValue}
|
||||
{completions}
|
||||
{bindings}
|
||||
{validations}
|
||||
mode={EditorModes.JS}
|
||||
bind:getCaretPosition
|
||||
|
|
|
@ -147,6 +147,7 @@
|
|||
on:change={onChangeJSValue}
|
||||
on:blur
|
||||
completions={jsCompletions}
|
||||
{bindings}
|
||||
mode={EditorModes.JS}
|
||||
bind:getCaretPosition
|
||||
bind:insertAtPos
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
export let type = "label"
|
||||
export let size = "M"
|
||||
|
||||
export const disableEditingState = () => setEditing(false)
|
||||
|
||||
let editing = false
|
||||
|
||||
function setEditing(state) {
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
import { lowercase } from "@/helpers"
|
||||
import DrawerBindableInput from "@/components/common/bindings/DrawerBindableInput.svelte"
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let defaults = undefined
|
||||
export let object = defaults || {}
|
||||
|
@ -46,10 +46,17 @@
|
|||
}))
|
||||
let fieldActivity = buildFieldActivity(activity)
|
||||
|
||||
$: object = fields.reduce(
|
||||
(acc, next) => ({ ...acc, [next.name]: next.value }),
|
||||
{}
|
||||
)
|
||||
$: fullObject = fields.reduce((acc, next) => {
|
||||
acc[next.name] = next.value
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
$: object = Object.entries(fullObject).reduce((acc, [key, next]) => {
|
||||
if (key) {
|
||||
acc[key] = next
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
function buildFieldActivity(obj) {
|
||||
if (!obj || typeof obj !== "object") {
|
||||
|
@ -77,16 +84,19 @@
|
|||
}
|
||||
|
||||
function changed() {
|
||||
// Required for reactivity
|
||||
fields = fields
|
||||
const newActivity = {}
|
||||
const trimmedFields = []
|
||||
for (let idx = 0; idx < fields.length; idx++) {
|
||||
const fieldName = fields[idx].name
|
||||
if (fieldName) {
|
||||
newActivity[fieldName] = fieldActivity[idx]
|
||||
trimmedFields.push(fields[idx])
|
||||
}
|
||||
}
|
||||
activity = newActivity
|
||||
dispatch("change", fields)
|
||||
dispatch("change", trimmedFields)
|
||||
}
|
||||
|
||||
function isJsonArray(value) {
|
||||
|
@ -101,7 +111,7 @@
|
|||
</script>
|
||||
|
||||
<!-- Builds Objects with Key Value Pairs. Useful for building things like Request Headers. -->
|
||||
{#if Object.keys(object || {}).length > 0}
|
||||
{#if Object.keys(fullObject || {}).length > 0}
|
||||
{#if headings}
|
||||
<div class="container" class:container-active={toggle}>
|
||||
<Label {tooltip}>{keyHeading || keyPlaceholder}</Label>
|
||||
|
|
|
@ -56,12 +56,13 @@
|
|||
let query, datasource
|
||||
let breakQs = {},
|
||||
requestBindings = {}
|
||||
let saveId, url
|
||||
let saveId
|
||||
let response, schema, enabledHeaders
|
||||
let authConfigId
|
||||
let dynamicVariables, addVariableModal, varBinding, globalDynamicBindings
|
||||
let restBindings = getRestBindings()
|
||||
let nestedSchemaFields = {}
|
||||
let saving
|
||||
let queryNameLabel
|
||||
|
||||
$: staticVariables = datasource?.config?.staticVariables || {}
|
||||
|
||||
|
@ -91,7 +92,7 @@
|
|||
$: datasourceType = datasource?.source
|
||||
$: integrationInfo = $integrations[datasourceType]
|
||||
$: queryConfig = integrationInfo?.query
|
||||
$: url = buildUrl(url, breakQs)
|
||||
$: url = buildUrl(query?.fields?.path, breakQs)
|
||||
$: checkQueryName(url)
|
||||
$: responseSuccess = response?.info?.code >= 200 && response?.info?.code < 400
|
||||
$: isGet = query?.queryVerb === "read"
|
||||
|
@ -103,6 +104,10 @@
|
|||
|
||||
$: runtimeUrlQueries = readableToRuntimeMap(mergedBindings, breakQs)
|
||||
|
||||
$: originalQuery = originalQuery ?? cloneDeep(query)
|
||||
$: builtQuery = buildQuery(query, runtimeUrlQueries, requestBindings)
|
||||
$: isModified = JSON.stringify(originalQuery) !== JSON.stringify(builtQuery)
|
||||
|
||||
function getSelectedQuery() {
|
||||
return cloneDeep(
|
||||
$queries.list.find(q => q._id === queryId) || {
|
||||
|
@ -126,7 +131,8 @@
|
|||
?.trim() || inputUrl
|
||||
|
||||
function checkQueryName(inputUrl = null) {
|
||||
if (query && (!query.name || query.flags.urlName)) {
|
||||
if (query && (!query.name || query.flags?.urlName)) {
|
||||
query.flags ??= {}
|
||||
query.flags.urlName = true
|
||||
query.name = cleanUrl(inputUrl)
|
||||
}
|
||||
|
@ -147,9 +153,12 @@
|
|||
return qs.length === 0 ? newUrl : `${newUrl}?${qs}`
|
||||
}
|
||||
|
||||
function buildQuery() {
|
||||
const newQuery = cloneDeep(query)
|
||||
const queryString = restUtils.buildQueryString(runtimeUrlQueries)
|
||||
function buildQuery(fromQuery, urlQueries, requestBindings) {
|
||||
if (!fromQuery) {
|
||||
return
|
||||
}
|
||||
const newQuery = cloneDeep(fromQuery)
|
||||
const queryString = restUtils.buildQueryString(urlQueries)
|
||||
|
||||
newQuery.parameters = restUtils.keyValueToQueryParameters(requestBindings)
|
||||
newQuery.fields.requestBody =
|
||||
|
@ -157,9 +166,8 @@
|
|||
? readableToRuntimeMap(mergedBindings, newQuery.fields.requestBody)
|
||||
: readableToRuntimeBinding(mergedBindings, newQuery.fields.requestBody)
|
||||
|
||||
newQuery.fields.path = url.split("?")[0]
|
||||
newQuery.fields.path = url?.split("?")[0]
|
||||
newQuery.fields.queryString = queryString
|
||||
newQuery.fields.authConfigId = authConfigId
|
||||
newQuery.fields.disabledHeaders = restUtils.flipHeaderState(enabledHeaders)
|
||||
newQuery.schema = schema || {}
|
||||
newQuery.nestedSchemaFields = nestedSchemaFields || {}
|
||||
|
@ -168,13 +176,12 @@
|
|||
}
|
||||
|
||||
async function saveQuery() {
|
||||
const toSave = buildQuery()
|
||||
const toSave = builtQuery
|
||||
saving = true
|
||||
try {
|
||||
const isNew = !query._rev
|
||||
const { _id } = await queries.save(toSave.datasourceId, toSave)
|
||||
saveId = _id
|
||||
query = getSelectedQuery()
|
||||
notifications.success(`Request saved successfully`)
|
||||
if (dynamicVariables) {
|
||||
datasource.config.dynamicVariables = rebuildVariables(saveId)
|
||||
datasource = await datasources.save({
|
||||
|
@ -182,6 +189,13 @@
|
|||
datasource,
|
||||
})
|
||||
}
|
||||
|
||||
notifications.success(`Request saved successfully`)
|
||||
if (isNew) {
|
||||
$goto(`../../${_id}`)
|
||||
}
|
||||
|
||||
query = getSelectedQuery()
|
||||
prettifyQueryRequestBody(
|
||||
query,
|
||||
requestBindings,
|
||||
|
@ -189,11 +203,15 @@
|
|||
staticVariables,
|
||||
restBindings
|
||||
)
|
||||
if (isNew) {
|
||||
$goto(`../../${_id}`)
|
||||
}
|
||||
|
||||
// Force rebuilding original query
|
||||
originalQuery = null
|
||||
|
||||
queryNameLabel.disableEditingState()
|
||||
} catch (err) {
|
||||
notifications.error(`Error saving query`)
|
||||
} finally {
|
||||
saving = false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,7 +245,7 @@
|
|||
async function runQuery() {
|
||||
try {
|
||||
await validateQuery()
|
||||
response = await queries.preview(buildQuery())
|
||||
response = await queries.preview(builtQuery)
|
||||
if (response.rows.length === 0) {
|
||||
notifications.info("Request did not return any data")
|
||||
} else {
|
||||
|
@ -249,22 +267,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
const getAuthConfigId = () => {
|
||||
let id = query.fields.authConfigId
|
||||
if (id) {
|
||||
// find the matching config on the datasource
|
||||
const matchedConfig = datasource?.config?.authConfigs?.filter(
|
||||
c => c._id === id
|
||||
)[0]
|
||||
// clear the id if the config is not found (deleted)
|
||||
// i.e. just show 'None' in the dropdown
|
||||
if (!matchedConfig) {
|
||||
id = undefined
|
||||
}
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
const buildAuthConfigs = datasource => {
|
||||
if (datasource?.config?.authConfigs) {
|
||||
return datasource.config.authConfigs.map(c => ({
|
||||
|
@ -375,13 +377,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
const paramsChanged = evt => {
|
||||
breakQs = {}
|
||||
for (let param of evt.detail) {
|
||||
breakQs[param.name] = param.value
|
||||
}
|
||||
}
|
||||
|
||||
const urlChanged = evt => {
|
||||
breakQs = {}
|
||||
const qs = evt.target.value.split("?")[1]
|
||||
|
@ -426,9 +421,7 @@
|
|||
) {
|
||||
query.fields.path = `${datasource.config.url}/${path ? path : ""}`
|
||||
}
|
||||
url = buildUrl(query.fields.path, breakQs)
|
||||
requestBindings = restUtils.queryParametersToKeyValue(query.parameters)
|
||||
authConfigId = getAuthConfigId()
|
||||
if (!query.fields.disabledHeaders) {
|
||||
query.fields.disabledHeaders = {}
|
||||
}
|
||||
|
@ -497,6 +490,7 @@
|
|||
<Layout gap="S">
|
||||
<div class="top-bar">
|
||||
<EditableLabel
|
||||
bind:this={queryNameLabel}
|
||||
type="heading"
|
||||
bind:value={query.name}
|
||||
defaultValue="Untitled"
|
||||
|
@ -504,7 +498,9 @@
|
|||
on:save={saveQuery}
|
||||
/>
|
||||
<div class="controls">
|
||||
<ConnectedQueryScreens sourceId={query._id} />
|
||||
{#if query._id}
|
||||
<ConnectedQueryScreens sourceId={query._id} />
|
||||
{/if}
|
||||
<div class="access">
|
||||
<Label>Access</Label>
|
||||
<AccessLevelSelect {query} {saveId} />
|
||||
|
@ -524,13 +520,13 @@
|
|||
<div class="url">
|
||||
<Input
|
||||
on:blur={urlChanged}
|
||||
bind:value={url}
|
||||
bind:value={query.fields.path}
|
||||
placeholder="http://www.api.com/endpoint"
|
||||
/>
|
||||
</div>
|
||||
<Button primary disabled={!url} on:click={runQuery}>Send</Button>
|
||||
<Button
|
||||
disabled={!query.name}
|
||||
disabled={!query.name || !isModified || saving}
|
||||
cta
|
||||
on:click={saveQuery}
|
||||
tooltip={!hasSchema
|
||||
|
@ -556,15 +552,12 @@
|
|||
/>
|
||||
</Tab>
|
||||
<Tab title="Params">
|
||||
{#key breakQs}
|
||||
<KeyValueBuilder
|
||||
on:change={paramsChanged}
|
||||
object={breakQs}
|
||||
name="param"
|
||||
headings
|
||||
bindings={mergedBindings}
|
||||
/>
|
||||
{/key}
|
||||
<KeyValueBuilder
|
||||
bind:object={breakQs}
|
||||
name="param"
|
||||
headings
|
||||
bindings={mergedBindings}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab title="Headers">
|
||||
<KeyValueBuilder
|
||||
|
@ -651,7 +644,7 @@
|
|||
label="Auth"
|
||||
labelPosition="left"
|
||||
placeholder="None"
|
||||
bind:value={authConfigId}
|
||||
bind:value={query.fields.authConfigId}
|
||||
options={authConfigs}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,20 +1,29 @@
|
|||
<script>
|
||||
import RestAuthenticationBuilder from "./RestAuthenticationBuilder.svelte"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
import SaveDatasourceButton from "../SaveDatasourceButton.svelte"
|
||||
import Panel from "../Panel.svelte"
|
||||
import Tooltip from "../Tooltip.svelte"
|
||||
import { integrations } from "@/stores/builder"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
|
||||
export let datasource
|
||||
$: updatedDatasource = cloneDeep(datasource)
|
||||
|
||||
const updateAuthConfigs = newAuthConfigs => {
|
||||
const updateAuthConfigs = async newAuthConfigs => {
|
||||
updatedDatasource.config.authConfigs = newAuthConfigs
|
||||
|
||||
try {
|
||||
await integrations.saveDatasource(updatedDatasource)
|
||||
notifications.success(
|
||||
`Datasource ${updatedDatasource.name} updated successfully`
|
||||
)
|
||||
} catch (error) {
|
||||
notifications.error(`Error saving datasource: ${error.message}`)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Panel>
|
||||
<SaveDatasourceButton slot="controls" {datasource} {updatedDatasource} />
|
||||
<Tooltip
|
||||
slot="tooltip"
|
||||
title="REST Authentication"
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
<script>
|
||||
import { get } from "svelte/store"
|
||||
import { isEqual } from "lodash"
|
||||
import { integrationForDatasource } from "@/stores/selectors"
|
||||
import { integrations, datasources } from "@/stores/builder"
|
||||
import { integrations } from "@/stores/builder"
|
||||
import { notifications, Button } from "@budibase/bbui"
|
||||
|
||||
export let datasource
|
||||
|
@ -12,11 +10,7 @@
|
|||
|
||||
const save = async () => {
|
||||
try {
|
||||
const integration = integrationForDatasource(
|
||||
get(integrations),
|
||||
updatedDatasource
|
||||
)
|
||||
await datasources.save({ datasource: updatedDatasource, integration })
|
||||
await integrations.saveDatasource(updatedDatasource)
|
||||
notifications.success(
|
||||
`Datasource ${updatedDatasource.name} updated successfully`
|
||||
)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { writable, type Writable } from "svelte/store"
|
||||
import { get, writable, type Writable } from "svelte/store"
|
||||
import { API } from "@/api"
|
||||
import { Integration } from "@budibase/types"
|
||||
import { Datasource, Integration } from "@budibase/types"
|
||||
import { integrationForDatasource } from "@/stores/selectors"
|
||||
import { datasources } from "./datasources"
|
||||
|
||||
type IntegrationsState = Record<string, Integration>
|
||||
|
||||
|
@ -25,9 +27,15 @@ const createIntegrationsStore = () => {
|
|||
store.set(integrations)
|
||||
}
|
||||
|
||||
const saveDatasource = async (datasource: Datasource) => {
|
||||
const integration = integrationForDatasource(get(store), datasource)
|
||||
await datasources.save({ datasource, integration })
|
||||
}
|
||||
|
||||
return {
|
||||
...store,
|
||||
init,
|
||||
saveDatasource,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit bb1ed6fa96ebed30e30659e47b0712567601f3c0
|
||||
Subproject commit f709bb6a07483785c32ebb6f186709450d735ec3
|
|
@ -1,6 +1,10 @@
|
|||
import { EnrichedBinding } from "../../ui"
|
||||
|
||||
export interface GenerateJsRequest {
|
||||
prompt: string
|
||||
bindings?: EnrichedBinding[]
|
||||
}
|
||||
|
||||
export interface GenerateJsResponse {
|
||||
code: string
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue