Adding all response capabilities.

This commit is contained in:
mike12345567 2021-12-06 17:39:51 +00:00
parent 65d6ca9249
commit ba9854d121
18 changed files with 212 additions and 189 deletions

View File

@ -8,6 +8,7 @@
export let error = null
export let id = null
export let height = null
export let minHeight = null
export const getCaretPosition = () => ({
start: textarea.selectionStart,
end: textarea.selectionEnd,
@ -23,7 +24,8 @@
</script>
<div
style={height ? `height: ${height}px;` : ""}
style={(height ? `height: ${height}px;` : "") +
(minHeight ? `min-height: ${minHeight}px` : "")}
class="spectrum-Textfield spectrum-Textfield--multiline"
class:is-invalid={!!error}
class:is-disabled={disabled}

View File

@ -11,6 +11,7 @@
export let error = null
export let getCaretPosition = null
export let height = null
export let minHeight = null
const dispatch = createEventDispatcher()
const onChange = e => {
@ -27,6 +28,7 @@
{value}
{placeholder}
{height}
{minHeight}
on:change={onChange}
/>
</Field>

View File

@ -1,5 +1,5 @@
import { datasources, tables } from "../stores/backend"
import { IntegrationNames } from "../constants"
import { IntegrationNames } from "../constants/backend"
import analytics, { Events } from "../analytics"
import { get } from "svelte/store"
import cloneDeep from "lodash/cloneDeepWith"

View File

@ -39,7 +39,7 @@
$: editRowComponent = isUsersTable ? CreateEditUser : CreateEditRow
$: {
UNSORTABLE_TYPES.forEach(type => {
Object.values(schema).forEach(col => {
Object.values(schema || {}).forEach(col => {
if (col.type === type) {
col.sortable = false
}
@ -113,16 +113,16 @@
<Layout noPadding gap="S">
<div>
<div class="table-title">
{#if title}
{#if title}
<div class="table-title">
<Heading size="S">{title}</Heading>
{/if}
{#if loading}
<div transition:fade|local>
<Spinner size="10" />
</div>
{/if}
</div>
{#if loading}
<div transition:fade|local>
<Spinner size="10" />
</div>
{/if}
</div>
{/if}
<div class="popovers">
<slot />
{#if !isUsersTable && selectedRows.length > 0}

View File

@ -9,7 +9,7 @@
} from "@budibase/bbui"
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
import { capitalise } from "helpers"
import { IntegrationTypes } from "constants"
import { IntegrationTypes } from "constants/backend"
export let datasource
export let schema

View File

@ -3,7 +3,7 @@
import { onMount } from "svelte"
import ICONS from "../icons"
import api from "builderStore/api"
import { IntegrationNames, IntegrationTypes } from "constants"
import { IntegrationNames, IntegrationTypes } from "constants/backend"
import CreateTableModal from "components/backend/TableNavigator/modals/CreateTableModal.svelte"
import DatasourceConfigModal from "components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte"
import { createRestDatasource } from "builderStore/datasource"

View File

@ -2,7 +2,7 @@
import { goto } from "@roxi/routify"
import { ModalContent, notifications, Body, Layout } from "@budibase/bbui"
import IntegrationConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte"
import { IntegrationNames } from "constants"
import { IntegrationNames } from "constants/backend"
import cloneDeep from "lodash/cloneDeepWith"
import { saveDatasource as save } from "builderStore/datasource"

View File

@ -0,0 +1,11 @@
<script>
import { TextArea } from "@budibase/bbui"
export let data
export let height
export let minHeight = "120"
$: string = JSON.stringify(data || {}, null, 2)
</script>
<TextArea disabled value={string} {height} {minHeight} />

View File

@ -1,5 +1,12 @@
<script>
import { Icon, ActionButton, Input, Label, Toggle } from "@budibase/bbui"
import {
Icon,
ActionButton,
Input,
Label,
Toggle,
Select,
} from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import { lowercase } from "helpers"
@ -12,6 +19,7 @@
export let name
export let headings = false
export let activity = false
export let options
let fields = Object.entries(object).map(([name, value]) => ({ name, value }))
@ -50,7 +58,15 @@
<div class="container" class:container-active={activity} class:readOnly>
{#each fields as field, idx}
<Input placeholder="Key" bind:value={field.name} on:change={changed} />
<Input placeholder="Value" bind:value={field.value} on:change={changed} />
{#if options}
<Select bind:value={field.value} on:change={changed} {options} />
{:else}
<Input
placeholder="Value"
bind:value={field.value}
on:change={changed}
/>
{/if}
{#if activity}
<Toggle />
{/if}

View File

@ -14,7 +14,6 @@
Tab,
} from "@budibase/bbui"
import { notifications, Divider } from "@budibase/bbui"
import api from "builderStore/api"
import ExtraQueryConfig from "./ExtraQueryConfig.svelte"
import IntegrationQueryEditor from "components/integration/index.svelte"
import ExternalDataSourceTable from "components/backend/DataTable/ExternalDataSourceTable.svelte"
@ -28,23 +27,20 @@
} from "stores/backend"
import { capitalise } from "../../helpers"
import CodeMirrorEditor from "components/common/CodeMirrorEditor.svelte"
import { Roles } from "constants/backend"
import JSONPreview from "./JSONPreview.svelte"
import { Roles, SchemaTypeOptions } from "constants/backend"
import { onMount } from "svelte"
import KeyValueBuilder from "./KeyValueBuilder.svelte"
import { fieldsToSchema, schemaToFields } from "helpers/data/utils"
export let query
let fields = query.schema ? schemaToFields(query.schema) : []
let fields = query?.schema ? schemaToFields(query.schema) : []
let parameters
let data = []
let roleId
const transformerDocs =
"https://docs.budibase.com/building-apps/data/transformers"
const typeOptions = [
{ label: "Text", value: "string" },
{ label: "Number", value: "number" },
{ label: "Boolean", value: "boolean" },
{ label: "Datetime", value: "datetime" },
]
$: datasource = $datasources.list.find(ds => ds._id === query.datasourceId)
$: query.schema = fieldsToSchema(fields)
@ -60,15 +56,6 @@
query.transformer = "return data"
}
function newField() {
fields = [...fields, {}]
}
function deleteField(idx) {
fields.splice(idx, 1)
fields = fields
}
function resetDependentFields() {
if (query.fields.extra) {
query.fields.extra = {}
@ -94,43 +81,18 @@
async function previewQuery() {
try {
const response = await api.post(`/api/queries/preview`, {
fields: query.fields,
queryVerb: query.queryVerb,
transformer: query.transformer,
parameters: query.parameters.reduce(
(acc, next) => ({
...acc,
[next.name]: next.default,
}),
{}
),
datasourceId: datasource._id,
})
const json = await response.json()
if (response.status !== 200) throw new Error(json.message)
data = json.rows || []
if (data.length === 0) {
const response = await queries.preview(query)
if (response.rows.length === 0) {
notifications.info(
"Query results empty. Please execute a query with results to create your schema."
)
return
}
data = response.rows
fields = response.schema
notifications.success("Query executed successfully.")
// Assume all the fields are strings and create a basic schema from the
// unique fields returned by the server
fields = json.schemaFields.map(field => ({
name: field,
type: "string",
}))
} catch (err) {
notifications.error(`Query Error: ${err.message}`)
console.error(err)
notifications.error(err)
}
}
@ -146,26 +108,6 @@
}
}
function schemaToFields(schema) {
return Object.keys(schema).map(key => ({
name: key,
type: query.schema[key].type,
}))
}
function fieldsToSchema(fieldsToConvert) {
return fieldsToConvert.reduce(
(acc, next) => ({
...acc,
[next.name]: {
name: next.name,
type: next.type,
},
}),
{}
)
}
onMount(async () => {
if (!query || !query._id) {
roleId = Roles.BASIC
@ -271,29 +213,15 @@
{#if data}
<Tabs selected="JSON">
<Tab title="JSON">
<pre
class="preview">
<!-- prettier-ignore -->
{#if !data[0]}
Please run your query to fetch some data.
{:else}
{JSON.stringify(data[0], undefined, 2)}
{/if}
</pre>
<JSONPreview data={data[0]} minHeight="120" />
</Tab>
<Tab title="Schema">
<Layout gap="S">
{#each fields as field, idx}
<div class="field">
<Input placeholder="Field Name" bind:value={field.name} />
<Select bind:value={field.type} options={typeOptions} />
<Icon name="bleClose" on:click={() => deleteField(idx)} />
</div>
{/each}
<div>
<Button secondary on:click={newField}>Add Field</Button>
</div>
</Layout>
<KeyValueBuilder
bind:object={fields}
name="field"
headings
options={SchemaTypeOptions}
/>
</Tab>
<Tab title="Preview">
<ExternalDataSourceTable {query} {data} />
@ -322,29 +250,11 @@
justify-content: space-between;
}
.field {
display: grid;
grid-template-columns: 1fr 1fr 5%;
gap: var(--spacing-l);
}
.viewer {
min-height: 200px;
width: 640px;
}
.preview {
height: 100%;
min-height: 120px;
overflow-y: auto;
overflow-wrap: break-word;
white-space: pre-wrap;
background-color: var(--grey-2);
padding: var(--spacing-m);
border-radius: 8px;
color: var(--ink);
}
.viewer-controls {
display: flex;
flex-direction: row;

View File

@ -154,3 +154,58 @@ export const ALLOWABLE_NUMBER_TYPES = ALLOWABLE_NUMBER_OPTIONS.map(
export const SWITCHABLE_TYPES = ALLOWABLE_NUMBER_TYPES.concat(
ALLOWABLE_STRING_TYPES
)
export const IntegrationTypes = {
POSTGRES: "POSTGRES",
MONGODB: "MONGODB",
COUCHDB: "COUCHDB",
S3: "S3",
MYSQL: "MYSQL",
REST: "REST",
DYNAMODB: "DYNAMODB",
ELASTICSEARCH: "ELASTICSEARCH",
SQL_SERVER: "SQL_SERVER",
AIRTABLE: "AIRTABLE",
ARANGODB: "ARANGODB",
ORACLE: "ORACLE",
INTERNAL: "INTERNAL",
}
export const IntegrationNames = {
[IntegrationTypes.POSTGRES]: "PostgreSQL",
[IntegrationTypes.MONGODB]: "MongoDB",
[IntegrationTypes.COUCHDB]: "CouchDB",
[IntegrationTypes.S3]: "S3",
[IntegrationTypes.MYSQL]: "MySQL",
[IntegrationTypes.REST]: "REST",
[IntegrationTypes.DYNAMODB]: "DynamoDB",
[IntegrationTypes.ELASTICSEARCH]: "ElasticSearch",
[IntegrationTypes.SQL_SERVER]: "SQL Server",
[IntegrationTypes.AIRTABLE]: "Airtable",
[IntegrationTypes.ARANGODB]: "ArangoDB",
[IntegrationTypes.ORACLE]: "Oracle",
[IntegrationTypes.INTERNAL]: "Internal",
}
export const SchemaTypeOptions = [
{ label: "Text", value: "string" },
{ label: "Number", value: "number" },
{ label: "Boolean", value: "boolean" },
{ label: "Datetime", value: "datetime" },
]
export const RawRestBodyTypes = {
NONE: "none",
FORM: "form",
ENCODED: "encoded",
JSON: "json",
TEXT: "text",
}
export const RestBodyTypes = [
{ name: "none", value: "none" },
{ name: "form-data", value: "form" },
{ name: "x-www-form-encoded", value: "encoded" },
{ name: "raw (JSON)", value: "json" },
{ name: "raw (Text)", value: "text" },
]

View File

@ -15,38 +15,6 @@ export const AppStatus = {
DEPLOYED: "published",
}
export const IntegrationTypes = {
POSTGRES: "POSTGRES",
MONGODB: "MONGODB",
COUCHDB: "COUCHDB",
S3: "S3",
MYSQL: "MYSQL",
REST: "REST",
DYNAMODB: "DYNAMODB",
ELASTICSEARCH: "ELASTICSEARCH",
SQL_SERVER: "SQL_SERVER",
AIRTABLE: "AIRTABLE",
ARANGODB: "ARANGODB",
ORACLE: "ORACLE",
INTERNAL: "INTERNAL",
}
export const IntegrationNames = {
[IntegrationTypes.POSTGRES]: "PostgreSQL",
[IntegrationTypes.MONGODB]: "MongoDB",
[IntegrationTypes.COUCHDB]: "CouchDB",
[IntegrationTypes.S3]: "S3",
[IntegrationTypes.MYSQL]: "MySQL",
[IntegrationTypes.REST]: "REST",
[IntegrationTypes.DYNAMODB]: "DynamoDB",
[IntegrationTypes.ELASTICSEARCH]: "ElasticSearch",
[IntegrationTypes.SQL_SERVER]: "SQL Server",
[IntegrationTypes.AIRTABLE]: "Airtable",
[IntegrationTypes.ARANGODB]: "ArangoDB",
[IntegrationTypes.ORACLE]: "Oracle",
[IntegrationTypes.INTERNAL]: "Internal",
}
// fields on the user table that cannot be edited
export const UNEDITABLE_USER_FIELDS = [
"email",
@ -66,22 +34,6 @@ export const LAYOUT_NAMES = {
},
}
export const RawRestBodyTypes = {
NONE: "none",
FORM: "form",
ENCODED: "encoded",
JSON: "json",
TEXT: "text",
}
export const RestBodyTypes = [
{ name: "none", value: "none" },
{ name: "form-data", value: "form" },
{ name: "x-www-form-encoded", value: "encoded" },
{ name: "raw (JSON)", value: "json" },
{ name: "raw (Text)", value: "text" },
]
export const BUDIBASE_INTERNAL_DB = "bb_internal"
export const APP_NAME_REGEX = /^[\w\s]+$/

View File

@ -0,0 +1,19 @@
export function schemaToFields(schema) {
const response = {}
if (schema && typeof schema === "object") {
for (let [field, value] of Object.entries(schema)) {
response[field] = value?.type || "string"
}
}
return response
}
export function fieldsToSchema(fields) {
const response = {}
if (fields && typeof fields === "object") {
for (let [name, type] of Object.entries(fields)) {
response[name] = { name, type }
}
}
return response
}

View File

@ -1,7 +1,7 @@
<script>
import { params } from "@roxi/routify"
import { queries, datasources } from "stores/backend"
import { IntegrationTypes } from "constants"
import { IntegrationTypes } from "constants/backend"
import { goto } from "@roxi/routify"
if ($params.query) {
@ -13,7 +13,7 @@
const datasource = $datasources.list.find(
ds => ds._id === $datasources.selected
)
if (datasource.source === IntegrationTypes.REST) {
if (datasource?.source === IntegrationTypes.REST) {
$goto("../rest")
}
</script>

View File

@ -1,6 +1,6 @@
<script>
import { Body } from "@budibase/bbui"
import { RawRestBodyTypes } from "constants"
import { RawRestBodyTypes } from "constants/backend"
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
import CodeMirrorEditor, {
EditorModes,

View File

@ -15,7 +15,7 @@
import PlusConfigForm from "components/backend/DatasourceNavigator/TableIntegrationMenu/PlusConfigForm.svelte"
import ICONS from "components/backend/DatasourceNavigator/icons"
import VerbRenderer from "./_components/VerbRenderer.svelte"
import { IntegrationTypes } from "constants"
import { IntegrationTypes } from "constants/backend"
import { isEqual } from "lodash"
import { cloneDeep } from "lodash/fp"

View File

@ -15,6 +15,7 @@
Label,
TextArea,
Table,
notifications,
} from "@budibase/bbui"
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
import EditableLabel from "components/common/inputs/EditableLabel.svelte"
@ -24,13 +25,19 @@
import RestBodyInput from "../_components/RestBodyInput.svelte"
import { capitalise } from "helpers"
import { onMount } from "svelte"
import { RestBodyTypes as bodyTypes } from "constants"
import { fieldsToSchema, schemaToFields } from "helpers/data/utils"
import {
RestBodyTypes as bodyTypes,
SchemaTypeOptions,
} from "constants/backend"
import JSONPreview from "components/integration/JSONPreview.svelte"
let query
let breakQs = {}
let url = ""
// test - { info: { code: 500, time: "455ms", size: "2.09KB" }}
let response
let schema
$: datasource = $datasources.list.find(ds => ds._id === query?.datasourceId)
$: datasourceType = datasource?.source
@ -102,15 +109,38 @@
function learnMoreBanner() {}
function saveQuery() {}
function buildQuery() {
const newQuery = { ...query }
const queryString = buildQueryString(breakQs)
newQuery.fields.path = url.split("?")[0]
newQuery.fields.queryString = queryString
return newQuery
}
function sendQuery() {}
function saveQuery() {
query.schema = fieldsToSchema(schema)
}
async function runQuery() {
try {
response = await queries.preview(buildQuery(query))
if (response.rows.length === 0) {
notifications.info("Request did not return any data.")
} else {
response.info = response.info || { code: 200 }
notifications.success("Request sent successfully.")
}
} catch (err) {
notifications.error(err)
}
}
onMount(() => {
query = getSelectedQuery()
const qs = query?.fields.queryString
breakQs = breakQueryString(qs)
url = buildUrl(query.fields.path, qs)
schema = schemaToFields(query.schema)
if (query && !query.transformer) {
query.transformer = "return data"
}
@ -149,7 +179,7 @@
<div class="url">
<Input bind:value={url} />
</div>
<Button cta disabled={!url} on:click={sendQuery}>Send</Button>
<Button cta disabled={!url} on:click={runQuery}>Send</Button>
</div>
<Tabs selected="Params" quiet noPadding noHorizPadding>
<Tab title="Params">
@ -203,20 +233,17 @@
{:else}
<Tabs selected="JSON" quiet noPadding noHorizPadding>
<Tab title="JSON">
<CodeMirrorEditor
height={300}
value={response.text}
resize="vertical"
readonly
on:change={e => (query.transformer = e.detail)}
/>
<div>
<JSONPreview height="300" data={response.rows[0]} />
</div>
</Tab>
<Tab title="Schema">
<KeyValueBuilder
bind:object={response.schemaFields}
bind:object={response.schema}
name="header"
headings
activity
options={SchemaTypeOptions}
/>
</Tab>
<Tab title="Raw">
@ -225,7 +252,7 @@
<Tab title="Preview">
{#if response}
<Table
schema={response?.schemaFields}
schema={response?.schema}
data={response?.rows}
allowEditColumns={false}
allowEditRows={false}

View File

@ -62,6 +62,35 @@ export function createQueriesStore() {
unselect: () => {
update(state => ({ ...state, selected: null }))
},
preview: async query => {
const response = await api.post("/api/queries/preview", {
fields: query.fields,
queryVerb: query.queryVerb,
transformer: query.transformer,
parameters: query.parameters.reduce(
(acc, next) => ({
...acc,
[next.name]: next.default,
}),
{}
),
datasourceId: query.datasourceId,
})
if (response.status !== 200) {
const error = await response.text()
throw `Query error: ${error}`
}
const json = await response.json()
// Assume all the fields are strings and create a basic schema from the
// unique fields returned by the server
const schema = {}
for (let field of json.schemaFields) {
schema[field] = "string"
}
return { ...json, schema, rows: json.rows || [] }
},
delete: async query => {
const response = await api.delete(
`/api/queries/${query._id}/${query._rev}`