Merge branch 'develop' of github.com:Budibase/budibase into feature/budibase-api
This commit is contained in:
commit
2436bc2e32
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "1.0.76-alpha.5",
|
"version": "1.0.79-alpha.5",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"name": "@budibase/backend-core",
|
||||||
"version": "1.0.76-alpha.5",
|
"version": "1.0.79-alpha.5",
|
||||||
"description": "Budibase backend core libraries used in server and worker",
|
"description": "Budibase backend core libraries used in server and worker",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/bbui",
|
"name": "@budibase/bbui",
|
||||||
"description": "A UI solution used in the different Budibase projects.",
|
"description": "A UI solution used in the different Budibase projects.",
|
||||||
"version": "1.0.76-alpha.5",
|
"version": "1.0.79-alpha.5",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
|
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
|
||||||
"@budibase/string-templates": "^1.0.72-alpha.0",
|
"@budibase/string-templates": "^1.0.79-alpha.5",
|
||||||
"@spectrum-css/actionbutton": "^1.0.1",
|
"@spectrum-css/actionbutton": "^1.0.1",
|
||||||
"@spectrum-css/actiongroup": "^1.0.1",
|
"@spectrum-css/actiongroup": "^1.0.1",
|
||||||
"@spectrum-css/avatar": "^3.0.2",
|
"@spectrum-css/avatar": "^3.0.2",
|
||||||
|
|
|
@ -47,7 +47,9 @@
|
||||||
<use xlink:href="#spectrum-css-icon-Dash100" />
|
<use xlink:href="#spectrum-css-icon-Dash100" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<span class="spectrum-Checkbox-label">{text || ""}</span>
|
{#if text}
|
||||||
|
<span class="spectrum-Checkbox-label">{text}</span>
|
||||||
|
{/if}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -54,34 +54,43 @@
|
||||||
|
|
||||||
<svelte:window on:keydown={handleKey} />
|
<svelte:window on:keydown={handleKey} />
|
||||||
|
|
||||||
<!-- These svelte if statements need to be defined like this. -->
|
{#if inline}
|
||||||
<!-- The modal transitions do not work if nested inside more than one "if" -->
|
{#if visible}
|
||||||
{#if visible && inline}
|
<div use:focusFirstInput class="spectrum-Modal inline is-open">
|
||||||
<div use:focusFirstInput class="spectrum-Modal inline is-open">
|
<slot />
|
||||||
<slot />
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
{:else if visible}
|
{:else}
|
||||||
|
<!--
|
||||||
|
We cannot conditionally render the portal as this leads to a missing
|
||||||
|
insertion point when using nested modals. Therefore we just conditionally
|
||||||
|
render the content of the portal.
|
||||||
|
It still breaks the modal animation, but its better than soft bricking the
|
||||||
|
screen.
|
||||||
|
-->
|
||||||
<Portal target=".modal-container">
|
<Portal target=".modal-container">
|
||||||
<div
|
{#if visible}
|
||||||
class="spectrum-Underlay is-open"
|
<div
|
||||||
in:fade={{ duration: 200 }}
|
class="spectrum-Underlay is-open"
|
||||||
out:fade|local={{ duration: 200 }}
|
in:fade={{ duration: 200 }}
|
||||||
on:mousedown|self={cancel}
|
out:fade|local={{ duration: 200 }}
|
||||||
>
|
on:mousedown|self={cancel}
|
||||||
<div class="modal-wrapper" on:mousedown|self={cancel}>
|
>
|
||||||
<div class="modal-inner-wrapper" on:mousedown|self={cancel}>
|
<div class="modal-wrapper" on:mousedown|self={cancel}>
|
||||||
<slot name="outside" />
|
<div class="modal-inner-wrapper" on:mousedown|self={cancel}>
|
||||||
<div
|
<slot name="outside" />
|
||||||
use:focusFirstInput
|
<div
|
||||||
class="spectrum-Modal is-open"
|
use:focusFirstInput
|
||||||
in:fly={{ y: 30, duration: 200 }}
|
class="spectrum-Modal is-open"
|
||||||
out:fly|local={{ y: 30, duration: 200 }}
|
in:fly={{ y: 30, duration: 200 }}
|
||||||
>
|
out:fly|local={{ y: 30, duration: 200 }}
|
||||||
<slot />
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
</Portal>
|
</Portal>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,21 @@
|
||||||
export let allowEditRows = false
|
export let allowEditRows = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if allowSelectRows}
|
<div>
|
||||||
<Checkbox value={selected} />
|
{#if allowSelectRows}
|
||||||
{/if}
|
<Checkbox value={selected} />
|
||||||
{#if allowEditRows}
|
{/if}
|
||||||
<ActionButton size="S" on:click={onEdit}>Edit</ActionButton>
|
{#if allowEditRows}
|
||||||
{/if}
|
<ActionButton size="S" on:click={onEdit}>Edit</ActionButton>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import SelectEditRenderer from "./SelectEditRenderer.svelte"
|
import SelectEditRenderer from "./SelectEditRenderer.svelte"
|
||||||
import { cloneDeep, deepGet } from "../helpers"
|
import { cloneDeep, deepGet } from "../helpers"
|
||||||
import ProgressCircle from "../ProgressCircle/ProgressCircle.svelte"
|
import ProgressCircle from "../ProgressCircle/ProgressCircle.svelte"
|
||||||
|
import Checkbox from "../Form/Checkbox.svelte"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The expected schema is our normal couch schemas for our tables.
|
* The expected schema is our normal couch schemas for our tables.
|
||||||
|
@ -31,7 +32,6 @@
|
||||||
export let allowEditRows = true
|
export let allowEditRows = true
|
||||||
export let allowEditColumns = true
|
export let allowEditColumns = true
|
||||||
export let selectedRows = []
|
export let selectedRows = []
|
||||||
export let editColumnTitle = "Edit"
|
|
||||||
export let customRenderers = []
|
export let customRenderers = []
|
||||||
export let disableSorting = false
|
export let disableSorting = false
|
||||||
export let autoSortColumns = true
|
export let autoSortColumns = true
|
||||||
|
@ -50,6 +50,8 @@
|
||||||
// Table state
|
// Table state
|
||||||
let height = 0
|
let height = 0
|
||||||
let loaded = false
|
let loaded = false
|
||||||
|
let checkboxStatus = false
|
||||||
|
|
||||||
$: schema = fixSchema(schema)
|
$: schema = fixSchema(schema)
|
||||||
$: if (!loading) loaded = true
|
$: if (!loading) loaded = true
|
||||||
$: fields = getFields(schema, showAutoColumns, autoSortColumns)
|
$: fields = getFields(schema, showAutoColumns, autoSortColumns)
|
||||||
|
@ -67,6 +69,16 @@
|
||||||
$: showEditColumn = allowEditRows || allowSelectRows
|
$: showEditColumn = allowEditRows || allowSelectRows
|
||||||
$: cellStyles = computeCellStyles(schema)
|
$: cellStyles = computeCellStyles(schema)
|
||||||
|
|
||||||
|
// Deselect the "select all" checkbox when the user navigates to a new page
|
||||||
|
$: {
|
||||||
|
let checkRowCount = rows.filter(o1 =>
|
||||||
|
selectedRows.some(o2 => o1._id === o2._id)
|
||||||
|
)
|
||||||
|
if (checkRowCount.length === 0) {
|
||||||
|
checkboxStatus = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const fixSchema = schema => {
|
const fixSchema = schema => {
|
||||||
let fixedSchema = {}
|
let fixedSchema = {}
|
||||||
Object.entries(schema || {}).forEach(([fieldName, fieldSchema]) => {
|
Object.entries(schema || {}).forEach(([fieldName, fieldSchema]) => {
|
||||||
|
@ -197,13 +209,32 @@
|
||||||
if (!allowSelectRows) {
|
if (!allowSelectRows) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (selectedRows.includes(row)) {
|
if (selectedRows.some(selectedRow => selectedRow._id === row._id)) {
|
||||||
selectedRows = selectedRows.filter(selectedRow => selectedRow !== row)
|
selectedRows = selectedRows.filter(
|
||||||
|
selectedRow => selectedRow._id !== row._id
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
selectedRows = [...selectedRows, row]
|
selectedRows = [...selectedRows, row]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toggleSelectAll = e => {
|
||||||
|
const select = !!e.detail
|
||||||
|
if (select) {
|
||||||
|
// Add any rows which are not already in selected rows
|
||||||
|
rows.forEach(row => {
|
||||||
|
if (selectedRows.findIndex(x => x._id === row._id) === -1) {
|
||||||
|
selectedRows.push(row)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Remove any rows from selected rows that are in the current data set
|
||||||
|
selectedRows = selectedRows.filter(el =>
|
||||||
|
rows.every(f => f._id !== el._id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const computeCellStyles = schema => {
|
const computeCellStyles = schema => {
|
||||||
let styles = {}
|
let styles = {}
|
||||||
Object.keys(schema || {}).forEach(field => {
|
Object.keys(schema || {}).forEach(field => {
|
||||||
|
@ -244,7 +275,14 @@
|
||||||
<div
|
<div
|
||||||
class="spectrum-Table-headCell spectrum-Table-headCell--divider spectrum-Table-headCell--edit"
|
class="spectrum-Table-headCell spectrum-Table-headCell--divider spectrum-Table-headCell--edit"
|
||||||
>
|
>
|
||||||
{editColumnTitle || ""}
|
{#if allowSelectRows}
|
||||||
|
<Checkbox
|
||||||
|
bind:value={checkboxStatus}
|
||||||
|
on:change={toggleSelectAll}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
Edit
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#each fields as field}
|
{#each fields as field}
|
||||||
|
@ -302,11 +340,16 @@
|
||||||
{#if showEditColumn}
|
{#if showEditColumn}
|
||||||
<div
|
<div
|
||||||
class="spectrum-Table-cell spectrum-Table-cell--divider spectrum-Table-cell--edit"
|
class="spectrum-Table-cell spectrum-Table-cell--divider spectrum-Table-cell--edit"
|
||||||
|
on:click={e => {
|
||||||
|
toggleSelectRow(row)
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<SelectEditRenderer
|
<SelectEditRenderer
|
||||||
data={row}
|
data={row}
|
||||||
selected={selectedRows.includes(row)}
|
selected={selectedRows.findIndex(
|
||||||
onToggleSelection={() => toggleSelectRow(row)}
|
selectedRow => selectedRow._id === row._id
|
||||||
|
) !== -1}
|
||||||
onEdit={e => editRow(e, row)}
|
onEdit={e => editRow(e, row)}
|
||||||
{allowSelectRows}
|
{allowSelectRows}
|
||||||
{allowEditRows}
|
{allowEditRows}
|
||||||
|
|
|
@ -33,7 +33,7 @@ filterTests(['smoke', 'all'], () => {
|
||||||
cy.get(".spectrum-Button--cta").click()
|
cy.get(".spectrum-Button--cta").click()
|
||||||
})
|
})
|
||||||
cy.contains("Setup").click()
|
cy.contains("Setup").click()
|
||||||
cy.get(".spectrum-Picker-label").click()
|
cy.get(".spectrum-Picker-label").eq(1).click()
|
||||||
cy.contains("dog").click()
|
cy.contains("dog").click()
|
||||||
cy.get(".spectrum-Textfield-input")
|
cy.get(".spectrum-Textfield-input")
|
||||||
.first()
|
.first()
|
||||||
|
|
|
@ -27,10 +27,13 @@ filterTests(["smoke", "all"], () => {
|
||||||
it("updates a column on the table", () => {
|
it("updates a column on the table", () => {
|
||||||
cy.get(".title").click()
|
cy.get(".title").click()
|
||||||
cy.get(".spectrum-Table-editIcon > use").click()
|
cy.get(".spectrum-Table-editIcon > use").click()
|
||||||
cy.get("input").eq(1).type("updated", { force: true })
|
cy.get(".modal-inner-wrapper").within(() => {
|
||||||
|
|
||||||
|
cy.get("input").eq(0).type("updated", { force: true })
|
||||||
// Unset table display column
|
// Unset table display column
|
||||||
cy.get(".spectrum-Switch-input").eq(1).click()
|
cy.get(".spectrum-Switch-input").eq(1).click()
|
||||||
cy.contains("Save Column").click()
|
cy.contains("Save Column").click()
|
||||||
|
})
|
||||||
cy.contains("nameupdated ").should("contain", "nameupdated")
|
cy.contains("nameupdated ").should("contain", "nameupdated")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,43 +1,45 @@
|
||||||
import filterTests from "../../support/filterTests"
|
import filterTests from "../../support/filterTests"
|
||||||
|
|
||||||
filterTests(['smoke', 'all'], () => {
|
filterTests(["smoke", "all"], () => {
|
||||||
context("REST Datasource Testing", () => {
|
context("REST Datasource Testing", () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.login()
|
cy.login()
|
||||||
cy.createTestApp()
|
cy.createTestApp()
|
||||||
})
|
|
||||||
|
|
||||||
const datasource = "REST"
|
|
||||||
const restUrl = "https://api.openbrewerydb.org/breweries"
|
|
||||||
|
|
||||||
it("Should add REST data source with incorrect API", () => {
|
|
||||||
// Select REST data source
|
|
||||||
cy.selectExternalDatasource(datasource)
|
|
||||||
// Enter incorrect api & attempt to send query
|
|
||||||
cy.wait(500)
|
|
||||||
cy.get(".spectrum-Button").contains("Add query").click({ force: true })
|
|
||||||
cy.intercept('**/preview').as('queryError')
|
|
||||||
cy.get("input").clear().type("random text")
|
|
||||||
cy.get(".spectrum-Button").contains("Send").click({ force: true })
|
|
||||||
// Intercept Request after button click & apply assertions
|
|
||||||
cy.wait("@queryError")
|
|
||||||
cy.get("@queryError").its('response.body')
|
|
||||||
.should('have.property', 'message', 'Invalid URL: http://random text?')
|
|
||||||
cy.get("@queryError").its('response.body')
|
|
||||||
.should('have.property', 'status', 400)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should add and configure a REST datasource", () => {
|
|
||||||
// Select REST datasource and create query
|
|
||||||
cy.selectExternalDatasource(datasource)
|
|
||||||
cy.wait(500)
|
|
||||||
// createRestQuery confirms query creation
|
|
||||||
cy.createRestQuery("GET", restUrl)
|
|
||||||
// Confirm status code response within REST datasource
|
|
||||||
cy.get(".spectrum-FieldLabel")
|
|
||||||
.contains("Status")
|
|
||||||
.children()
|
|
||||||
.should('contain', 200)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const datasource = "REST"
|
||||||
|
const restUrl = "https://api.openbrewerydb.org/breweries"
|
||||||
|
|
||||||
|
it("Should add REST data source with incorrect API", () => {
|
||||||
|
// Select REST data source
|
||||||
|
cy.selectExternalDatasource(datasource)
|
||||||
|
// Enter incorrect api & attempt to send query
|
||||||
|
cy.wait(500)
|
||||||
|
cy.get(".spectrum-Button").contains("Add query").click({ force: true })
|
||||||
|
cy.intercept("**/preview").as("queryError")
|
||||||
|
cy.get("input").clear().type("random text")
|
||||||
|
cy.get(".spectrum-Button").contains("Send").click({ force: true })
|
||||||
|
// Intercept Request after button click & apply assertions
|
||||||
|
cy.wait("@queryError")
|
||||||
|
cy.get("@queryError")
|
||||||
|
.its("response.body")
|
||||||
|
.should("have.property", "message", "Invalid URL: http://random text?")
|
||||||
|
cy.get("@queryError")
|
||||||
|
.its("response.body")
|
||||||
|
.should("have.property", "status", 400)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should add and configure a REST datasource", () => {
|
||||||
|
// Select REST datasource and create query
|
||||||
|
cy.selectExternalDatasource(datasource)
|
||||||
|
cy.wait(500)
|
||||||
|
// createRestQuery confirms query creation
|
||||||
|
cy.createRestQuery("GET", restUrl, "/breweries")
|
||||||
|
// Confirm status code response within REST datasource
|
||||||
|
cy.get(".spectrum-FieldLabel")
|
||||||
|
.contains("Status")
|
||||||
|
.children()
|
||||||
|
.should("contain", 200)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import filterTests from "../support/filterTests"
|
import filterTests from "../support/filterTests"
|
||||||
|
|
||||||
filterTests(['smoke', 'all'], () => {
|
filterTests(["smoke", "all"], () => {
|
||||||
context("Query Level Transformers", () => {
|
context("Query Level Transformers", () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.login()
|
cy.login()
|
||||||
|
@ -9,54 +9,60 @@ filterTests(['smoke', 'all'], () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should write a transformer function", () => {
|
it("should write a transformer function", () => {
|
||||||
// Add REST datasource - contains API for breweries
|
// Add REST datasource - contains API for breweries
|
||||||
const datasource = "REST"
|
const datasource = "REST"
|
||||||
const restUrl = "https://api.openbrewerydb.org/breweries"
|
const restUrl = "https://api.openbrewerydb.org/breweries"
|
||||||
cy.selectExternalDatasource(datasource)
|
cy.selectExternalDatasource(datasource)
|
||||||
cy.createRestQuery("GET", restUrl)
|
cy.createRestQuery("GET", restUrl, "/breweries")
|
||||||
cy.get(".spectrum-Tabs-itemLabel").contains("Transformer").click()
|
cy.get(".spectrum-Tabs-itemLabel").contains("Transformer").click()
|
||||||
// Get Transformer Function from file
|
// Get Transformer Function from file
|
||||||
cy.readFile("cypress/support/queryLevelTransformerFunction.js").then((transformerFunction) => {
|
cy.readFile("cypress/support/queryLevelTransformerFunction.js").then(
|
||||||
|
transformerFunction => {
|
||||||
cy.get(".CodeMirror textarea")
|
cy.get(".CodeMirror textarea")
|
||||||
// Highlight current text and overwrite with file contents
|
// Highlight current text and overwrite with file contents
|
||||||
.type(Cypress.platform === 'darwin' ? '{cmd}a' : '{ctrl}a', { force: true })
|
.type(Cypress.platform === "darwin" ? "{cmd}a" : "{ctrl}a", {
|
||||||
.type(transformerFunction, { parseSpecialCharSequences: false })
|
force: true,
|
||||||
})
|
})
|
||||||
// Send Query
|
.type(transformerFunction, { parseSpecialCharSequences: false })
|
||||||
cy.intercept('**/queries/preview').as('query')
|
}
|
||||||
cy.get(".spectrum-Button").contains("Send").click({ force: true })
|
)
|
||||||
cy.wait("@query")
|
// Send Query
|
||||||
// Assert against Status Code, body, & body rows
|
cy.intercept("**/queries/preview").as("query")
|
||||||
cy.get("@query").its('response.statusCode')
|
cy.get(".spectrum-Button").contains("Send").click({ force: true })
|
||||||
.should('eq', 200)
|
cy.wait("@query")
|
||||||
cy.get("@query").its('response.body').should('not.be.empty')
|
// Assert against Status Code, body, & body rows
|
||||||
cy.get("@query").its('response.body.rows').should('not.be.empty')
|
cy.get("@query").its("response.statusCode").should("eq", 200)
|
||||||
})
|
cy.get("@query").its("response.body").should("not.be.empty")
|
||||||
|
cy.get("@query").its("response.body.rows").should("not.be.empty")
|
||||||
|
})
|
||||||
|
|
||||||
it("should add data to the previous query", () => {
|
it("should add data to the previous query", () => {
|
||||||
// Add REST datasource - contains API for breweries
|
// Add REST datasource - contains API for breweries
|
||||||
const datasource = "REST"
|
const datasource = "REST"
|
||||||
const restUrl = "https://api.openbrewerydb.org/breweries"
|
const restUrl = "https://api.openbrewerydb.org/breweries"
|
||||||
cy.selectExternalDatasource(datasource)
|
cy.selectExternalDatasource(datasource)
|
||||||
cy.createRestQuery("GET", restUrl)
|
cy.createRestQuery("GET", restUrl, "/breweries")
|
||||||
cy.get(".spectrum-Tabs-itemLabel").contains("Transformer").click()
|
cy.get(".spectrum-Tabs-itemLabel").contains("Transformer").click()
|
||||||
// Get Transformer Function with Data from file
|
// Get Transformer Function with Data from file
|
||||||
cy.readFile("cypress/support/queryLevelTransformerFunctionWithData.js").then((transformerFunction) => {
|
cy.readFile(
|
||||||
|
"cypress/support/queryLevelTransformerFunctionWithData.js"
|
||||||
|
).then(transformerFunction => {
|
||||||
//console.log(transformerFunction[1])
|
//console.log(transformerFunction[1])
|
||||||
cy.get(".CodeMirror textarea")
|
cy.get(".CodeMirror textarea")
|
||||||
// Highlight current text and overwrite with file contents
|
// Highlight current text and overwrite with file contents
|
||||||
.type(Cypress.platform === 'darwin' ? '{cmd}a' : '{ctrl}a', { force: true })
|
.type(Cypress.platform === "darwin" ? "{cmd}a" : "{ctrl}a", {
|
||||||
.type(transformerFunction, { parseSpecialCharSequences: false })
|
force: true,
|
||||||
|
})
|
||||||
|
.type(transformerFunction, { parseSpecialCharSequences: false })
|
||||||
})
|
})
|
||||||
// Send Query
|
// Send Query
|
||||||
cy.intercept('**/queries/preview').as('query')
|
cy.intercept("**/queries/preview").as("query")
|
||||||
cy.get(".spectrum-Button").contains("Send").click({ force: true })
|
cy.get(".spectrum-Button").contains("Send").click({ force: true })
|
||||||
cy.wait("@query")
|
cy.wait("@query")
|
||||||
// Assert against Status Code, body, & body rows
|
// Assert against Status Code, body, & body rows
|
||||||
cy.get("@query").its('response.statusCode')
|
cy.get("@query").its("response.statusCode").should("eq", 200)
|
||||||
.should('eq', 200)
|
cy.get("@query").its("response.body").should("not.be.empty")
|
||||||
cy.get("@query").its('response.body').should('not.be.empty')
|
cy.get("@query").its("response.body.rows").should("not.be.empty")
|
||||||
cy.get("@query").its('response.body.rows').should('not.be.empty')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should run an invalid query within the transformer section", () => {
|
it("should run an invalid query within the transformer section", () => {
|
||||||
|
@ -64,52 +70,70 @@ filterTests(['smoke', 'all'], () => {
|
||||||
const datasource = "REST"
|
const datasource = "REST"
|
||||||
const restUrl = "https://api.openbrewerydb.org/breweries"
|
const restUrl = "https://api.openbrewerydb.org/breweries"
|
||||||
cy.selectExternalDatasource(datasource)
|
cy.selectExternalDatasource(datasource)
|
||||||
cy.createRestQuery("GET", restUrl)
|
cy.createRestQuery("GET", restUrl, "/breweries")
|
||||||
cy.get(".spectrum-Tabs-itemLabel").contains("Transformer").click()
|
cy.get(".spectrum-Tabs-itemLabel").contains("Transformer").click()
|
||||||
// Clear the code box and add "test"
|
// Clear the code box and add "test"
|
||||||
cy.get(".CodeMirror textarea")
|
cy.get(".CodeMirror textarea")
|
||||||
.type(Cypress.platform === 'darwin' ? '{cmd}a' : '{ctrl}a', { force: true })
|
.type(Cypress.platform === "darwin" ? "{cmd}a" : "{ctrl}a", {
|
||||||
.type("test")
|
force: true,
|
||||||
|
})
|
||||||
|
.type("test")
|
||||||
// Run Query and intercept
|
// Run Query and intercept
|
||||||
cy.intercept('**/preview').as('queryError')
|
cy.intercept("**/preview").as("queryError")
|
||||||
cy.get(".spectrum-Button").contains("Send").click({ force: true })
|
cy.get(".spectrum-Button").contains("Send").click({ force: true })
|
||||||
cy.wait("@queryError")
|
cy.wait("@queryError")
|
||||||
cy.wait(500)
|
cy.wait(500)
|
||||||
// Assert against message and status for the query error
|
// Assert against message and status for the query error
|
||||||
cy.get("@queryError").its('response.body').should('have.property', 'message', "test is not defined")
|
cy.get("@queryError")
|
||||||
cy.get("@queryError").its('response.body').should('have.property', 'status', 400)
|
.its("response.body")
|
||||||
|
.should("have.property", "message", "test is not defined")
|
||||||
|
cy.get("@queryError")
|
||||||
|
.its("response.body")
|
||||||
|
.should("have.property", "status", 400)
|
||||||
})
|
})
|
||||||
|
|
||||||
xit("should run an invalid query via POST request", () => {
|
xit("should run an invalid query via POST request", () => {
|
||||||
// POST request with transformer as null
|
// POST request with transformer as null
|
||||||
cy.request({method: 'POST',
|
cy.request({
|
||||||
url: `${Cypress.config().baseUrl}/api/queries/`,
|
method: "POST",
|
||||||
body: {fields : {"headers":{},"queryString":null,"path":null},
|
url: `${Cypress.config().baseUrl}/api/queries/`,
|
||||||
parameters : [],
|
body: {
|
||||||
schema : {},
|
fields: { headers: {}, queryString: null, path: null },
|
||||||
name : "test",
|
parameters: [],
|
||||||
queryVerb : "read",
|
schema: {},
|
||||||
transformer : null,
|
name: "test",
|
||||||
datasourceId: "test"},
|
queryVerb: "read",
|
||||||
// Expected 400 error - Transformer must be a string
|
transformer: null,
|
||||||
failOnStatusCode: false}).then((response) => {
|
datasourceId: "test",
|
||||||
|
},
|
||||||
|
// Expected 400 error - Transformer must be a string
|
||||||
|
failOnStatusCode: false,
|
||||||
|
}).then(response => {
|
||||||
expect(response.status).to.equal(400)
|
expect(response.status).to.equal(400)
|
||||||
expect(response.body.message).to.include('Invalid body - "transformer" must be a string')
|
expect(response.body.message).to.include(
|
||||||
|
'Invalid body - "transformer" must be a string'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
xit("should run an empty query", () => {
|
xit("should run an empty query", () => {
|
||||||
// POST request with Transformer as an empty string
|
// POST request with Transformer as an empty string
|
||||||
cy.request({method: 'POST',
|
cy.request({
|
||||||
url: `${Cypress.config().baseUrl}/api/queries/preview`,
|
method: "POST",
|
||||||
body: {fields : {"headers":{},"queryString":null,"path":null},
|
url: `${Cypress.config().baseUrl}/api/queries/preview`,
|
||||||
queryVerb : "read",
|
body: {
|
||||||
transformer : "",
|
fields: { headers: {}, queryString: null, path: null },
|
||||||
datasourceId: "test"},
|
queryVerb: "read",
|
||||||
// Expected 400 error - Transformer is not allowed to be empty
|
transformer: "",
|
||||||
failOnStatusCode: false}).then((response) => {
|
datasourceId: "test",
|
||||||
|
},
|
||||||
|
// Expected 400 error - Transformer is not allowed to be empty
|
||||||
|
failOnStatusCode: false,
|
||||||
|
}).then(response => {
|
||||||
expect(response.status).to.equal(400)
|
expect(response.status).to.equal(400)
|
||||||
expect(response.body.message).to.include('Invalid body - "transformer" is not allowed to be empty')
|
expect(response.body.message).to.include(
|
||||||
|
'Invalid body - "transformer" is not allowed to be empty'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -39,7 +39,7 @@ Cypress.Commands.add("createApp", name => {
|
||||||
cy.get(".spectrum-Modal").within(() => {
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
cy.get("input").eq(0).type(name).should("have.value", name).blur()
|
cy.get("input").eq(0).type(name).should("have.value", name).blur()
|
||||||
cy.get(".spectrum-ButtonGroup").contains("Create app").click()
|
cy.get(".spectrum-ButtonGroup").contains("Create app").click()
|
||||||
cy.wait(5000)
|
cy.wait(10000)
|
||||||
})
|
})
|
||||||
cy.createTable("Cypress Tests", true)
|
cy.createTable("Cypress Tests", true)
|
||||||
})
|
})
|
||||||
|
@ -116,10 +116,10 @@ Cypress.Commands.add("createTestTableWithData", () => {
|
||||||
Cypress.Commands.add("createTable", (tableName, initialTable) => {
|
Cypress.Commands.add("createTable", (tableName, initialTable) => {
|
||||||
if (!initialTable) {
|
if (!initialTable) {
|
||||||
cy.navigateToDataSection()
|
cy.navigateToDataSection()
|
||||||
cy.get(".add-button").click()
|
cy.get(`[data-cy="new-table"]`).click()
|
||||||
}
|
}
|
||||||
cy.wait(7000)
|
cy.wait(5000)
|
||||||
cy.get(".spectrum-Modal")
|
cy.get(".spectrum-Dialog-grid")
|
||||||
.contains("Budibase DB")
|
.contains("Budibase DB")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -172,17 +172,19 @@ Cypress.Commands.add("addRow", values => {
|
||||||
|
|
||||||
Cypress.Commands.add("addRowMultiValue", values => {
|
Cypress.Commands.add("addRowMultiValue", values => {
|
||||||
cy.contains("Create row").click()
|
cy.contains("Create row").click()
|
||||||
cy.get(".spectrum-Form-itemField")
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
.click()
|
cy.get(".spectrum-Form-itemField")
|
||||||
.then(() => {
|
.click()
|
||||||
cy.get(".spectrum-Popover").within(() => {
|
.then(() => {
|
||||||
for (let i = 0; i < values.length; i++) {
|
cy.get(".spectrum-Popover").within(() => {
|
||||||
cy.get(".spectrum-Menu-item").eq(i).click()
|
for (let i = 0; i < values.length; i++) {
|
||||||
}
|
cy.get(".spectrum-Menu-item").eq(i).click()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
cy.get(".spectrum-Dialog-grid").click("top")
|
||||||
|
cy.get(".spectrum-ButtonGroup").contains("Create").click()
|
||||||
})
|
})
|
||||||
cy.get(".spectrum-Dialog-grid").click("top")
|
})
|
||||||
cy.get(".spectrum-ButtonGroup").contains("Create").click()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("createUser", email => {
|
Cypress.Commands.add("createUser", email => {
|
||||||
|
@ -435,7 +437,7 @@ Cypress.Commands.add("addDatasourceConfig", (datasource, skipFetch) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("createRestQuery", (method, restUrl) => {
|
Cypress.Commands.add("createRestQuery", (method, restUrl, queryPrettyName) => {
|
||||||
// addExternalDatasource should be called prior to this
|
// addExternalDatasource should be called prior to this
|
||||||
// Configures REST datasource & sends query
|
// Configures REST datasource & sends query
|
||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
|
@ -450,5 +452,5 @@ Cypress.Commands.add("createRestQuery", (method, restUrl) => {
|
||||||
cy.get(".spectrum-Button").contains("Save").click({ force: true })
|
cy.get(".spectrum-Button").contains("Save").click({ force: true })
|
||||||
cy.get(".hierarchy-items-container")
|
cy.get(".hierarchy-items-container")
|
||||||
.should("contain", method)
|
.should("contain", method)
|
||||||
.and("contain", restUrl)
|
.and("contain", queryPrettyName)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "1.0.76-alpha.5",
|
"version": "1.0.79-alpha.5",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
"cy:setup:ci": "node ./cypress/setup.js",
|
"cy:setup:ci": "node ./cypress/setup.js",
|
||||||
"cy:open": "cypress open",
|
"cy:open": "cypress open",
|
||||||
"cy:run": "cypress run",
|
"cy:run": "cypress run",
|
||||||
"cy:run:ci": "xvfb-run cypress run --headed --browser chrome --record",
|
"cy:run:ci": "xvfb-run cypress run --headed --browser chrome",
|
||||||
"cy:test": "start-server-and-test cy:setup http://localhost:4100/builder cy:run",
|
"cy:test": "start-server-and-test cy:setup http://localhost:4100/builder cy:run",
|
||||||
"cy:ci": "start-server-and-test cy:setup:ci http://localhost:4100/builder cy:run:ci",
|
"cy:ci": "start-server-and-test cy:setup:ci http://localhost:4100/builder cy:run:ci",
|
||||||
"cy:debug": "start-server-and-test cy:setup http://localhost:4100/builder cy:open",
|
"cy:debug": "start-server-and-test cy:setup http://localhost:4100/builder cy:open",
|
||||||
|
@ -65,10 +65,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.76-alpha.5",
|
"@budibase/bbui": "^1.0.79-alpha.5",
|
||||||
"@budibase/client": "^1.0.76-alpha.5",
|
"@budibase/client": "^1.0.79-alpha.5",
|
||||||
"@budibase/frontend-core": "^1.0.76-alpha.5",
|
"@budibase/frontend-core": "^1.0.79-alpha.5",
|
||||||
"@budibase/string-templates": "^1.0.76-alpha.5",
|
"@budibase/string-templates": "^1.0.79-alpha.5",
|
||||||
"@sentry/browser": "5.19.1",
|
"@sentry/browser": "5.19.1",
|
||||||
"@spectrum-css/page": "^3.0.1",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
|
|
|
@ -32,12 +32,14 @@ export const getBindableProperties = (asset, componentId) => {
|
||||||
const urlBindings = getUrlBindings(asset)
|
const urlBindings = getUrlBindings(asset)
|
||||||
const deviceBindings = getDeviceBindings()
|
const deviceBindings = getDeviceBindings()
|
||||||
const stateBindings = getStateBindings()
|
const stateBindings = getStateBindings()
|
||||||
|
const selectedRowsBindings = getSelectedRowsBindings(asset)
|
||||||
return [
|
return [
|
||||||
...contextBindings,
|
...contextBindings,
|
||||||
...urlBindings,
|
...urlBindings,
|
||||||
...stateBindings,
|
...stateBindings,
|
||||||
...userBindings,
|
...userBindings,
|
||||||
...deviceBindings,
|
...deviceBindings,
|
||||||
|
...selectedRowsBindings,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,6 +317,40 @@ const getDeviceBindings = () => {
|
||||||
return bindings
|
return bindings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all selected rows bindings for tables in the current asset.
|
||||||
|
*/
|
||||||
|
const getSelectedRowsBindings = asset => {
|
||||||
|
let bindings = []
|
||||||
|
if (get(store).clientFeatures?.rowSelection) {
|
||||||
|
// Add bindings for table components
|
||||||
|
let tables = findAllMatchingComponents(asset?.props, component =>
|
||||||
|
component._component.endsWith("table")
|
||||||
|
)
|
||||||
|
const safeState = makePropSafe("rowSelection")
|
||||||
|
bindings = bindings.concat(
|
||||||
|
tables.map(table => ({
|
||||||
|
type: "context",
|
||||||
|
runtimeBinding: `${safeState}.${makePropSafe(table._id)}`,
|
||||||
|
readableBinding: `${table._instanceName}.Selected rows`,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add bindings for table blocks
|
||||||
|
let tableBlocks = findAllMatchingComponents(asset?.props, component =>
|
||||||
|
component._component.endsWith("tableblock")
|
||||||
|
)
|
||||||
|
bindings = bindings.concat(
|
||||||
|
tableBlocks.map(block => ({
|
||||||
|
type: "context",
|
||||||
|
runtimeBinding: `${safeState}.${makePropSafe(block._id + "-table")}`,
|
||||||
|
readableBinding: `${block._instanceName}.Selected rows`,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return bindings
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all state bindings that are globally available.
|
* Gets all state bindings that are globally available.
|
||||||
*/
|
*/
|
||||||
|
@ -597,14 +633,9 @@ const buildFormSchema = component => {
|
||||||
* in the app.
|
* in the app.
|
||||||
*/
|
*/
|
||||||
export const getAllStateVariables = () => {
|
export const getAllStateVariables = () => {
|
||||||
// Get all component containing assets
|
|
||||||
let allAssets = []
|
|
||||||
allAssets = allAssets.concat(get(store).layouts || [])
|
|
||||||
allAssets = allAssets.concat(get(store).screens || [])
|
|
||||||
|
|
||||||
// Find all button action settings in all components
|
// Find all button action settings in all components
|
||||||
let eventSettings = []
|
let eventSettings = []
|
||||||
allAssets.forEach(asset => {
|
getAllAssets().forEach(asset => {
|
||||||
findAllMatchingComponents(asset.props, component => {
|
findAllMatchingComponents(asset.props, component => {
|
||||||
const settings = getComponentSettings(component._component)
|
const settings = getComponentSettings(component._component)
|
||||||
settings
|
settings
|
||||||
|
@ -635,6 +666,15 @@ export const getAllStateVariables = () => {
|
||||||
return Array.from(bindingSet)
|
return Array.from(bindingSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getAllAssets = () => {
|
||||||
|
// Get all component containing assets
|
||||||
|
let allAssets = []
|
||||||
|
allAssets = allAssets.concat(get(store).layouts || [])
|
||||||
|
allAssets = allAssets.concat(get(store).screens || [])
|
||||||
|
|
||||||
|
return allAssets
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recurses the input object to remove any instances of bindings.
|
* Recurses the input object to remove any instances of bindings.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -57,6 +57,7 @@ const automationActions = store => ({
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
save: async automation => {
|
save: async automation => {
|
||||||
const response = await API.updateAutomation(automation)
|
const response = await API.updateAutomation(automation)
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
|
@ -130,6 +131,12 @@ const automationActions = store => ({
|
||||||
name: block.name,
|
name: block.name,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
toggleFieldControl: value => {
|
||||||
|
store.update(state => {
|
||||||
|
state.selectedBlock.rowControl = value
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
},
|
||||||
deleteAutomationBlock: block => {
|
deleteAutomationBlock: block => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
const idx =
|
const idx =
|
||||||
|
|
|
@ -41,6 +41,7 @@ const INITIAL_FRONTEND_STATE = {
|
||||||
intelligentLoading: false,
|
intelligentLoading: false,
|
||||||
deviceAwareness: false,
|
deviceAwareness: false,
|
||||||
state: false,
|
state: false,
|
||||||
|
rowSelection: false,
|
||||||
customThemes: false,
|
customThemes: false,
|
||||||
devicePreview: false,
|
devicePreview: false,
|
||||||
messagePassing: false,
|
messagePassing: false,
|
||||||
|
|
|
@ -3,14 +3,8 @@
|
||||||
import Flowchart from "./FlowChart/FlowChart.svelte"
|
import Flowchart from "./FlowChart/FlowChart.svelte"
|
||||||
|
|
||||||
$: automation = $automationStore.selectedAutomation?.automation
|
$: automation = $automationStore.selectedAutomation?.automation
|
||||||
function onSelect(block) {
|
|
||||||
automationStore.update(state => {
|
|
||||||
state.selectedBlock = block
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if automation}
|
{#if automation}
|
||||||
<Flowchart {automation} {onSelect} />
|
<Flowchart {automation} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
|
|
||||||
export let automation
|
export let automation
|
||||||
export let onSelect
|
|
||||||
let testDataModal
|
let testDataModal
|
||||||
let blocks
|
let blocks
|
||||||
let confirmDeleteDialog
|
let confirmDeleteDialog
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<div class="subtitle">
|
<div class="subtitle">
|
||||||
<Heading size="S">{automation.name}</Heading>
|
<Heading size="S">{automation.name}</Heading>
|
||||||
<div style="display:flex;">
|
<div style="display:flex; align-items: center;">
|
||||||
<div class="iconPadding">
|
<div class="iconPadding">
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<Icon
|
<Icon
|
||||||
|
@ -72,7 +72,7 @@
|
||||||
animate:flip={{ duration: 500 }}
|
animate:flip={{ duration: 500 }}
|
||||||
in:fly|local={{ x: 500, duration: 1500 }}
|
in:fly|local={{ x: 500, duration: 1500 }}
|
||||||
>
|
>
|
||||||
<FlowItem {testDataModal} {onSelect} {block} />
|
<FlowItem {testDataModal} {block} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
Button,
|
Button,
|
||||||
StatusLight,
|
StatusLight,
|
||||||
ActionButton,
|
ActionButton,
|
||||||
|
Select,
|
||||||
notifications,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte"
|
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte"
|
||||||
|
@ -18,7 +19,6 @@
|
||||||
import ActionModal from "./ActionModal.svelte"
|
import ActionModal from "./ActionModal.svelte"
|
||||||
import { externalActions } from "./ExternalActions"
|
import { externalActions } from "./ExternalActions"
|
||||||
|
|
||||||
export let onSelect
|
|
||||||
export let block
|
export let block
|
||||||
export let testDataModal
|
export let testDataModal
|
||||||
let selected
|
let selected
|
||||||
|
@ -28,6 +28,10 @@
|
||||||
let setupToggled
|
let setupToggled
|
||||||
let blockComplete
|
let blockComplete
|
||||||
|
|
||||||
|
$: rowControl = $automationStore.selectedAutomation.automation.rowControl
|
||||||
|
$: showBindingPicker =
|
||||||
|
block.stepId === "CREATE_ROW" || block.stepId === "UPDATE_ROW"
|
||||||
|
|
||||||
$: testResult = $automationStore.selectedAutomation.testResults?.steps.filter(
|
$: testResult = $automationStore.selectedAutomation.testResults?.steps.filter(
|
||||||
step => (block.id ? step.id === block.id : step.stepId === block.stepId)
|
step => (block.id ? step.id === block.id : step.stepId === block.stepId)
|
||||||
)
|
)
|
||||||
|
@ -44,12 +48,6 @@
|
||||||
$automationStore.selectedAutomation?.automation?.definition?.steps.length +
|
$automationStore.selectedAutomation?.automation?.definition?.steps.length +
|
||||||
1
|
1
|
||||||
|
|
||||||
// Logic for hiding / showing the add button.first we check if it has a child
|
|
||||||
// then we check to see whether its inputs have been commpleted
|
|
||||||
$: disableAddButton = isTrigger
|
|
||||||
? $automationStore.selectedAutomation?.automation?.definition?.steps
|
|
||||||
.length > 0
|
|
||||||
: !isTrigger && steps.length - blockIdx > 1
|
|
||||||
$: hasCompletedInputs = Object.keys(
|
$: hasCompletedInputs = Object.keys(
|
||||||
block.schema?.inputs?.properties || {}
|
block.schema?.inputs?.properties || {}
|
||||||
).every(x => block?.inputs[x])
|
).every(x => block?.inputs[x])
|
||||||
|
@ -64,6 +62,26 @@
|
||||||
notifications.error("Error saving notification")
|
notifications.error("Error saving notification")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function toggleFieldControl(evt) {
|
||||||
|
onSelect(block)
|
||||||
|
let rowControl
|
||||||
|
if (evt.detail === "Use values") {
|
||||||
|
rowControl = false
|
||||||
|
} else {
|
||||||
|
rowControl = true
|
||||||
|
}
|
||||||
|
automationStore.actions.toggleFieldControl(rowControl)
|
||||||
|
automationStore.actions.save(
|
||||||
|
$automationStore.selectedAutomation?.automation
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSelect(block) {
|
||||||
|
await automationStore.update(state => {
|
||||||
|
state.selectedBlock = block
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -126,15 +144,33 @@
|
||||||
<Layout noPadding gap="S">
|
<Layout noPadding gap="S">
|
||||||
<div class="splitHeader">
|
<div class="splitHeader">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
on:click={() => (setupToggled = !setupToggled)}
|
on:click={() => {
|
||||||
|
onSelect(block)
|
||||||
|
setupToggled = !setupToggled
|
||||||
|
}}
|
||||||
quiet
|
quiet
|
||||||
icon={setupToggled ? "ChevronDown" : "ChevronRight"}
|
icon={setupToggled ? "ChevronDown" : "ChevronRight"}
|
||||||
>
|
>
|
||||||
<Detail size="S">Setup</Detail>
|
<Detail size="S">Setup</Detail>
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
{#if !isTrigger}
|
{#if !isTrigger}
|
||||||
<div on:click={() => deleteStep()}>
|
<div class="block-options">
|
||||||
<Icon name="DeleteOutline" />
|
{#if showBindingPicker}
|
||||||
|
<div>
|
||||||
|
<Select
|
||||||
|
on:change={toggleFieldControl}
|
||||||
|
quiet
|
||||||
|
defaultValue="Use values"
|
||||||
|
autoWidth
|
||||||
|
value={rowControl ? "Use bindings" : "Use values"}
|
||||||
|
options={["Use values", "Use bindings"]}
|
||||||
|
placeholder={null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="delete-padding" on:click={() => deleteStep()}>
|
||||||
|
<Icon name="DeleteOutline" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -180,6 +216,13 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.delete-padding {
|
||||||
|
padding-left: 30px;
|
||||||
|
}
|
||||||
|
.block-options {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
.center-items {
|
.center-items {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -227,6 +227,7 @@
|
||||||
/>
|
/>
|
||||||
{:else if value.customType === "row"}
|
{:else if value.customType === "row"}
|
||||||
<RowSelector
|
<RowSelector
|
||||||
|
{block}
|
||||||
value={inputData[key]}
|
value={inputData[key]}
|
||||||
on:change={e => onChange(e, key)}
|
on:change={e => onChange(e, key)}
|
||||||
{bindings}
|
{bindings}
|
||||||
|
|
|
@ -1,26 +1,31 @@
|
||||||
<script>
|
<script>
|
||||||
import { tables } from "stores/backend"
|
import { tables } from "stores/backend"
|
||||||
import {
|
import { Select } from "@budibase/bbui"
|
||||||
Select,
|
|
||||||
Toggle,
|
|
||||||
DatePicker,
|
|
||||||
Multiselect,
|
|
||||||
TextArea,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte"
|
import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte"
|
||||||
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
|
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
|
|
||||||
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
|
|
||||||
import { automationStore } from "builderStore"
|
import { automationStore } from "builderStore"
|
||||||
|
import RowSelectorTypes from "./RowSelectorTypes.svelte"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
export let bindings
|
export let bindings
|
||||||
|
export let block
|
||||||
|
|
||||||
let table
|
let table
|
||||||
let schemaFields
|
let schemaFields
|
||||||
|
|
||||||
|
let placeholders = {
|
||||||
|
number: 10,
|
||||||
|
boolean: "true",
|
||||||
|
datetime: "2022-02-16T12:00:00.000Z ",
|
||||||
|
options: "1",
|
||||||
|
array: "1 2 3 4",
|
||||||
|
link: "ro_ta_123_456",
|
||||||
|
longform: "long form text",
|
||||||
|
}
|
||||||
|
$: rowControl = block.rowControl
|
||||||
$: {
|
$: {
|
||||||
table = $tables.list.find(table => table._id === value?.tableId)
|
table = $tables.list.find(table => table._id === value?.tableId)
|
||||||
schemaFields = Object.entries(table?.schema ?? {})
|
schemaFields = Object.entries(table?.schema ?? {})
|
||||||
|
@ -37,18 +42,48 @@
|
||||||
dispatch("change", value)
|
dispatch("change", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChange = (e, field) => {
|
const coerce = (value, type) => {
|
||||||
value[field] = e.detail
|
if (type === "boolean") {
|
||||||
|
if (typeof value === "boolean") {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return value === "true"
|
||||||
|
}
|
||||||
|
if (type === "number") {
|
||||||
|
if (typeof value === "number") {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return Number(value)
|
||||||
|
}
|
||||||
|
if (type === "options") {
|
||||||
|
return [value]
|
||||||
|
}
|
||||||
|
if (type === "array") {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return value.split(",").map(x => x.trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "link") {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
return [value]
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChange = (e, field, type) => {
|
||||||
|
value[field] = coerce(e.detail, type)
|
||||||
dispatch("change", value)
|
dispatch("change", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure any nullish tableId values get set to empty string so
|
// Ensure any nullish tableId values get set to empty string so
|
||||||
// that the select works
|
// that the select works
|
||||||
$: if (value?.tableId == null) value = { tableId: "" }
|
$: if (value?.tableId == null) value = { tableId: "" }
|
||||||
|
|
||||||
function schemaHasOptions(schema) {
|
|
||||||
return !!schema.constraints?.inclusion?.length
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
|
@ -62,55 +97,46 @@
|
||||||
<div class="schema-fields">
|
<div class="schema-fields">
|
||||||
{#each schemaFields as [field, schema]}
|
{#each schemaFields as [field, schema]}
|
||||||
{#if !schema.autocolumn}
|
{#if !schema.autocolumn}
|
||||||
{#if schemaHasOptions(schema) && schema.type !== "array"}
|
{#if schema.type !== "attachment"}
|
||||||
<Select
|
|
||||||
on:change={e => onChange(e, field)}
|
|
||||||
label={field}
|
|
||||||
value={value[field]}
|
|
||||||
options={schema.constraints.inclusion}
|
|
||||||
/>
|
|
||||||
{:else if schema.type === "datetime"}
|
|
||||||
<DatePicker
|
|
||||||
label={field}
|
|
||||||
value={value[field]}
|
|
||||||
on:change={e => onChange(e, field)}
|
|
||||||
/>
|
|
||||||
{:else if schema.type === "boolean"}
|
|
||||||
<Toggle
|
|
||||||
text={field}
|
|
||||||
value={value[field]}
|
|
||||||
on:change={e => onChange(e, field)}
|
|
||||||
/>
|
|
||||||
{:else if schema.type === "array"}
|
|
||||||
<Multiselect
|
|
||||||
bind:value={value[field]}
|
|
||||||
label={field}
|
|
||||||
options={schema.constraints.inclusion}
|
|
||||||
/>
|
|
||||||
{:else if schema.type === "longform"}
|
|
||||||
<TextArea label={field} bind:value={value[field]} />
|
|
||||||
{:else if schema.type === "link"}
|
|
||||||
<LinkedRowSelector bind:linkedRows={value[field]} {schema} />
|
|
||||||
{:else if schema.type === "string" || schema.type === "number"}
|
|
||||||
{#if $automationStore.selectedAutomation.automation.testData}
|
{#if $automationStore.selectedAutomation.automation.testData}
|
||||||
<ModalBindableInput
|
{#if !rowControl}
|
||||||
value={value[field]}
|
<RowSelectorTypes
|
||||||
panel={AutomationBindingPanel}
|
{field}
|
||||||
label={field}
|
{schema}
|
||||||
type={value.customType}
|
{bindings}
|
||||||
on:change={e => onChange(e, field)}
|
{value}
|
||||||
{bindings}
|
{onChange}
|
||||||
/>
|
/>
|
||||||
|
{:else}
|
||||||
|
<DrawerBindableInput
|
||||||
|
placeholder={placeholders[schema.type]}
|
||||||
|
panel={AutomationBindingPanel}
|
||||||
|
value={Array.isArray(value[field])
|
||||||
|
? value[field].join(" ")
|
||||||
|
: value[field]}
|
||||||
|
on:change={e => onChange(e, field, schema.type)}
|
||||||
|
label={field}
|
||||||
|
type="string"
|
||||||
|
{bindings}
|
||||||
|
fillWidth={true}
|
||||||
|
allowJS={true}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{:else if !rowControl}
|
||||||
|
<RowSelectorTypes {field} {schema} {bindings} {value} {onChange} />
|
||||||
{:else}
|
{:else}
|
||||||
<DrawerBindableInput
|
<DrawerBindableInput
|
||||||
|
placeholder={placeholders[schema.type]}
|
||||||
panel={AutomationBindingPanel}
|
panel={AutomationBindingPanel}
|
||||||
value={value[field]}
|
value={Array.isArray(value[field])
|
||||||
on:change={e => onChange(e, field)}
|
? value[field].join(" ")
|
||||||
|
: value[field]}
|
||||||
|
on:change={e => onChange(e, field, schema.type)}
|
||||||
label={field}
|
label={field}
|
||||||
type="string"
|
type="string"
|
||||||
{bindings}
|
{bindings}
|
||||||
fillWidth={true}
|
fillWidth={true}
|
||||||
allowJS={false}
|
allowJS={true}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
Toggle,
|
||||||
|
DatePicker,
|
||||||
|
Multiselect,
|
||||||
|
TextArea,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
|
||||||
|
import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte"
|
||||||
|
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
|
||||||
|
|
||||||
|
export let onChange
|
||||||
|
export let field
|
||||||
|
export let schema
|
||||||
|
export let value
|
||||||
|
export let bindings
|
||||||
|
|
||||||
|
function schemaHasOptions(schema) {
|
||||||
|
return !!schema.constraints?.inclusion?.length
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if schemaHasOptions(schema) && schema.type !== "array"}
|
||||||
|
<Select
|
||||||
|
on:change={e => onChange(e, field)}
|
||||||
|
label={field}
|
||||||
|
value={value[field]}
|
||||||
|
options={schema.constraints.inclusion}
|
||||||
|
/>
|
||||||
|
{:else if schema.type === "datetime"}
|
||||||
|
<DatePicker
|
||||||
|
label={field}
|
||||||
|
value={value[field]}
|
||||||
|
on:change={e => onChange(e, field)}
|
||||||
|
/>
|
||||||
|
{:else if schema.type === "boolean"}
|
||||||
|
<Toggle
|
||||||
|
text={field}
|
||||||
|
value={value[field]}
|
||||||
|
on:change={e => onChange(e, field)}
|
||||||
|
/>
|
||||||
|
{:else if schema.type === "array"}
|
||||||
|
<Multiselect
|
||||||
|
bind:value={value[field]}
|
||||||
|
label={field}
|
||||||
|
options={schema.constraints.inclusion}
|
||||||
|
/>
|
||||||
|
{:else if schema.type === "longform"}
|
||||||
|
<TextArea label={field} bind:value={value[field]} />
|
||||||
|
{:else if schema.type === "link"}
|
||||||
|
<LinkedRowSelector bind:linkedRows={value[field]} {schema} />
|
||||||
|
{:else if schema.type === "string" || schema.type === "number"}
|
||||||
|
<DrawerBindableInput
|
||||||
|
panel={AutomationBindingPanel}
|
||||||
|
value={value[field]}
|
||||||
|
on:change={e => onChange(e, field)}
|
||||||
|
label={field}
|
||||||
|
type="string"
|
||||||
|
{bindings}
|
||||||
|
fillWidth={true}
|
||||||
|
allowJS={true}
|
||||||
|
/>
|
||||||
|
{/if}
|
|
@ -57,7 +57,8 @@
|
||||||
{data}
|
{data}
|
||||||
{loading}
|
{loading}
|
||||||
{type}
|
{type}
|
||||||
allowEditing={!view?.calculation}
|
allowEditing={false}
|
||||||
|
rowCount={10}
|
||||||
bind:hideAutocolumns
|
bind:hideAutocolumns
|
||||||
>
|
>
|
||||||
<ViewFilterButton {view} />
|
<ViewFilterButton {view} />
|
||||||
|
|
|
@ -8,7 +8,11 @@
|
||||||
import EditQueryPopover from "./popovers/EditQueryPopover.svelte"
|
import EditQueryPopover from "./popovers/EditQueryPopover.svelte"
|
||||||
import NavItem from "components/common/NavItem.svelte"
|
import NavItem from "components/common/NavItem.svelte"
|
||||||
import TableNavigator from "components/backend/TableNavigator/TableNavigator.svelte"
|
import TableNavigator from "components/backend/TableNavigator/TableNavigator.svelte"
|
||||||
import { customQueryIconText, customQueryIconColor } from "helpers/data/utils"
|
import {
|
||||||
|
customQueryIconText,
|
||||||
|
customQueryIconColor,
|
||||||
|
customQueryText,
|
||||||
|
} from "helpers/data/utils"
|
||||||
import ICONS from "./icons"
|
import ICONS from "./icons"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
|
|
||||||
|
@ -137,7 +141,7 @@
|
||||||
icon="SQLQuery"
|
icon="SQLQuery"
|
||||||
iconText={customQueryIconText(datasource, query)}
|
iconText={customQueryIconText(datasource, query)}
|
||||||
iconColor={customQueryIconColor(datasource, query)}
|
iconColor={customQueryIconColor(datasource, query)}
|
||||||
text={query.name}
|
text={customQueryText(datasource, query)}
|
||||||
opened={$queries.selected === query._id}
|
opened={$queries.selected === query._id}
|
||||||
selected={$queries.selected === query._id}
|
selected={$queries.selected === query._id}
|
||||||
on:click={() => onClickQuery(query)}
|
on:click={() => onClickQuery(query)}
|
||||||
|
|
|
@ -30,8 +30,8 @@ export function breakQueryString(qs) {
|
||||||
const params = qs.split("&")
|
const params = qs.split("&")
|
||||||
let paramObj = {}
|
let paramObj = {}
|
||||||
for (let param of params) {
|
for (let param of params) {
|
||||||
const [key, value] = param.split("=")
|
const split = param.split("=")
|
||||||
paramObj[key] = value
|
paramObj[split[0]] = split.slice(1).join("=")
|
||||||
}
|
}
|
||||||
return paramObj
|
return paramObj
|
||||||
}
|
}
|
||||||
|
@ -109,6 +109,36 @@ export function customQueryIconColor(datasource, query) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function customQueryText(datasource, query) {
|
||||||
|
if (!query.name || datasource.source !== IntegrationTypes.REST) {
|
||||||
|
return query.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove protocol
|
||||||
|
let name = query.name
|
||||||
|
if (name.includes("//")) {
|
||||||
|
name = name.split("//")[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no path, return the full name
|
||||||
|
if (!name.includes("/")) {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove trailing slash
|
||||||
|
if (name.endsWith("/")) {
|
||||||
|
name = name.slice(0, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only use path
|
||||||
|
const split = name.split("/")
|
||||||
|
if (split[1]) {
|
||||||
|
return `/${split.slice(1).join("/")}`
|
||||||
|
} else {
|
||||||
|
return split[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function flipHeaderState(headersActivity) {
|
export function flipHeaderState(headersActivity) {
|
||||||
if (!headersActivity) {
|
if (!headersActivity) {
|
||||||
return {}
|
return {}
|
||||||
|
|
|
@ -60,7 +60,7 @@ export function createQueriesStore() {
|
||||||
})
|
})
|
||||||
return savedQuery
|
return savedQuery
|
||||||
},
|
},
|
||||||
import: async (data, datasourceId) => {
|
import: async ({ data, datasourceId }) => {
|
||||||
return await API.importQueries({
|
return await API.importQueries({
|
||||||
datasourceId,
|
datasourceId,
|
||||||
data,
|
data,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "1.0.76-alpha.5",
|
"version": "1.0.79-alpha.5",
|
||||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
"state": true,
|
"state": true,
|
||||||
"customThemes": true,
|
"customThemes": true,
|
||||||
"devicePreview": true,
|
"devicePreview": true,
|
||||||
"messagePassing": true
|
"messagePassing": true,
|
||||||
|
"rowSelection": true
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
"name": "Layout",
|
"name": "Layout",
|
||||||
|
@ -2714,6 +2715,13 @@
|
||||||
"key": "showAutoColumns",
|
"key": "showAutoColumns",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Allow row selection",
|
||||||
|
"key": "allowSelectRows",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Link table rows",
|
"label": "Link table rows",
|
||||||
|
@ -2973,6 +2981,11 @@
|
||||||
"label": "Show auto columns",
|
"label": "Show auto columns",
|
||||||
"key": "showAutoColumns"
|
"key": "showAutoColumns"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Allow row selection",
|
||||||
|
"key": "allowSelectRows"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Link table rows",
|
"label": "Link table rows",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "1.0.76-alpha.5",
|
"version": "1.0.79-alpha.5",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"module": "dist/budibase-client.js",
|
"module": "dist/budibase-client.js",
|
||||||
"main": "dist/budibase-client.js",
|
"main": "dist/budibase-client.js",
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
"dev:builder": "rollup -cw"
|
"dev:builder": "rollup -cw"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.76-alpha.5",
|
"@budibase/bbui": "^1.0.79-alpha.5",
|
||||||
"@budibase/frontend-core": "^1.0.76-alpha.5",
|
"@budibase/frontend-core": "^1.0.79-alpha.5",
|
||||||
"@budibase/string-templates": "^1.0.76-alpha.5",
|
"@budibase/string-templates": "^1.0.79-alpha.5",
|
||||||
"@spectrum-css/button": "^3.0.3",
|
"@spectrum-css/button": "^3.0.3",
|
||||||
"@spectrum-css/card": "^3.0.3",
|
"@spectrum-css/card": "^3.0.3",
|
||||||
"@spectrum-css/divider": "^1.0.3",
|
"@spectrum-css/divider": "^1.0.3",
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
import UserBindingsProvider from "components/context/UserBindingsProvider.svelte"
|
import UserBindingsProvider from "components/context/UserBindingsProvider.svelte"
|
||||||
import DeviceBindingsProvider from "components/context/DeviceBindingsProvider.svelte"
|
import DeviceBindingsProvider from "components/context/DeviceBindingsProvider.svelte"
|
||||||
import StateBindingsProvider from "components/context/StateBindingsProvider.svelte"
|
import StateBindingsProvider from "components/context/StateBindingsProvider.svelte"
|
||||||
|
import RowSelectionProvider from "components/context/RowSelectionProvider.svelte"
|
||||||
import SettingsBar from "components/preview/SettingsBar.svelte"
|
import SettingsBar from "components/preview/SettingsBar.svelte"
|
||||||
import SelectionIndicator from "components/preview/SelectionIndicator.svelte"
|
import SelectionIndicator from "components/preview/SelectionIndicator.svelte"
|
||||||
import HoverIndicator from "components/preview/HoverIndicator.svelte"
|
import HoverIndicator from "components/preview/HoverIndicator.svelte"
|
||||||
|
@ -90,59 +91,61 @@
|
||||||
<UserBindingsProvider>
|
<UserBindingsProvider>
|
||||||
<DeviceBindingsProvider>
|
<DeviceBindingsProvider>
|
||||||
<StateBindingsProvider>
|
<StateBindingsProvider>
|
||||||
<!-- Settings bar can be rendered outside of device preview -->
|
<RowSelectionProvider>
|
||||||
<!-- Key block needs to be outside the if statement or it breaks -->
|
<!-- Settings bar can be rendered outside of device preview -->
|
||||||
{#key $builderStore.selectedComponentId}
|
<!-- Key block needs to be outside the if statement or it breaks -->
|
||||||
{#if $builderStore.inBuilder}
|
{#key $builderStore.selectedComponentId}
|
||||||
<SettingsBar />
|
{#if $builderStore.inBuilder}
|
||||||
{/if}
|
<SettingsBar />
|
||||||
{/key}
|
{/if}
|
||||||
|
{/key}
|
||||||
|
|
||||||
<!-- Clip boundary for selection indicators -->
|
<!-- Clip boundary for selection indicators -->
|
||||||
<div
|
<div
|
||||||
id="clip-root"
|
id="clip-root"
|
||||||
class:preview={$builderStore.inBuilder}
|
class:preview={$builderStore.inBuilder}
|
||||||
class:tablet-preview={$builderStore.previewDevice === "tablet"}
|
class:tablet-preview={$builderStore.previewDevice === "tablet"}
|
||||||
class:mobile-preview={$builderStore.previewDevice === "mobile"}
|
class:mobile-preview={$builderStore.previewDevice === "mobile"}
|
||||||
>
|
>
|
||||||
<!-- Actual app -->
|
<!-- Actual app -->
|
||||||
<div id="app-root">
|
<div id="app-root">
|
||||||
<CustomThemeWrapper>
|
<CustomThemeWrapper>
|
||||||
{#key `${$screenStore.activeLayout._id}-${$builderStore.previewType}`}
|
{#key `${$screenStore.activeLayout._id}-${$builderStore.previewType}`}
|
||||||
<Component
|
<Component
|
||||||
isLayout
|
isLayout
|
||||||
instance={$screenStore.activeLayout.props}
|
instance={$screenStore.activeLayout.props}
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Flatpickr needs to be inside the theme wrapper.
|
Flatpickr needs to be inside the theme wrapper.
|
||||||
It also needs its own container because otherwise it hijacks
|
It also needs its own container because otherwise it hijacks
|
||||||
key events on the whole page. It is painful to work with.
|
key events on the whole page. It is painful to work with.
|
||||||
-->
|
-->
|
||||||
<div id="flatpickr-root" />
|
<div id="flatpickr-root" />
|
||||||
|
|
||||||
<!-- Modal container to ensure they sit on top -->
|
<!-- Modal container to ensure they sit on top -->
|
||||||
<div class="modal-container" />
|
<div class="modal-container" />
|
||||||
|
|
||||||
<!-- Layers on top of app -->
|
<!-- Layers on top of app -->
|
||||||
<NotificationDisplay />
|
<NotificationDisplay />
|
||||||
<ConfirmationDisplay />
|
<ConfirmationDisplay />
|
||||||
<PeekScreenDisplay />
|
<PeekScreenDisplay />
|
||||||
</CustomThemeWrapper>
|
</CustomThemeWrapper>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Selection indicators should be bounded by device -->
|
<!-- Selection indicators should be bounded by device -->
|
||||||
<!--
|
<!--
|
||||||
We don't want to key these by componentID as they control their own
|
We don't want to key these by componentID as they control their own
|
||||||
re-mounting to avoid flashes.
|
re-mounting to avoid flashes.
|
||||||
-->
|
-->
|
||||||
{#if $builderStore.inBuilder}
|
{#if $builderStore.inBuilder}
|
||||||
<SelectionIndicator />
|
<SelectionIndicator />
|
||||||
<HoverIndicator />
|
<HoverIndicator />
|
||||||
<DNDHandler />
|
<DNDHandler />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</RowSelectionProvider>
|
||||||
</StateBindingsProvider>
|
</StateBindingsProvider>
|
||||||
</DeviceBindingsProvider>
|
</DeviceBindingsProvider>
|
||||||
</UserBindingsProvider>
|
</UserBindingsProvider>
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
export let quiet
|
export let quiet
|
||||||
export let compact
|
export let compact
|
||||||
export let size
|
export let size
|
||||||
|
export let allowSelectRows
|
||||||
export let linkRows
|
export let linkRows
|
||||||
export let linkURL
|
export let linkURL
|
||||||
export let linkColumn
|
export let linkColumn
|
||||||
|
@ -157,6 +158,7 @@
|
||||||
>
|
>
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
type="table"
|
type="table"
|
||||||
|
context="table"
|
||||||
props={{
|
props={{
|
||||||
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
|
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
|
||||||
columns: tableColumns,
|
columns: tableColumns,
|
||||||
|
@ -164,6 +166,7 @@
|
||||||
rowCount,
|
rowCount,
|
||||||
quiet,
|
quiet,
|
||||||
compact,
|
compact,
|
||||||
|
allowSelectRows,
|
||||||
size,
|
size,
|
||||||
linkRows,
|
linkRows,
|
||||||
linkURL,
|
linkURL,
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import { Table } from "@budibase/bbui"
|
import { Table } from "@budibase/bbui"
|
||||||
import SlotRenderer from "./SlotRenderer.svelte"
|
import SlotRenderer from "./SlotRenderer.svelte"
|
||||||
import { UnsortableTypes } from "../../../constants"
|
import { UnsortableTypes } from "../../../constants"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
export let dataProvider
|
export let dataProvider
|
||||||
export let columns
|
export let columns
|
||||||
|
@ -14,10 +15,12 @@
|
||||||
export let linkURL
|
export let linkURL
|
||||||
export let linkColumn
|
export let linkColumn
|
||||||
export let linkPeek
|
export let linkPeek
|
||||||
|
export let allowSelectRows
|
||||||
export let compact
|
export let compact
|
||||||
|
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
const { styleable, getAction, ActionTypes, routeStore } = getContext("sdk")
|
const { styleable, getAction, ActionTypes, routeStore, rowSelectionStore } =
|
||||||
|
getContext("sdk")
|
||||||
const customColumnKey = `custom-${Math.random()}`
|
const customColumnKey = `custom-${Math.random()}`
|
||||||
const customRenderers = [
|
const customRenderers = [
|
||||||
{
|
{
|
||||||
|
@ -25,7 +28,7 @@
|
||||||
component: SlotRenderer,
|
component: SlotRenderer,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
let selectedRows = []
|
||||||
$: hasChildren = $component.children
|
$: hasChildren = $component.children
|
||||||
$: loading = dataProvider?.loading ?? false
|
$: loading = dataProvider?.loading ?? false
|
||||||
$: data = dataProvider?.rows || []
|
$: data = dataProvider?.rows || []
|
||||||
|
@ -36,6 +39,12 @@
|
||||||
dataProvider?.id,
|
dataProvider?.id,
|
||||||
ActionTypes.SetDataProviderSorting
|
ActionTypes.SetDataProviderSorting
|
||||||
)
|
)
|
||||||
|
$: {
|
||||||
|
rowSelectionStore.actions.updateSelection(
|
||||||
|
$component.id,
|
||||||
|
selectedRows.map(row => row._id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const getFields = (schema, customColumns, showAutoColumns) => {
|
const getFields = (schema, customColumns, showAutoColumns) => {
|
||||||
// Check for an invalid column selection
|
// Check for an invalid column selection
|
||||||
|
@ -117,6 +126,10 @@
|
||||||
const split = linkURL.split("/:")
|
const split = linkURL.split("/:")
|
||||||
routeStore.actions.navigate(`${split[0]}/${id}`, linkPeek)
|
routeStore.actions.navigate(`${split[0]}/${id}`, linkPeek)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
rowSelectionStore.actions.updateSelection($component.id, [])
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div use:styleable={$component.styles} class={size}>
|
<div use:styleable={$component.styles} class={size}>
|
||||||
|
@ -128,7 +141,8 @@
|
||||||
{quiet}
|
{quiet}
|
||||||
{compact}
|
{compact}
|
||||||
{customRenderers}
|
{customRenderers}
|
||||||
allowSelectRows={false}
|
allowSelectRows={!!allowSelectRows}
|
||||||
|
bind:selectedRows
|
||||||
allowEditRows={false}
|
allowEditRows={false}
|
||||||
allowEditColumns={false}
|
allowEditColumns={false}
|
||||||
showAutoColumns={true}
|
showAutoColumns={true}
|
||||||
|
@ -139,10 +153,19 @@
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</Table>
|
</Table>
|
||||||
|
{#if allowSelectRows && selectedRows.length}
|
||||||
|
<div class="row-count">
|
||||||
|
{selectedRows.length} row{selectedRows.length === 1 ? "" : "s"} selected
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
div {
|
div {
|
||||||
background-color: var(--spectrum-alias-background-color-secondary);
|
background-color: var(--spectrum-alias-background-color-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.row-count {
|
||||||
|
margin-top: var(--spacing-l);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<script>
|
||||||
|
import Provider from "./Provider.svelte"
|
||||||
|
import { rowSelectionStore } from "stores"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Provider key="rowSelection" data={$rowSelectionStore}>
|
||||||
|
<slot />
|
||||||
|
</Provider>
|
|
@ -6,6 +6,7 @@ import {
|
||||||
screenStore,
|
screenStore,
|
||||||
builderStore,
|
builderStore,
|
||||||
uploadStore,
|
uploadStore,
|
||||||
|
rowSelectionStore,
|
||||||
} from "stores"
|
} from "stores"
|
||||||
import { styleable } from "utils/styleable"
|
import { styleable } from "utils/styleable"
|
||||||
import { linkable } from "utils/linkable"
|
import { linkable } from "utils/linkable"
|
||||||
|
@ -19,6 +20,7 @@ export default {
|
||||||
authStore,
|
authStore,
|
||||||
notificationStore,
|
notificationStore,
|
||||||
routeStore,
|
routeStore,
|
||||||
|
rowSelectionStore,
|
||||||
screenStore,
|
screenStore,
|
||||||
builderStore,
|
builderStore,
|
||||||
uploadStore,
|
uploadStore,
|
||||||
|
|
|
@ -10,7 +10,7 @@ export { peekStore } from "./peek"
|
||||||
export { stateStore } from "./state"
|
export { stateStore } from "./state"
|
||||||
export { themeStore } from "./theme"
|
export { themeStore } from "./theme"
|
||||||
export { uploadStore } from "./uploads.js"
|
export { uploadStore } from "./uploads.js"
|
||||||
|
export { rowSelectionStore } from "./rowSelection.js"
|
||||||
// Context stores are layered and duplicated, so it is not a singleton
|
// Context stores are layered and duplicated, so it is not a singleton
|
||||||
export { createContextStore } from "./context"
|
export { createContextStore } from "./context"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { writable } from "svelte/store"
|
||||||
|
|
||||||
|
const createRowSelectionStore = () => {
|
||||||
|
const store = writable({})
|
||||||
|
|
||||||
|
function updateSelection(componentId, selectedRows) {
|
||||||
|
store.update(state => {
|
||||||
|
state[componentId] = [...selectedRows]
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe: store.subscribe,
|
||||||
|
set: store.set,
|
||||||
|
actions: {
|
||||||
|
updateSelection,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const rowSelectionStore = createRowSelectionStore()
|
|
@ -127,12 +127,16 @@ const queryExecutionHandler = async action => {
|
||||||
// Trigger a notification and invalidate the datasource as long as this
|
// Trigger a notification and invalidate the datasource as long as this
|
||||||
// was not a readable query
|
// was not a readable query
|
||||||
if (!query.readable) {
|
if (!query.readable) {
|
||||||
API.notifications.error.success("Query executed successfully")
|
notificationStore.actions.success("Query executed successfully")
|
||||||
await dataSourceStore.actions.invalidateDataSource(query.datasourceId)
|
await dataSourceStore.actions.invalidateDataSource(query.datasourceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { result }
|
return { result }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
notificationStore.actions.error(
|
||||||
|
"An error occurred while executing the query"
|
||||||
|
)
|
||||||
|
|
||||||
// Abort next actions
|
// Abort next actions
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/frontend-core",
|
"name": "@budibase/frontend-core",
|
||||||
"version": "1.0.76-alpha.5",
|
"version": "1.0.79-alpha.5",
|
||||||
"description": "Budibase frontend core libraries used in builder and client",
|
"description": "Budibase frontend core libraries used in builder and client",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.76-alpha.5",
|
"@budibase/bbui": "^1.0.79-alpha.5",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"svelte": "^3.46.2"
|
"svelte": "^3.46.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "1.0.76-alpha.3",
|
"version": "1.0.79-alpha.5",
|
||||||
"description": "Budibase Web Server",
|
"description": "Budibase Web Server",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -74,9 +74,9 @@
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apidevtools/swagger-parser": "^10.0.3",
|
"@apidevtools/swagger-parser": "^10.0.3",
|
||||||
"@budibase/backend-core": "^1.0.76-alpha.5",
|
"@budibase/backend-core": "^1.0.79-alpha.5",
|
||||||
"@budibase/client": "^1.0.76-alpha.5",
|
"@budibase/client": "^1.0.79-alpha.5",
|
||||||
"@budibase/string-templates": "^1.0.76-alpha.5",
|
"@budibase/string-templates": "^1.0.79-alpha.5",
|
||||||
"@bull-board/api": "^3.7.0",
|
"@bull-board/api": "^3.7.0",
|
||||||
"@bull-board/koa": "^3.7.0",
|
"@bull-board/koa": "^3.7.0",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/string-templates",
|
"name": "@budibase/string-templates",
|
||||||
"version": "1.0.76-alpha.5",
|
"version": "1.0.79-alpha.5",
|
||||||
"description": "Handlebars wrapper for Budibase templating.",
|
"description": "Handlebars wrapper for Budibase templating.",
|
||||||
"main": "src/index.cjs",
|
"main": "src/index.cjs",
|
||||||
"module": "dist/bundle.mjs",
|
"module": "dist/bundle.mjs",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/worker",
|
"name": "@budibase/worker",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "1.0.76-alpha.5",
|
"version": "1.0.79-alpha.5",
|
||||||
"description": "Budibase background service",
|
"description": "Budibase background service",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -34,8 +34,8 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "^1.0.76-alpha.5",
|
"@budibase/backend-core": "^1.0.79-alpha.5",
|
||||||
"@budibase/string-templates": "^1.0.76-alpha.5",
|
"@budibase/string-templates": "^1.0.79-alpha.5",
|
||||||
"@koa/router": "^8.0.0",
|
"@koa/router": "^8.0.0",
|
||||||
"@sentry/node": "^6.0.0",
|
"@sentry/node": "^6.0.0",
|
||||||
"@techpass/passport-openidconnect": "^0.3.0",
|
"@techpass/passport-openidconnect": "^0.3.0",
|
||||||
|
|
Loading…
Reference in New Issue