Merge remote-tracking branch 'origin/master' into feature/multistep-form-block
This commit is contained in:
commit
f0603f7edc
|
@ -87,6 +87,7 @@ couchdb:
|
|||
storageClass: "nfs-client"
|
||||
adminPassword: admin
|
||||
|
||||
services:
|
||||
objectStore:
|
||||
storageClass: "nfs-client"
|
||||
redis:
|
||||
|
|
|
@ -86,6 +86,7 @@ couchdb:
|
|||
storageClass: "nfs-client"
|
||||
adminPassword: admin
|
||||
|
||||
services:
|
||||
objectStore:
|
||||
storageClass: "nfs-client"
|
||||
redis:
|
||||
|
|
|
@ -16,6 +16,7 @@ spec:
|
|||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: budibase-proxy
|
||||
minReadySeconds: 10
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
template:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "2.13.31",
|
||||
"version": "2.13.32",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
|
|
@ -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={() => {
|
||||
|
|
|
@ -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"}
|
||||
/>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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] = {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue