Merge remote-tracking branch 'origin/master' into feature/multistep-form-block

This commit is contained in:
Dean 2023-12-06 14:22:08 +00:00
commit f0603f7edc
18 changed files with 240 additions and 100 deletions

View File

@ -87,6 +87,7 @@ couchdb:
storageClass: "nfs-client"
adminPassword: admin
services:
objectStore:
storageClass: "nfs-client"
redis:

View File

@ -86,6 +86,7 @@ couchdb:
storageClass: "nfs-client"
adminPassword: admin
services:
objectStore:
storageClass: "nfs-client"
redis:

View File

@ -16,6 +16,7 @@ spec:
selector:
matchLabels:
app.kubernetes.io/name: budibase-proxy
minReadySeconds: 10
strategy:
type: RollingUpdate
template:

View File

@ -249,4 +249,30 @@ http {
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
}
# From https://docs.datadoghq.com/integrations/nginx/?tab=kubernetes
server {
listen 81;
server_name localhost;
access_log off;
allow 127.0.0.1;
deny all;
location /nginx_status {
# Choose your status module
# freely available with open source NGINX
stub_status;
# for open source NGINX < version 1.7.5
# stub_status on;
# available only with NGINX Plus
# status;
# ensures the version information can be retrieved
server_tokens on;
}
}
}

View File

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

View File

@ -57,16 +57,11 @@
}}
class="buttons"
>
<Icon hoverable size="M" name="Play" />
<Icon size="M" name="Play" />
<div>Run test</div>
</div>
<div class="buttons">
<Icon
disabled={!$automationStore.testResults}
hoverable
size="M"
name="Multiple"
/>
<Icon disabled={!$automationStore.testResults} size="M" name="Multiple" />
<div
class:disabled={!$automationStore.testResults}
on:click={() => {

View File

@ -97,6 +97,7 @@
class:typing={typing && !automationNameError}
class:typing-error={automationNameError}
class="blockSection"
on:click={() => dispatch("toggle")}
>
<div class="splitHeader">
<div class="center-items">
@ -138,7 +139,20 @@
on:input={e => {
automationName = e.target.value.trim()
}}
on:click={startTyping}
on:click={e => {
e.stopPropagation()
startTyping()
}}
on:keydown={async e => {
if (e.key === "Enter") {
typing = false
if (automationNameError) {
automationName = stepNames[block.id] || block?.name
} else {
await saveName()
}
}
}}
on:blur={async () => {
typing = false
if (automationNameError) {
@ -168,7 +182,11 @@
</StatusLight>
</div>
<Icon
on:click={() => dispatch("toggle")}
e.stopPropagation()
on:click={e => {
e.stopPropagation()
dispatch("toggle")
}}
hoverable
name={open ? "ChevronUp" : "ChevronDown"}
/>
@ -195,7 +213,10 @@
{/if}
{#if !showTestStatus}
<Icon
on:click={() => dispatch("toggle")}
on:click={e => {
e.stopPropagation()
dispatch("toggle")
}}
hoverable
name={open ? "ChevronUp" : "ChevronDown"}
/>

View File

@ -1,11 +1,9 @@
<script>
import {
ModalContent,
Tabs,
Tab,
TextArea,
Label,
notifications,
ActionButton,
} from "@budibase/bbui"
import { automationStore, selectedAutomation } from "builderStore"
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte"
@ -55,19 +53,36 @@
notifications.error(error)
}
}
const toggle = () => {
selectedValues = !selectedValues
selectedJSON = !selectedJSON
}
let selectedValues = true
let selectedJSON = false
</script>
<ModalContent
title="Add test data"
confirmText="Test"
size="M"
confirmText="Run test"
size="L"
showConfirmButton={true}
disabled={isError}
onConfirm={testAutomation}
cancelText="Cancel"
>
<Tabs selected="Form" quiet>
<Tab icon="Form" title="Form">
<div class="size">
<div class="options">
<ActionButton quiet selected={selectedValues} on:click={toggle}
>Use values</ActionButton
>
<ActionButton quiet selected={selectedJSON} on:click={toggle}
>Use JSON</ActionButton
>
</div>
</div>
{#if selectedValues}
<div class="tab-content-padding">
<AutomationBlockSetup
{testData}
@ -75,11 +90,9 @@
isTestModal
block={trigger}
/>
</div></Tab
>
<Tab icon="FileJson" title="JSON">
<div class="tab-content-padding">
<Label>JSON</Label>
</div>
{/if}
{#if selectedJSON}
<div class="text-area-container">
<TextArea
value={JSON.stringify($selectedAutomation.testData, null, 2)}
@ -87,18 +100,22 @@
on:change={e => parseTestJSON(e)}
/>
</div>
</div>
</Tab>
</Tabs>
{/if}
</ModalContent>
<style>
.text-area-container :global(textarea) {
min-height: 200px;
height: 200px;
min-height: 300px;
height: 300px;
}
.tab-content-padding {
padding: 0 var(--spacing-xl);
padding: 0 var(--spacing-s);
}
.options {
display: flex;
align-items: center;
gap: 8px;
}
</style>

View File

@ -9,7 +9,7 @@
<div class="title">
<div class="title-text">
<Icon name="MultipleCheck" />
<div style="padding-left: var(--spacing-l)">Test Details</div>
<div style="padding-left: var(--spacing-l); ">Test Details</div>
</div>
<div style="padding-right: var(--spacing-xl)">
<Icon
@ -40,6 +40,7 @@
display: flex;
flex-direction: row;
align-items: center;
padding-top: var(--spacing-s);
}
.title :global(h1) {

View File

@ -1,20 +1,44 @@
<script>
import AutomationList from "./AutomationList.svelte"
import CreateAutomationModal from "./CreateAutomationModal.svelte"
import { Modal, Button, Layout } from "@budibase/bbui"
import { Modal, Icon } from "@budibase/bbui"
import Panel from "components/design/Panel.svelte"
export let modal
export let webhookModal
</script>
<Panel title="Automations" borderRight>
<Layout paddingX="L" paddingY="XL" gap="S">
<Button cta on:click={modal.show}>Add automation</Button>
</Layout>
<Panel title="Automations" borderRight noHeaderBorder titleCSS={false}>
<span class="panel-title-content" slot="panel-title-content">
<div class="header">
<div>Automations</div>
<div on:click={modal.show} class="add-automation-button">
<Icon name="Add" />
</div>
</div>
</span>
<AutomationList />
</Panel>
<Modal bind:this={modal}>
<CreateAutomationModal {webhookModal} />
</Modal>
<style>
.header {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-m);
}
.add-automation-button {
margin-left: 130px;
color: var(--grey-7);
cursor: pointer;
}
.add-automation-button:hover {
color: var(--ink);
}
</style>

View File

@ -149,7 +149,6 @@
}
const initialiseField = (field, savingColumn) => {
isCreating = !field
if (field && !savingColumn) {
editableColumn = cloneDeep(field)
originalName = editableColumn.name ? editableColumn.name + "" : null
@ -171,7 +170,8 @@
relationshipPart2 = part2
}
}
} else if (!savingColumn) {
}
if (!savingColumn) {
let highestNumber = 0
Object.keys(table.schema).forEach(columnName => {
const columnNumber = extractColumnNumber(columnName)
@ -529,8 +529,16 @@
<Layout noPadding gap="S">
{#if mounted}
<Input
value={editableColumn.name}
autofocus
bind:value={editableColumn.name}
on:input={e => {
if (
!uneditable &&
!(linkEditDisabled && editableColumn.type === LINK_TYPE)
) {
editableColumn.name = e.target.value
}
}}
disabled={uneditable ||
(linkEditDisabled && editableColumn.type === LINK_TYPE)}
error={errors?.name}

View File

@ -16,7 +16,8 @@
export let wide = false
export let extraWide = false
export let closeButtonIcon = "Close"
export let noHeaderBorder = false
export let titleCSS = true
$: customHeaderContent = $$slots["panel-header-content"]
$: customTitleContent = $$slots["panel-title-content"]
</script>
@ -32,6 +33,7 @@
class="header"
class:custom={customHeaderContent}
class:borderBottom={borderBottomHeader}
class:noHeaderBorder
>
{#if showBackButton}
<Icon name="ArrowLeft" hoverable on:click={onClickBackButton} />
@ -41,7 +43,7 @@
<Icon name={icon} />
</AbsTooltip>
{/if}
<div class="title">
<div class:title={titleCSS}>
{#if customTitleContent}
<slot name="panel-title-content" />
{:else}
@ -106,6 +108,10 @@
padding: 0 var(--spacing-l);
gap: var(--spacing-m);
}
.noHeaderBorder {
border-bottom: none !important;
}
.header.borderBottom {
border-bottom: var(--border-light);
}

View File

@ -110,7 +110,7 @@
}
.setup {
padding-top: var(--spectrum-global-dimension-size-200);
padding-top: 9px;
border-left: var(--border-light);
display: flex;
flex-direction: column;

View File

@ -17,7 +17,7 @@ import {
import {
getSqlQuery,
buildExternalTableId,
convertSqlType,
generateColumnDefinition,
finaliseExternalTables,
SqlClient,
checkExternalTables,
@ -429,15 +429,12 @@ class SqlServerIntegration extends Sql implements DatasourcePlus {
const hasDefault = def.COLUMN_DEFAULT
const isAuto = !!autoColumns.find(col => col === name)
const required = !!requiredColumns.find(col => col === name)
schema[name] = {
schema[name] = generateColumnDefinition({
autocolumn: isAuto,
name: name,
constraints: {
name,
presence: required && !isAuto && !hasDefault,
},
...convertSqlType(def.DATA_TYPE),
externalType: def.DATA_TYPE,
}
})
}
tables[tableName] = {
_id: buildExternalTableId(datasourceId, tableName),

View File

@ -12,12 +12,13 @@ import {
SourceName,
Schema,
TableSourceType,
FieldType,
} from "@budibase/types"
import {
getSqlQuery,
SqlClient,
buildExternalTableId,
convertSqlType,
generateColumnDefinition,
finaliseExternalTables,
checkExternalTables,
} from "./utils"
@ -305,16 +306,17 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
(column.Extra === "auto_increment" ||
column.Extra.toLowerCase().includes("generated"))
const required = column.Null !== "YES"
const constraints = {
presence: required && !isAuto && !hasDefault,
}
schema[columnName] = {
schema[columnName] = generateColumnDefinition({
name: columnName,
autocolumn: isAuto,
constraints,
...convertSqlType(column.Type),
presence: required && !isAuto && !hasDefault,
externalType: column.Type,
}
options: column.Type.startsWith("enum")
? column.Type.substring(5, column.Type.length - 1)
.split(",")
.map(str => str.replace(/^'(.*)'$/, "$1"))
: undefined,
})
}
if (!tables[tableName]) {
tables[tableName] = {

View File

@ -15,7 +15,7 @@ import {
import {
buildExternalTableId,
checkExternalTables,
convertSqlType,
generateColumnDefinition,
finaliseExternalTables,
getSqlQuery,
SqlClient,
@ -250,14 +250,6 @@ class OracleIntegration extends Sql implements DatasourcePlus {
)
}
private internalConvertType(column: OracleColumn) {
if (this.isBooleanType(column)) {
return { type: FieldTypes.BOOLEAN }
}
return convertSqlType(column.type)
}
/**
* Fetches the tables from the oracle table and assigns them to the datasource.
* @param datasourceId - datasourceId to fetch
@ -302,13 +294,15 @@ class OracleIntegration extends Sql implements DatasourcePlus {
const columnName = oracleColumn.name
let fieldSchema = table.schema[columnName]
if (!fieldSchema) {
fieldSchema = {
fieldSchema = generateColumnDefinition({
autocolumn: OracleIntegration.isAutoColumn(oracleColumn),
name: columnName,
constraints: {
presence: false,
},
...this.internalConvertType(oracleColumn),
externalType: oracleColumn.type,
})
if (this.isBooleanType(oracleColumn)) {
fieldSchema.type = FieldTypes.BOOLEAN
}
table.schema[columnName] = fieldSchema

View File

@ -16,7 +16,7 @@ import {
import {
getSqlQuery,
buildExternalTableId,
convertSqlType,
generateColumnDefinition,
finaliseExternalTables,
SqlClient,
checkExternalTables,
@ -162,6 +162,14 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
WHERE pg_namespace.nspname = '${this.config.schema}';
`
ENUM_VALUES = () => `
SELECT t.typname,
e.enumlabel
FROM pg_type t
JOIN pg_enum e on t.oid = e.enumtypid
JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace;
`
constructor(config: PostgresConfig) {
super(SqlClient.POSTGRES)
this.config = config
@ -303,6 +311,18 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
const tables: { [key: string]: Table } = {}
// Fetch enum values
const enumsResponse = await this.client.query(this.ENUM_VALUES())
const enumValues = enumsResponse.rows?.reduce((acc, row) => {
if (!acc[row.typname]) {
return {
[row.typname]: [row.enumlabel],
}
}
acc[row.typname].push(row.enumlabel)
return acc
}, {})
for (let column of columnsResponse.rows) {
const tableName: string = column.table_name
const columnName: string = column.column_name
@ -333,16 +353,13 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
column.is_generated && column.is_generated !== "NEVER"
const isAuto: boolean = hasNextVal || identity || isGenerated
const required = column.is_nullable === "NO"
const constraints = {
presence: required && !hasDefault && !isGenerated,
}
tables[tableName].schema[columnName] = {
tables[tableName].schema[columnName] = generateColumnDefinition({
autocolumn: isAuto,
name: columnName,
constraints,
...convertSqlType(column.data_type),
presence: required && !hasDefault && !isGenerated,
externalType: column.data_type,
}
options: enumValues?.[column.udt_name],
})
}
let finalizedTables = finaliseExternalTables(tables, entities)

View File

@ -67,6 +67,10 @@ const SQL_BOOLEAN_TYPE_MAP = {
tinyint: FieldType.BOOLEAN,
}
const SQL_OPTIONS_TYPE_MAP = {
"user-defined": FieldType.OPTIONS,
}
const SQL_MISC_TYPE_MAP = {
json: FieldType.JSON,
bigint: FieldType.BIGINT,
@ -78,6 +82,7 @@ const SQL_TYPE_MAP = {
...SQL_STRING_TYPE_MAP,
...SQL_BOOLEAN_TYPE_MAP,
...SQL_MISC_TYPE_MAP,
...SQL_OPTIONS_TYPE_MAP,
}
export enum SqlClient {
@ -178,25 +183,49 @@ export function breakRowIdField(_id: string | { _id: string }): any[] {
}
}
export function convertSqlType(type: string) {
export function generateColumnDefinition(config: {
externalType: string
autocolumn: boolean
name: string
presence: boolean
options?: string[]
}) {
let { externalType, autocolumn, name, presence, options } = config
let foundType = FieldType.STRING
const lcType = type.toLowerCase()
const lowerCaseType = externalType.toLowerCase()
let matchingTypes = []
for (let [external, internal] of Object.entries(SQL_TYPE_MAP)) {
if (lcType.includes(external)) {
if (lowerCaseType.includes(external)) {
matchingTypes.push({ external, internal })
}
}
//Set the foundType based the longest match
// Set the foundType based the longest match
if (matchingTypes.length > 0) {
foundType = matchingTypes.reduce((acc, val) => {
return acc.external.length >= val.external.length ? acc : val
}).internal
}
const schema: any = { type: foundType }
const constraints: {
presence: boolean
inclusion?: string[]
} = {
presence,
}
if (foundType === FieldType.OPTIONS) {
constraints.inclusion = options
}
const schema: any = {
type: foundType,
externalType,
autocolumn,
name,
constraints,
}
if (foundType === FieldType.DATETIME) {
schema.dateOnly = SQL_DATE_ONLY_TYPES.includes(lcType)
schema.timeOnly = SQL_TIME_ONLY_TYPES.includes(lcType)
schema.dateOnly = SQL_DATE_ONLY_TYPES.includes(lowerCaseType)
schema.timeOnly = SQL_TIME_ONLY_TYPES.includes(lowerCaseType)
}
return schema
}