Merge branch 'master' of github.com:Budibase/budibase into labday/sqs
This commit is contained in:
commit
0144a5b844
|
@ -12,6 +12,13 @@ on:
|
||||||
- master
|
- master
|
||||||
pull_request:
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
run_as_oss:
|
||||||
|
type: boolean
|
||||||
|
required: false
|
||||||
|
description: Force running checks as if it was an OSS contributor
|
||||||
|
default: false
|
||||||
|
|
||||||
env:
|
env:
|
||||||
BRANCH: ${{ github.event.pull_request.head.ref }}
|
BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||||
|
@ -19,7 +26,7 @@ env:
|
||||||
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||||
NX_BASE_BRANCH: origin/${{ github.base_ref }}
|
NX_BASE_BRANCH: origin/${{ github.base_ref }}
|
||||||
USE_NX_AFFECTED: ${{ github.event_name == 'pull_request' }}
|
USE_NX_AFFECTED: ${{ github.event_name == 'pull_request' }}
|
||||||
IS_OSS_CONTRIBUTOR: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != 'Budibase/budibase' }}
|
IS_OSS_CONTRIBUTOR: ${{ inputs.run_as_oss == true || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != 'Budibase/budibase') }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
|
@ -200,6 +207,9 @@ jobs:
|
||||||
- run: yarn --frozen-lockfile
|
- run: yarn --frozen-lockfile
|
||||||
- name: Build packages
|
- name: Build packages
|
||||||
run: yarn build --scope @budibase/server --scope @budibase/worker
|
run: yarn build --scope @budibase/server --scope @budibase/worker
|
||||||
|
- name: Build backend-core for OSS contributor (required for pro)
|
||||||
|
if: ${{ env.IS_OSS_CONTRIBUTOR == 'true' }}
|
||||||
|
run: yarn build --scope @budibase/backend-core
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
cd qa-core
|
cd qa-core
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
name: OSS contributor checks
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 8,16 * * 1-5" # on weekdays at 8am and 4pm
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run-checks:
|
||||||
|
name: Publish server and worker docker images
|
||||||
|
uses: ./.github/workflows/budibase_ci.yml
|
||||||
|
with:
|
||||||
|
run_as_oss: true
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
notify-error:
|
||||||
|
needs: ["run-checks"]
|
||||||
|
if: ${{ failure() }}
|
||||||
|
name: Notify error
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set commit SHA
|
||||||
|
id: set_sha
|
||||||
|
run: echo "::set-output name=sha::$(git rev-parse --short ${{ github.sha }})"
|
||||||
|
|
||||||
|
- name: Notify error
|
||||||
|
uses: tsickert/discord-webhook@v5.3.0
|
||||||
|
with:
|
||||||
|
webhook-url: ${{ secrets.OSS_CHECKS_WEBHOOK_URL }}
|
||||||
|
embed-title: 🚨 OSS checks failed in master
|
||||||
|
embed-url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||||
|
embed-description: |
|
||||||
|
Git sha: `${{ steps.set_sha.outputs.sha }}`
|
|
@ -7,6 +7,7 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
*.spec.js
|
|
||||||
packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte
|
|
||||||
packages/server/builder
|
packages/server/builder
|
||||||
packages/server/coverage
|
packages/server/coverage
|
||||||
packages/worker/coverage
|
|
||||||
packages/backend-core/coverage
|
|
||||||
packages/server/client
|
packages/server/client
|
||||||
packages/server/src/definitions/openapi.ts
|
packages/server/src/definitions/openapi.ts
|
||||||
|
packages/worker/coverage
|
||||||
|
packages/backend-core/coverage
|
||||||
packages/builder/.routify
|
packages/builder/.routify
|
||||||
packages/sdk/sdk
|
packages/sdk/sdk
|
||||||
packages/pro/coverage
|
packages/pro/coverage
|
|
@ -46,11 +46,9 @@ spec:
|
||||||
image: minio/minio
|
image: minio/minio
|
||||||
imagePullPolicy: ""
|
imagePullPolicy: ""
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
exec:
|
httpGet:
|
||||||
command:
|
path: /minio/health/live
|
||||||
- curl
|
port: 9000
|
||||||
- -f
|
|
||||||
- http://localhost:9000/minio/health/live
|
|
||||||
failureThreshold: 3
|
failureThreshold: 3
|
||||||
periodSeconds: 30
|
periodSeconds: 30
|
||||||
timeoutSeconds: 20
|
timeoutSeconds: 20
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.13.10",
|
"version": "2.13.14",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -5,7 +5,6 @@ const { getDB } = require("../db")
|
||||||
describe("db", () => {
|
describe("db", () => {
|
||||||
describe("getDB", () => {
|
describe("getDB", () => {
|
||||||
it("returns a db", async () => {
|
it("returns a db", async () => {
|
||||||
|
|
||||||
const dbName = structures.db.id()
|
const dbName = structures.db.id()
|
||||||
const db = getDB(dbName)
|
const db = getDB(dbName)
|
||||||
expect(db).toBeDefined()
|
expect(db).toBeDefined()
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { getLockClient } from "./init"
|
||||||
import { LockOptions, LockType } from "@budibase/types"
|
import { LockOptions, LockType } from "@budibase/types"
|
||||||
import * as context from "../context"
|
import * as context from "../context"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
|
import { logWarn } from "../logging"
|
||||||
|
|
||||||
async function getClient(
|
async function getClient(
|
||||||
type: LockType,
|
type: LockType,
|
||||||
|
@ -116,7 +117,7 @@ export async function doWithLock<T>(
|
||||||
const result = await task()
|
const result = await task()
|
||||||
return { executed: true, result }
|
return { executed: true, result }
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.warn("lock error")
|
logWarn(`lock type: ${opts.type} error`, e)
|
||||||
// lock limit exceeded
|
// lock limit exceeded
|
||||||
if (e.name === "LockError") {
|
if (e.name === "LockError") {
|
||||||
if (opts.type === LockType.TRY_ONCE) {
|
if (opts.type === LockType.TRY_ONCE) {
|
||||||
|
@ -124,11 +125,9 @@ export async function doWithLock<T>(
|
||||||
// due to retry count (0) exceeded
|
// due to retry count (0) exceeded
|
||||||
return { executed: false }
|
return { executed: false }
|
||||||
} else {
|
} else {
|
||||||
console.error(e)
|
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error(e)
|
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -75,10 +75,12 @@ export function getRedisConnectionDetails() {
|
||||||
}
|
}
|
||||||
const [host, port] = url.split(":")
|
const [host, port] = url.split(":")
|
||||||
|
|
||||||
|
const portNumber = parseInt(port)
|
||||||
return {
|
return {
|
||||||
host,
|
host,
|
||||||
password,
|
password,
|
||||||
port: parseInt(port),
|
// assume default port for redis if invalid found
|
||||||
|
port: isNaN(portNumber) ? 6379 : portNumber,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const _ = require('lodash/fp')
|
const _ = require("lodash/fp")
|
||||||
const { structures } = require("../../../tests")
|
const { structures } = require("../../../tests")
|
||||||
|
|
||||||
jest.mock("../../../src/context")
|
jest.mock("../../../src/context")
|
||||||
|
@ -7,10 +7,9 @@ jest.mock("../../../src/db")
|
||||||
const context = require("../../../src/context")
|
const context = require("../../../src/context")
|
||||||
const db = require("../../../src/db")
|
const db = require("../../../src/db")
|
||||||
|
|
||||||
const {getCreatorCount} = require('../../../src/users/users')
|
const { getCreatorCount } = require("../../../src/users/users")
|
||||||
|
|
||||||
describe("Users", () => {
|
describe("Users", () => {
|
||||||
|
|
||||||
let getGlobalDBMock
|
let getGlobalDBMock
|
||||||
let getGlobalUserParamsMock
|
let getGlobalUserParamsMock
|
||||||
let paginationMock
|
let paginationMock
|
||||||
|
@ -34,18 +33,18 @@ describe("Users", () => {
|
||||||
getGlobalDBMock.mockImplementation(() => ({
|
getGlobalDBMock.mockImplementation(() => ({
|
||||||
name: "fake-db",
|
name: "fake-db",
|
||||||
allDocs: () => ({
|
allDocs: () => ({
|
||||||
rows: [...page1Data, ...page2Data]
|
rows: [...page1Data, ...page2Data],
|
||||||
})
|
}),
|
||||||
}))
|
}))
|
||||||
paginationMock.mockImplementationOnce(() => ({
|
paginationMock.mockImplementationOnce(() => ({
|
||||||
data: page1Data,
|
data: page1Data,
|
||||||
hasNextPage: true,
|
hasNextPage: true,
|
||||||
nextPage: "1"
|
nextPage: "1",
|
||||||
}))
|
}))
|
||||||
paginationMock.mockImplementation(() => ({
|
paginationMock.mockImplementation(() => ({
|
||||||
data: page2Data,
|
data: page2Data,
|
||||||
hasNextPage: false,
|
hasNextPage: false,
|
||||||
nextPage: undefined
|
nextPage: undefined,
|
||||||
}))
|
}))
|
||||||
const creatorsCount = await getCreatorCount()
|
const creatorsCount = await getCreatorCount()
|
||||||
expect(creatorsCount).toBe(4)
|
expect(creatorsCount).toBe(4)
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let error = null
|
export let error = null
|
||||||
export let size = "M"
|
export let size = "M"
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -18,6 +19,6 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<Checkbox {error} {disabled} {text} {value} {size} on:change={onChange} />
|
<Checkbox {error} {disabled} {text} {value} {size} on:change={onChange} />
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
export let error = null
|
export let error = null
|
||||||
export let placeholder = "Choose an option or type"
|
export let placeholder = "Choose an option or type"
|
||||||
export let options = []
|
export let options = []
|
||||||
|
export let helpText = null
|
||||||
export let getOptionLabel = option => extractProperty(option, "label")
|
export let getOptionLabel = option => extractProperty(option, "label")
|
||||||
export let getOptionValue = option => extractProperty(option, "value")
|
export let getOptionValue = option => extractProperty(option, "value")
|
||||||
|
|
||||||
|
@ -27,7 +28,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<Combobox
|
<Combobox
|
||||||
{error}
|
{error}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
export let value = false
|
export let value = false
|
||||||
export let error = null
|
|
||||||
export let id = null
|
export let id = null
|
||||||
export let text = null
|
export let text = null
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
|
@ -22,7 +21,6 @@
|
||||||
|
|
||||||
<label
|
<label
|
||||||
class="spectrum-Checkbox spectrum-Checkbox--emphasized {sizeClass}"
|
class="spectrum-Checkbox spectrum-Checkbox--emphasized {sizeClass}"
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:checked={value}
|
class:checked={value}
|
||||||
class:is-indeterminate={indeterminate}
|
class:is-indeterminate={indeterminate}
|
||||||
class:readonly
|
class:readonly
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
export let direction = "vertical"
|
export let direction = "vertical"
|
||||||
export let value = []
|
export let value = []
|
||||||
export let options = []
|
export let options = []
|
||||||
export let error = null
|
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let getOptionLabel = option => option
|
export let getOptionLabel = option => option
|
||||||
|
@ -34,7 +33,6 @@
|
||||||
<div
|
<div
|
||||||
title={getOptionLabel(option)}
|
title={getOptionLabel(option)}
|
||||||
class="spectrum-Checkbox spectrum-FieldGroup-item"
|
class="spectrum-Checkbox spectrum-FieldGroup-item"
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:readonly
|
class:readonly
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
export let placeholder = "Choose an option or type"
|
export let placeholder = "Choose an option or type"
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let error = null
|
|
||||||
export let options = []
|
export let options = []
|
||||||
export let getOptionLabel = option => option
|
export let getOptionLabel = option => option
|
||||||
export let getOptionValue = option => option
|
export let getOptionValue = option => option
|
||||||
|
@ -39,12 +38,10 @@
|
||||||
<div
|
<div
|
||||||
class="spectrum-InputGroup"
|
class="spectrum-InputGroup"
|
||||||
class:is-focused={open || focus}
|
class:is-focused={open || focus}
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
class:is-focused={open || focus}
|
class:is-focused={open || focus}
|
||||||
>
|
>
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
export let id = null
|
export let id = null
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let error = null
|
|
||||||
export let enableTime = true
|
export let enableTime = true
|
||||||
export let value = null
|
export let value = null
|
||||||
export let placeholder = null
|
export let placeholder = null
|
||||||
|
@ -188,7 +187,6 @@
|
||||||
<div
|
<div
|
||||||
id={flatpickrId}
|
id={flatpickrId}
|
||||||
class:is-disabled={disabled || readonly}
|
class:is-disabled={disabled || readonly}
|
||||||
class:is-invalid={!!error}
|
|
||||||
class="flatpickr spectrum-InputGroup spectrum-Datepicker"
|
class="flatpickr spectrum-InputGroup spectrum-Datepicker"
|
||||||
class:is-focused={open}
|
class:is-focused={open}
|
||||||
aria-readonly="false"
|
aria-readonly="false"
|
||||||
|
@ -199,17 +197,7 @@
|
||||||
on:click={flatpickr?.open}
|
on:click={flatpickr?.open}
|
||||||
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
class:is-invalid={!!error}
|
|
||||||
>
|
>
|
||||||
{#if !!error}
|
|
||||||
<svg
|
|
||||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
|
|
||||||
focusable="false"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
<input
|
<input
|
||||||
{disabled}
|
{disabled}
|
||||||
{readonly}
|
{readonly}
|
||||||
|
@ -227,7 +215,6 @@
|
||||||
class="spectrum-Picker spectrum-Picker--sizeM spectrum-InputGroup-button"
|
class="spectrum-Picker spectrum-Picker--sizeM spectrum-InputGroup-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
class:is-invalid={!!error}
|
|
||||||
on:click={flatpickr?.open}
|
on:click={flatpickr?.open}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
|
|
|
@ -22,7 +22,6 @@
|
||||||
export let handleFileTooLarge = null
|
export let handleFileTooLarge = null
|
||||||
export let handleTooManyFiles = null
|
export let handleTooManyFiles = null
|
||||||
export let gallery = true
|
export let gallery = true
|
||||||
export let error = null
|
|
||||||
export let fileTags = []
|
export let fileTags = []
|
||||||
export let maximum = null
|
export let maximum = null
|
||||||
export let extensions = "*"
|
export let extensions = "*"
|
||||||
|
@ -222,7 +221,6 @@
|
||||||
{#if showDropzone}
|
{#if showDropzone}
|
||||||
<div
|
<div
|
||||||
class="spectrum-Dropzone"
|
class="spectrum-Dropzone"
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:disabled
|
class:disabled
|
||||||
role="region"
|
role="region"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
@ -351,9 +349,6 @@
|
||||||
.spectrum-Dropzone {
|
.spectrum-Dropzone {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
.spectrum-Dropzone.is-invalid {
|
|
||||||
border-color: var(--spectrum-global-color-red-400);
|
|
||||||
}
|
|
||||||
input[type="file"] {
|
input[type="file"] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let error = null
|
|
||||||
export let options = []
|
export let options = []
|
||||||
export let getOptionLabel = option => extractProperty(option, "label")
|
export let getOptionLabel = option => extractProperty(option, "label")
|
||||||
export let getOptionValue = option => extractProperty(option, "value")
|
export let getOptionValue = option => extractProperty(option, "value")
|
||||||
|
@ -111,27 +110,12 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div class="spectrum-InputGroup" class:is-disabled={disabled}>
|
||||||
class="spectrum-InputGroup"
|
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:is-disabled={disabled}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
class:is-focused={focus}
|
class:is-focused={focus}
|
||||||
>
|
>
|
||||||
{#if error}
|
|
||||||
<svg
|
|
||||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
|
|
||||||
focusable="false"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<input
|
<input
|
||||||
{id}
|
{id}
|
||||||
on:click
|
on:click
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
export let id = null
|
export let id = null
|
||||||
export let placeholder = null
|
export let placeholder = null
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let error = null
|
|
||||||
export let options = []
|
export let options = []
|
||||||
export let getOptionLabel = option => option
|
export let getOptionLabel = option => option
|
||||||
export let getOptionValue = option => option
|
export let getOptionValue = option => option
|
||||||
|
@ -84,7 +83,6 @@
|
||||||
<Picker
|
<Picker
|
||||||
on:loadMore
|
on:loadMore
|
||||||
{id}
|
{id}
|
||||||
{error}
|
|
||||||
{disabled}
|
{disabled}
|
||||||
{readonly}
|
{readonly}
|
||||||
{fieldText}
|
{fieldText}
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
|
|
||||||
export let id = null
|
export let id = null
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let error = null
|
|
||||||
export let fieldText = ""
|
export let fieldText = ""
|
||||||
export let fieldIcon = ""
|
export let fieldIcon = ""
|
||||||
export let fieldColour = ""
|
export let fieldColour = ""
|
||||||
|
@ -113,7 +112,6 @@
|
||||||
class="spectrum-Picker spectrum-Picker--sizeM"
|
class="spectrum-Picker spectrum-Picker--sizeM"
|
||||||
class:spectrum-Picker--quiet={quiet}
|
class:spectrum-Picker--quiet={quiet}
|
||||||
{disabled}
|
{disabled}
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:is-open={open}
|
class:is-open={open}
|
||||||
aria-haspopup="listbox"
|
aria-haspopup="listbox"
|
||||||
on:click={onClick}
|
on:click={onClick}
|
||||||
|
@ -142,16 +140,6 @@
|
||||||
>
|
>
|
||||||
{fieldText}
|
{fieldText}
|
||||||
</span>
|
</span>
|
||||||
{#if error}
|
|
||||||
<svg
|
|
||||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Picker-validationIcon"
|
|
||||||
focusable="false"
|
|
||||||
aria-hidden="true"
|
|
||||||
aria-label="Folder"
|
|
||||||
>
|
|
||||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
<svg
|
<svg
|
||||||
class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon"
|
class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
export let id = null
|
export let id = null
|
||||||
export let placeholder = "Choose an option or type"
|
export let placeholder = "Choose an option or type"
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let error = null
|
|
||||||
export let secondaryOptions = []
|
export let secondaryOptions = []
|
||||||
export let primaryOptions = []
|
export let primaryOptions = []
|
||||||
export let secondaryFieldText = ""
|
export let secondaryFieldText = ""
|
||||||
|
@ -105,14 +104,9 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div class="spectrum-InputGroup" class:is-disabled={disabled}>
|
||||||
class="spectrum-InputGroup"
|
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:is-disabled={disabled}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
class:is-focused={focus}
|
class:is-focused={focus}
|
||||||
class:is-full-width={!secondaryOptions.length}
|
class:is-full-width={!secondaryOptions.length}
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
export let direction = "vertical"
|
export let direction = "vertical"
|
||||||
export let value = null
|
export let value = null
|
||||||
export let options = []
|
export let options = []
|
||||||
export let error = null
|
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let getOptionLabel = option => option
|
export let getOptionLabel = option => option
|
||||||
|
@ -40,7 +39,6 @@
|
||||||
<div
|
<div
|
||||||
title={getOptionTitle(option)}
|
title={getOptionTitle(option)}
|
||||||
class="spectrum-Radio spectrum-FieldGroup-item spectrum-Radio--emphasized"
|
class="spectrum-Radio spectrum-FieldGroup-item spectrum-Radio--emphasized"
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:readonly
|
class:readonly
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
|
|
@ -5,14 +5,13 @@
|
||||||
export let placeholder = null
|
export let placeholder = null
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let error = null
|
|
||||||
export let height = null
|
export let height = null
|
||||||
export let id = null
|
export let id = null
|
||||||
export let fullScreenOffset = null
|
export let fullScreenOffset = null
|
||||||
export let easyMDEOptions = null
|
export let easyMDEOptions = null
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class:error>
|
<div>
|
||||||
<MarkdownEditor
|
<MarkdownEditor
|
||||||
{value}
|
{value}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
|
@ -27,18 +26,4 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.error :global(.EasyMDEContainer .editor-toolbar) {
|
|
||||||
border-top-color: var(--spectrum-semantic-negative-color-default);
|
|
||||||
border-left-color: var(--spectrum-semantic-negative-color-default);
|
|
||||||
border-right-color: var(--spectrum-semantic-negative-color-default);
|
|
||||||
}
|
|
||||||
.error :global(.EasyMDEContainer .CodeMirror) {
|
|
||||||
border-bottom-color: var(--spectrum-semantic-negative-color-default);
|
|
||||||
border-left-color: var(--spectrum-semantic-negative-color-default);
|
|
||||||
border-right-color: var(--spectrum-semantic-negative-color-default);
|
|
||||||
}
|
|
||||||
.error :global(.EasyMDEContainer .editor-preview-side) {
|
|
||||||
border-bottom-color: var(--spectrum-semantic-negative-color-default);
|
|
||||||
border-right-color: var(--spectrum-semantic-negative-color-default);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
export let id = null
|
export let id = null
|
||||||
export let placeholder = "Choose an option"
|
export let placeholder = "Choose an option"
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let error = null
|
|
||||||
export let options = []
|
export let options = []
|
||||||
export let getOptionLabel = option => option
|
export let getOptionLabel = option => option
|
||||||
export let getOptionValue = option => option
|
export let getOptionValue = option => option
|
||||||
|
@ -71,7 +70,6 @@
|
||||||
on:loadMore
|
on:loadMore
|
||||||
{quiet}
|
{quiet}
|
||||||
{id}
|
{id}
|
||||||
{error}
|
|
||||||
{disabled}
|
{disabled}
|
||||||
{readonly}
|
{readonly}
|
||||||
{fieldText}
|
{fieldText}
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
export let value = null
|
export let value = null
|
||||||
export let placeholder = null
|
export let placeholder = null
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let error = null
|
|
||||||
export let id = null
|
export let id = null
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
|
@ -98,20 +97,9 @@
|
||||||
<div
|
<div
|
||||||
class="spectrum-Stepper"
|
class="spectrum-Stepper"
|
||||||
class:spectrum-Stepper--quiet={quiet}
|
class:spectrum-Stepper--quiet={quiet}
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
class:is-focused={focus}
|
class:is-focused={focus}
|
||||||
>
|
>
|
||||||
{#if error}
|
|
||||||
<svg
|
|
||||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
|
|
||||||
focusable="false"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="spectrum-Textfield spectrum-Stepper-textfield">
|
<div class="spectrum-Textfield spectrum-Stepper-textfield">
|
||||||
<input
|
<input
|
||||||
{disabled}
|
{disabled}
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
export let placeholder = null
|
export let placeholder = null
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let error = null
|
|
||||||
export let id = null
|
export let id = null
|
||||||
export let height = null
|
export let height = null
|
||||||
export let minHeight = null
|
export let minHeight = null
|
||||||
|
@ -41,20 +40,9 @@
|
||||||
<div
|
<div
|
||||||
style={`${heightString}${minHeightString}`}
|
style={`${heightString}${minHeightString}`}
|
||||||
class="spectrum-Textfield spectrum-Textfield--multiline"
|
class="spectrum-Textfield spectrum-Textfield--multiline"
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
class:is-focused={focus}
|
class:is-focused={focus}
|
||||||
>
|
>
|
||||||
{#if error}
|
|
||||||
<svg
|
|
||||||
class="spectrum-Icon spectrum-Icon--sizeM
|
|
||||||
spectrum-Textfield-validationIcon"
|
|
||||||
focusable="false"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<textarea
|
<textarea
|
||||||
bind:this={textarea}
|
bind:this={textarea}
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
export let placeholder = null
|
export let placeholder = null
|
||||||
export let type = "text"
|
export let type = "text"
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let error = null
|
|
||||||
export let id = null
|
export let id = null
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
|
@ -78,19 +77,9 @@
|
||||||
<div
|
<div
|
||||||
class="spectrum-Textfield"
|
class="spectrum-Textfield"
|
||||||
class:spectrum-Textfield--quiet={quiet}
|
class:spectrum-Textfield--quiet={quiet}
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
class:is-focused={focus}
|
class:is-focused={focus}
|
||||||
>
|
>
|
||||||
{#if error}
|
|
||||||
<svg
|
|
||||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
|
|
||||||
focusable="false"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
<input
|
<input
|
||||||
bind:this={field}
|
bind:this={field}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
export let appendTo = undefined
|
export let appendTo = undefined
|
||||||
export let ignoreTimezones = false
|
export let ignoreTimezones = false
|
||||||
export let range = false
|
export let range = false
|
||||||
|
export let helpText = null
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -30,7 +31,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{error}
|
{error}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
export let fileTags = []
|
export let fileTags = []
|
||||||
export let maximum = undefined
|
export let maximum = undefined
|
||||||
export let compact = false
|
export let compact = false
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -25,7 +26,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<CoreDropzone
|
<CoreDropzone
|
||||||
{error}
|
{error}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
export let autofocus
|
export let autofocus
|
||||||
export let variables
|
export let variables
|
||||||
export let showModal
|
export let showModal
|
||||||
|
export let helpText = null
|
||||||
export let environmentVariablesEnabled
|
export let environmentVariablesEnabled
|
||||||
export let handleUpgradePanel
|
export let handleUpgradePanel
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
@ -25,7 +26,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<EnvDropdown
|
<EnvDropdown
|
||||||
{updateOnChange}
|
{updateOnChange}
|
||||||
{error}
|
{error}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
<script>
|
<script>
|
||||||
import "@spectrum-css/fieldlabel/dist/index-vars.css"
|
import "@spectrum-css/fieldlabel/dist/index-vars.css"
|
||||||
import FieldLabel from "./FieldLabel.svelte"
|
import FieldLabel from "./FieldLabel.svelte"
|
||||||
|
import Icon from "../Icon/Icon.svelte"
|
||||||
|
|
||||||
export let id = null
|
export let id = null
|
||||||
export let label = null
|
export let label = null
|
||||||
export let labelPosition = "above"
|
export let labelPosition = "above"
|
||||||
export let error = null
|
export let error = null
|
||||||
|
export let helpText = null
|
||||||
export let tooltip = ""
|
export let tooltip = ""
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -17,6 +19,10 @@
|
||||||
<slot />
|
<slot />
|
||||||
{#if error}
|
{#if error}
|
||||||
<div class="error">{error}</div>
|
<div class="error">{error}</div>
|
||||||
|
{:else if helpText}
|
||||||
|
<div class="helpText">
|
||||||
|
<Icon name="HelpOutline" /> <span>{helpText}</span>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -39,4 +45,21 @@
|
||||||
font-size: var(--spectrum-global-dimension-font-size-75);
|
font-size: var(--spectrum-global-dimension-font-size-75);
|
||||||
margin-top: var(--spectrum-global-dimension-size-75);
|
margin-top: var(--spectrum-global-dimension-size-75);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.helpText {
|
||||||
|
display: flex;
|
||||||
|
margin-top: var(--spectrum-global-dimension-size-75);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.helpText :global(svg) {
|
||||||
|
width: 14px;
|
||||||
|
color: var(--grey-5);
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.helpText span {
|
||||||
|
color: var(--grey-7);
|
||||||
|
font-size: var(--spectrum-global-dimension-font-size-75);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
export let title = null
|
export let title = null
|
||||||
export let value = null
|
export let value = null
|
||||||
export let tooltip = null
|
export let tooltip = null
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -22,7 +23,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error} {tooltip}>
|
<Field {helpText} {label} {labelPosition} {error} {tooltip}>
|
||||||
<CoreFile
|
<CoreFile
|
||||||
{error}
|
{error}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let autofocus
|
export let autofocus
|
||||||
export let autocomplete
|
export let autocomplete
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -23,7 +24,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<TextField
|
<TextField
|
||||||
{updateOnChange}
|
{updateOnChange}
|
||||||
{error}
|
{error}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let autofocus
|
export let autofocus
|
||||||
|
export let helpText = null
|
||||||
export let options = []
|
export let options = []
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
@ -29,7 +30,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<InputDropdown
|
<InputDropdown
|
||||||
{updateOnChange}
|
{updateOnChange}
|
||||||
{error}
|
{error}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
export let autocomplete = false
|
export let autocomplete = false
|
||||||
export let searchTerm = null
|
export let searchTerm = null
|
||||||
export let customPopoverHeight
|
export let customPopoverHeight
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -26,7 +27,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<Multiselect
|
<Multiselect
|
||||||
{error}
|
{error}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
export let secondaryOptions = []
|
export let secondaryOptions = []
|
||||||
export let searchTerm
|
export let searchTerm
|
||||||
export let showClearIcon = true
|
export let showClearIcon = true
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
let primaryLabel
|
let primaryLabel
|
||||||
let secondaryLabel
|
let secondaryLabel
|
||||||
|
@ -93,7 +94,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<PickerDropdown
|
<PickerDropdown
|
||||||
{searchTerm}
|
{searchTerm}
|
||||||
{autocomplete}
|
{autocomplete}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
export let getOptionLabel = option => extractProperty(option, "label")
|
export let getOptionLabel = option => extractProperty(option, "label")
|
||||||
export let getOptionValue = option => extractProperty(option, "value")
|
export let getOptionValue = option => extractProperty(option, "value")
|
||||||
export let getOptionTitle = option => extractProperty(option, "label")
|
export let getOptionTitle = option => extractProperty(option, "label")
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -27,7 +28,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
{error}
|
{error}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
export let id = null
|
export let id = null
|
||||||
export let fullScreenOffset = null
|
export let fullScreenOffset = null
|
||||||
export let easyMDEOptions = null
|
export let easyMDEOptions = null
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -21,7 +22,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<RichTextField
|
<RichTextField
|
||||||
{error}
|
{error}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let inputRef
|
export let inputRef
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -19,7 +20,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition}>
|
<Field {helpText} {label} {labelPosition}>
|
||||||
<Search
|
<Search
|
||||||
{updateOnChange}
|
{updateOnChange}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
export let align
|
export let align
|
||||||
export let footer = null
|
export let footer = null
|
||||||
export let tag = null
|
export let tag = null
|
||||||
|
export let helpText = null
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
value = e.detail
|
value = e.detail
|
||||||
|
@ -40,7 +41,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error} {tooltip}>
|
<Field {helpText} {label} {labelPosition} {error} {tooltip}>
|
||||||
<Select
|
<Select
|
||||||
{quiet}
|
{quiet}
|
||||||
{error}
|
{error}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
export let step = 1
|
export let step = 1
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let error = null
|
export let error = null
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -19,6 +20,6 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<Slider {disabled} {value} {min} {max} {step} on:change={onChange} />
|
<Slider {disabled} {value} {min} {max} {step} on:change={onChange} />
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
export let min = null
|
export let min = null
|
||||||
export let max = null
|
export let max = null
|
||||||
export let step = 1
|
export let step = 1
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -23,7 +24,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<Stepper
|
<Stepper
|
||||||
{updateOnChange}
|
{updateOnChange}
|
||||||
{error}
|
{error}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
export let getCaretPosition = null
|
export let getCaretPosition = null
|
||||||
export let height = null
|
export let height = null
|
||||||
export let minHeight = null
|
export let minHeight = null
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -20,7 +21,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<TextArea
|
<TextArea
|
||||||
bind:getCaretPosition
|
bind:getCaretPosition
|
||||||
{error}
|
{error}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
export let text = null
|
export let text = null
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let error = null
|
export let error = null
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -17,6 +18,6 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<Switch {error} {disabled} {text} {value} on:change={onChange} />
|
<Switch {error} {disabled} {text} {value} on:change={onChange} />
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -16,10 +16,9 @@
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
const onClick = e => {
|
const onClick = () => {
|
||||||
if (!disabled) {
|
if (!disabled) {
|
||||||
dispatch("click")
|
dispatch("click")
|
||||||
e.stopPropagation()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import download from "downloadjs"
|
import download from "downloadjs"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { Constants, LuceneUtils } from "@budibase/frontend-core"
|
import { LuceneUtils } from "@budibase/frontend-core"
|
||||||
|
import { utils } from "@budibase/shared-core"
|
||||||
import { ROW_EXPORT_FORMATS } from "constants/backend"
|
import { ROW_EXPORT_FORMATS } from "constants/backend"
|
||||||
|
|
||||||
export let view
|
export let view
|
||||||
|
@ -32,6 +33,8 @@
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
$: appliedFilters = filters?.filter(filter => !filter.onEmptyFilter)
|
||||||
|
|
||||||
$: options = FORMATS.filter(format => {
|
$: options = FORMATS.filter(format => {
|
||||||
if (formats && !formats.includes(format.key)) {
|
if (formats && !formats.includes(format.key)) {
|
||||||
return false
|
return false
|
||||||
|
@ -46,23 +49,20 @@
|
||||||
exportFormat = Array.isArray(options) ? options[0]?.key : []
|
exportFormat = Array.isArray(options) ? options[0]?.key : []
|
||||||
}
|
}
|
||||||
|
|
||||||
$: luceneFilter = LuceneUtils.buildLuceneQuery(filters)
|
$: luceneFilter = LuceneUtils.buildLuceneQuery(appliedFilters)
|
||||||
$: exportOpDisplay = buildExportOpDisplay(sorting, filterDisplay, filters)
|
$: exportOpDisplay = buildExportOpDisplay(
|
||||||
|
sorting,
|
||||||
|
filterDisplay,
|
||||||
|
appliedFilters
|
||||||
|
)
|
||||||
|
|
||||||
const buildFilterLookup = () => {
|
filterLookup = utils.filterValueToLabel()
|
||||||
return Object.keys(Constants.OperatorOptions).reduce((acc, key) => {
|
|
||||||
const op = Constants.OperatorOptions[key]
|
|
||||||
acc[op.value] = op.label
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
filterLookup = buildFilterLookup()
|
|
||||||
|
|
||||||
const filterDisplay = () => {
|
const filterDisplay = () => {
|
||||||
if (!filters) {
|
if (!appliedFilters) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
return filters.map(filter => {
|
return appliedFilters.map(filter => {
|
||||||
let newFieldName = filter.field + ""
|
let newFieldName = filter.field + ""
|
||||||
const parts = newFieldName.split(":")
|
const parts = newFieldName.split(":")
|
||||||
parts.shift()
|
parts.shift()
|
||||||
|
@ -77,7 +77,7 @@
|
||||||
|
|
||||||
const buildExportOpDisplay = (sorting, filterDisplay) => {
|
const buildExportOpDisplay = (sorting, filterDisplay) => {
|
||||||
let filterDisplayConfig = filterDisplay()
|
let filterDisplayConfig = filterDisplay()
|
||||||
if (sorting) {
|
if (sorting?.sortColumn) {
|
||||||
filterDisplayConfig = [
|
filterDisplayConfig = [
|
||||||
...filterDisplayConfig,
|
...filterDisplayConfig,
|
||||||
{
|
{
|
||||||
|
@ -132,7 +132,7 @@
|
||||||
format: exportFormat,
|
format: exportFormat,
|
||||||
})
|
})
|
||||||
downloadWithBlob(data, `export.${exportFormat}`)
|
downloadWithBlob(data, `export.${exportFormat}`)
|
||||||
} else if (filters || sorting) {
|
} else if (appliedFilters || sorting) {
|
||||||
let response
|
let response
|
||||||
try {
|
try {
|
||||||
response = await API.exportRows({
|
response = await API.exportRows({
|
||||||
|
@ -163,29 +163,33 @@
|
||||||
title="Export Data"
|
title="Export Data"
|
||||||
confirmText="Export"
|
confirmText="Export"
|
||||||
onConfirm={exportRows}
|
onConfirm={exportRows}
|
||||||
size={filters?.length || sorting ? "M" : "S"}
|
size={appliedFilters?.length || sorting ? "M" : "S"}
|
||||||
>
|
>
|
||||||
{#if selectedRows?.length}
|
{#if selectedRows?.length}
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
|
<span data-testid="exporting-n-rows">
|
||||||
<strong>{selectedRows?.length}</strong>
|
<strong>{selectedRows?.length}</strong>
|
||||||
{`row${selectedRows?.length > 1 ? "s" : ""} will be exported`}
|
{`row${selectedRows?.length > 1 ? "s" : ""} will be exported`}
|
||||||
|
</span>
|
||||||
</Body>
|
</Body>
|
||||||
{:else if filters || (sorting?.sortOrder && sorting?.sortColumn)}
|
{:else if appliedFilters?.length || (sorting?.sortOrder && sorting?.sortColumn)}
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
{#if !filters}
|
{#if !appliedFilters}
|
||||||
|
<span data-testid="exporting-rows">
|
||||||
Exporting <strong>all</strong> rows
|
Exporting <strong>all</strong> rows
|
||||||
|
</span>
|
||||||
{:else}
|
{:else}
|
||||||
Filters applied
|
<span data-testid="filters-applied">Filters applied</span>
|
||||||
{/if}
|
{/if}
|
||||||
</Body>
|
</Body>
|
||||||
|
|
||||||
<div class="table-wrap">
|
<div class="table-wrap" data-testid="export-config-table">
|
||||||
<Table
|
<Table
|
||||||
schema={displaySchema}
|
schema={displaySchema}
|
||||||
data={exportOpDisplay}
|
data={exportOpDisplay}
|
||||||
{filters}
|
{appliedFilters}
|
||||||
loading={false}
|
loading={false}
|
||||||
rowCount={filters?.length + 1}
|
rowCount={appliedFilters?.length + 1}
|
||||||
disableSorting={true}
|
disableSorting={true}
|
||||||
allowSelectRows={false}
|
allowSelectRows={false}
|
||||||
allowEditRows={false}
|
allowEditRows={false}
|
||||||
|
@ -196,10 +200,12 @@
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
|
<span data-testid="export-all-rows">
|
||||||
Exporting <strong>all</strong> rows
|
Exporting <strong>all</strong> rows
|
||||||
|
</span>
|
||||||
</Body>
|
</Body>
|
||||||
{/if}
|
{/if}
|
||||||
|
<span data-testid="format-select">
|
||||||
<Select
|
<Select
|
||||||
label="Format"
|
label="Format"
|
||||||
bind:value={exportFormat}
|
bind:value={exportFormat}
|
||||||
|
@ -208,6 +214,7 @@
|
||||||
getOptionLabel={x => x.name}
|
getOptionLabel={x => x.name}
|
||||||
getOptionValue={x => x.key}
|
getOptionValue={x => x.key}
|
||||||
/>
|
/>
|
||||||
|
</span>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -0,0 +1,240 @@
|
||||||
|
import { it, expect, describe, vi } from "vitest"
|
||||||
|
import { render, screen } from "@testing-library/svelte"
|
||||||
|
import "@testing-library/jest-dom"
|
||||||
|
|
||||||
|
import ExportModal from "./ExportModal.svelte"
|
||||||
|
import { utils } from "@budibase/shared-core"
|
||||||
|
|
||||||
|
const labelLookup = utils.filterValueToLabel()
|
||||||
|
|
||||||
|
const rowText = filter => {
|
||||||
|
let readableField = filter.field.split(":")[1]
|
||||||
|
let rowLabel = labelLookup[filter.operator]
|
||||||
|
let value = Array.isArray(filter.value)
|
||||||
|
? JSON.stringify(filter.value)
|
||||||
|
: filter.value
|
||||||
|
return `${readableField}${rowLabel}${value}`.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultFilters = [
|
||||||
|
{
|
||||||
|
onEmptyFilter: "all",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
vi.mock("svelte", async () => {
|
||||||
|
return {
|
||||||
|
getContext: () => {
|
||||||
|
return {
|
||||||
|
hide: vi.fn(),
|
||||||
|
cancel: vi.fn(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createEventDispatcher: vi.fn(),
|
||||||
|
onDestroy: vi.fn(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
vi.mock("api", async () => {
|
||||||
|
return {
|
||||||
|
API: {
|
||||||
|
exportView: vi.fn(),
|
||||||
|
exportRows: vi.fn(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Export Modal", () => {
|
||||||
|
it("show default messaging with no export config specified", () => {
|
||||||
|
render(ExportModal, {
|
||||||
|
props: {},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(screen.getByTestId("export-all-rows")).toBeVisible()
|
||||||
|
expect(screen.getByTestId("export-all-rows")).toHaveTextContent(
|
||||||
|
"Exporting all rows"
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(screen.queryByTestId("export-config-table")).toBe(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("indicate that a filter is being applied to the export", () => {
|
||||||
|
const propsCfg = {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
id: "MOQkMx9p9",
|
||||||
|
field: "1:Cost",
|
||||||
|
operator: "rangeHigh",
|
||||||
|
value: "100",
|
||||||
|
valueType: "Value",
|
||||||
|
type: "number",
|
||||||
|
noValue: false,
|
||||||
|
},
|
||||||
|
...defaultFilters,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
render(ExportModal, {
|
||||||
|
props: propsCfg,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(screen.getByTestId("filters-applied")).toBeVisible()
|
||||||
|
expect(screen.getByTestId("filters-applied").textContent).toBe(
|
||||||
|
"Filters applied"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ele = screen.queryByTestId("export-config-table")
|
||||||
|
expect(ele).toBeVisible()
|
||||||
|
|
||||||
|
const rows = ele.getElementsByClassName("spectrum-Table-row")
|
||||||
|
|
||||||
|
expect(rows.length).toBe(1)
|
||||||
|
let rowTextContent = rowText(propsCfg.filters[0])
|
||||||
|
|
||||||
|
//"CostLess than or equal to100"
|
||||||
|
expect(rows[0].textContent?.trim()).toEqual(rowTextContent)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Show only selected row messaging if rows are supplied", () => {
|
||||||
|
const propsCfg = {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
id: "MOQkMx9p9",
|
||||||
|
field: "1:Cost",
|
||||||
|
operator: "rangeHigh",
|
||||||
|
value: "100",
|
||||||
|
valueType: "Value",
|
||||||
|
type: "number",
|
||||||
|
noValue: false,
|
||||||
|
},
|
||||||
|
...defaultFilters,
|
||||||
|
],
|
||||||
|
sorting: {
|
||||||
|
sortColumn: "Cost",
|
||||||
|
sortOrder: "descending",
|
||||||
|
},
|
||||||
|
selectedRows: [
|
||||||
|
{
|
||||||
|
_id: "ro_ta_bb_expenses_57d5f6fe1b6640d8bb22b15f5eae62cd",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: "ro_ta_bb_expenses_99ce5760a53a430bab4349cd70335a07",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
render(ExportModal, {
|
||||||
|
props: propsCfg,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(screen.queryByTestId("export-config-table")).toBeNull()
|
||||||
|
expect(screen.queryByTestId("filters-applied")).toBeNull()
|
||||||
|
|
||||||
|
expect(screen.queryByTestId("exporting-n-rows")).toBeVisible()
|
||||||
|
expect(screen.queryByTestId("exporting-n-rows").textContent).toEqual(
|
||||||
|
"2 rows will be exported"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Show only the configured sort when no filters are specified", () => {
|
||||||
|
const propsCfg = {
|
||||||
|
filters: [...defaultFilters],
|
||||||
|
sorting: {
|
||||||
|
sortColumn: "Cost",
|
||||||
|
sortOrder: "descending",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
render(ExportModal, {
|
||||||
|
props: propsCfg,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(screen.queryByTestId("export-config-table")).toBeVisible()
|
||||||
|
const ele = screen.queryByTestId("export-config-table")
|
||||||
|
const rows = ele.getElementsByClassName("spectrum-Table-row")
|
||||||
|
|
||||||
|
expect(rows.length).toBe(1)
|
||||||
|
expect(rows[0].textContent?.trim()).toEqual(
|
||||||
|
`${propsCfg.sorting.sortColumn}Order By${propsCfg.sorting.sortOrder}`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Display all currently configured filters and applied sort", () => {
|
||||||
|
const propsCfg = {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
id: "MOQkMx9p9",
|
||||||
|
field: "1:Cost",
|
||||||
|
operator: "rangeHigh",
|
||||||
|
value: "100",
|
||||||
|
valueType: "Value",
|
||||||
|
type: "number",
|
||||||
|
noValue: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "2ot-aB0gE",
|
||||||
|
field: "2:Expense Tags",
|
||||||
|
operator: "contains",
|
||||||
|
value: ["Equipment", "Services"],
|
||||||
|
valueType: "Value",
|
||||||
|
type: "array",
|
||||||
|
noValue: false,
|
||||||
|
},
|
||||||
|
...defaultFilters,
|
||||||
|
],
|
||||||
|
sorting: {
|
||||||
|
sortColumn: "Payment Due",
|
||||||
|
sortOrder: "ascending",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
render(ExportModal, {
|
||||||
|
props: propsCfg,
|
||||||
|
})
|
||||||
|
|
||||||
|
const ele = screen.queryByTestId("export-config-table")
|
||||||
|
expect(ele).toBeVisible()
|
||||||
|
|
||||||
|
const rows = ele.getElementsByClassName("spectrum-Table-row")
|
||||||
|
expect(rows.length).toBe(3)
|
||||||
|
|
||||||
|
let rowTextContent1 = rowText(propsCfg.filters[0])
|
||||||
|
expect(rows[0].textContent?.trim()).toEqual(rowTextContent1)
|
||||||
|
|
||||||
|
let rowTextContent2 = rowText(propsCfg.filters[1])
|
||||||
|
expect(rows[1].textContent?.trim()).toEqual(rowTextContent2)
|
||||||
|
|
||||||
|
expect(rows[2].textContent?.trim()).toEqual(
|
||||||
|
`${propsCfg.sorting.sortColumn}Order By${propsCfg.sorting.sortOrder}`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("show only the valid, configured download formats", () => {
|
||||||
|
const propsCfg = {
|
||||||
|
formats: ["badger", "json"],
|
||||||
|
}
|
||||||
|
|
||||||
|
render(ExportModal, {
|
||||||
|
props: propsCfg,
|
||||||
|
})
|
||||||
|
|
||||||
|
let ele = screen.getByTestId("format-select")
|
||||||
|
expect(ele).toBeVisible()
|
||||||
|
|
||||||
|
let formatDisplay = ele.getElementsByTagName("button")[0]
|
||||||
|
|
||||||
|
expect(formatDisplay.textContent.trim()).toBe("JSON")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Load the default format config when no explicit formats are configured", () => {
|
||||||
|
render(ExportModal, {
|
||||||
|
props: {},
|
||||||
|
})
|
||||||
|
|
||||||
|
let ele = screen.getByTestId("format-select")
|
||||||
|
expect(ele).toBeVisible()
|
||||||
|
|
||||||
|
let formatDisplay = ele.getElementsByTagName("button")[0]
|
||||||
|
|
||||||
|
expect(formatDisplay.textContent.trim()).toBe("CSV")
|
||||||
|
})
|
||||||
|
})
|
|
@ -21,6 +21,7 @@
|
||||||
export let allowHelpers = true
|
export let allowHelpers = true
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let drawerLeft
|
export let drawerLeft
|
||||||
|
export let disableBindings = false
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
let bindingDrawer
|
let bindingDrawer
|
||||||
|
@ -62,7 +63,7 @@
|
||||||
{placeholder}
|
{placeholder}
|
||||||
{updateOnChange}
|
{updateOnChange}
|
||||||
/>
|
/>
|
||||||
{#if !disabled}
|
{#if !disabled && !disableBindings}
|
||||||
<div
|
<div
|
||||||
class="icon"
|
class="icon"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
|
|
|
@ -20,7 +20,12 @@
|
||||||
import analytics, { Events, EventSource } from "analytics"
|
import analytics, { Events, EventSource } from "analytics"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { apps } from "stores/portal"
|
import { apps } from "stores/portal"
|
||||||
import { deploymentStore, store, isOnlyUser } from "builderStore"
|
import {
|
||||||
|
deploymentStore,
|
||||||
|
store,
|
||||||
|
isOnlyUser,
|
||||||
|
sortedScreens,
|
||||||
|
} from "builderStore"
|
||||||
import TourWrap from "components/portal/onboarding/TourWrap.svelte"
|
import TourWrap from "components/portal/onboarding/TourWrap.svelte"
|
||||||
import { TOUR_STEP_KEYS } from "components/portal/onboarding/tours.js"
|
import { TOUR_STEP_KEYS } from "components/portal/onboarding/tours.js"
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
|
@ -48,7 +53,7 @@
|
||||||
$store.upgradableVersion &&
|
$store.upgradableVersion &&
|
||||||
$store.version &&
|
$store.version &&
|
||||||
$store.upgradableVersion !== $store.version
|
$store.upgradableVersion !== $store.version
|
||||||
$: canPublish = !publishing && loaded
|
$: canPublish = !publishing && loaded && $sortedScreens.length > 0
|
||||||
$: lastDeployed = getLastDeployedString($deploymentStore)
|
$: lastDeployed = getLastDeployedString($deploymentStore)
|
||||||
|
|
||||||
const initialiseApp = async () => {
|
const initialiseApp = async () => {
|
||||||
|
@ -175,7 +180,12 @@
|
||||||
|
|
||||||
<div class="app-action-button preview">
|
<div class="app-action-button preview">
|
||||||
<div class="app-action">
|
<div class="app-action">
|
||||||
<ActionButton quiet icon="PlayCircle" on:click={previewApp}>
|
<ActionButton
|
||||||
|
disabled={$sortedScreens.length === 0}
|
||||||
|
quiet
|
||||||
|
icon="PlayCircle"
|
||||||
|
on:click={previewApp}
|
||||||
|
>
|
||||||
Preview
|
Preview
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -21,7 +21,8 @@
|
||||||
$: schemaComponents = getContextProviderComponents(
|
$: schemaComponents = getContextProviderComponents(
|
||||||
$currentAsset,
|
$currentAsset,
|
||||||
$store.selectedComponentId,
|
$store.selectedComponentId,
|
||||||
"schema"
|
"schema",
|
||||||
|
{ includeSelf: nested }
|
||||||
)
|
)
|
||||||
$: providerOptions = getProviderOptions(formComponents, schemaComponents)
|
$: providerOptions = getProviderOptions(formComponents, schemaComponents)
|
||||||
$: schemaFields = getSchemaFields(parameters?.tableId)
|
$: schemaFields = getSchemaFields(parameters?.tableId)
|
||||||
|
|
|
@ -4,10 +4,15 @@
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import { Helpers } from "@budibase/bbui"
|
import { Helpers } from "@budibase/bbui"
|
||||||
|
import { getEventContextBindings } from "builderStore/dataBinding"
|
||||||
|
|
||||||
|
export let componentInstance
|
||||||
export let componentBindings
|
export let componentBindings
|
||||||
export let bindings
|
export let bindings
|
||||||
export let value
|
export let value
|
||||||
|
export let key
|
||||||
|
export let nested
|
||||||
|
export let max
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -15,12 +20,18 @@
|
||||||
|
|
||||||
$: buttonList = sanitizeValue(value) || []
|
$: buttonList = sanitizeValue(value) || []
|
||||||
$: buttonCount = buttonList.length
|
$: buttonCount = buttonList.length
|
||||||
|
$: eventContextBindings = getEventContextBindings({
|
||||||
|
componentInstance,
|
||||||
|
settingKey: key,
|
||||||
|
})
|
||||||
|
$: allBindings = [...bindings, ...eventContextBindings]
|
||||||
$: itemProps = {
|
$: itemProps = {
|
||||||
componentBindings: componentBindings || [],
|
componentBindings: componentBindings || [],
|
||||||
bindings,
|
bindings: allBindings,
|
||||||
removeButton,
|
removeButton,
|
||||||
canRemove: buttonCount > 1,
|
nested,
|
||||||
}
|
}
|
||||||
|
$: canAddButtons = max == null || buttonList.length < max
|
||||||
|
|
||||||
const sanitizeValue = val => {
|
const sanitizeValue = val => {
|
||||||
return val?.map(button => {
|
return val?.map(button => {
|
||||||
|
@ -86,11 +97,16 @@
|
||||||
focus={focusItem}
|
focus={focusItem}
|
||||||
draggable={buttonCount > 1}
|
draggable={buttonCount > 1}
|
||||||
/>
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="list-footer" on:click={addButton}>
|
<div
|
||||||
|
class="list-footer"
|
||||||
|
class:disabled={!canAddButtons}
|
||||||
|
on:click={addButton}
|
||||||
|
class:empty={!buttonCount}
|
||||||
|
>
|
||||||
<div class="add-button">Add button</div>
|
<div class="add-button">Add button</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -120,15 +136,21 @@
|
||||||
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid));
|
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid));
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
.list-footer.empty {
|
||||||
.add-button {
|
border-radius: 4px;
|
||||||
margin: var(--spacing-s);
|
}
|
||||||
|
.list-footer.disabled {
|
||||||
|
color: var(--spectrum-global-color-gray-500);
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-footer:hover {
|
.list-footer:hover {
|
||||||
background-color: var(
|
background-color: var(
|
||||||
--spectrum-table-row-background-color-hover,
|
--spectrum-table-row-background-color-hover,
|
||||||
var(--spectrum-alias-highlight-hover)
|
var(--spectrum-alias-highlight-hover)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.add-button {
|
||||||
|
margin: var(--spacing-s);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -9,11 +9,33 @@
|
||||||
export let bindings
|
export let bindings
|
||||||
export let anchor
|
export let anchor
|
||||||
export let removeButton
|
export let removeButton
|
||||||
export let canRemove
|
export let nested
|
||||||
|
|
||||||
$: readableText = isJSBinding(item.text)
|
$: readableText = isJSBinding(item.text)
|
||||||
? "(JavaScript function)"
|
? "(JavaScript function)"
|
||||||
: runtimeToReadableBinding([...bindings, componentBindings], item.text)
|
: runtimeToReadableBinding([...bindings, componentBindings], item.text)
|
||||||
|
|
||||||
|
// If this is a nested setting (for example inside a grid or form block) then
|
||||||
|
// we need to mark all the settings of the actual buttons as nested too, to
|
||||||
|
// allow us to reference context provided by the block.
|
||||||
|
// We will need to update this in future if the normal button component
|
||||||
|
// gets broken into multiple settings sections, as we assume a flat array.
|
||||||
|
const updatedNestedFlags = settings => {
|
||||||
|
if (!nested || !settings?.length) {
|
||||||
|
return settings
|
||||||
|
}
|
||||||
|
let newSettings = settings.map(setting => ({
|
||||||
|
...setting,
|
||||||
|
nested: true,
|
||||||
|
}))
|
||||||
|
// We need to prevent bindings for the button names because of how grid
|
||||||
|
// blocks work. This is an edge case but unavoidable.
|
||||||
|
let name = newSettings.find(x => x.key === "text")
|
||||||
|
if (name) {
|
||||||
|
name.disableBindings = true
|
||||||
|
}
|
||||||
|
return newSettings
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="list-item-body">
|
<div class="list-item-body">
|
||||||
|
@ -24,12 +46,12 @@
|
||||||
{componentBindings}
|
{componentBindings}
|
||||||
{bindings}
|
{bindings}
|
||||||
on:change
|
on:change
|
||||||
|
parseSettings={updatedNestedFlags}
|
||||||
/>
|
/>
|
||||||
<div class="field-label">{readableText || "Button"}</div>
|
<div class="field-label">{readableText || "Button"}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-item-right">
|
<div class="list-item-right">
|
||||||
<Icon
|
<Icon
|
||||||
disabled={!canRemove}
|
|
||||||
size="S"
|
size="S"
|
||||||
name="Close"
|
name="Close"
|
||||||
hoverable
|
hoverable
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
export let value
|
export let value
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let sanitisedFields
|
let sanitisedFields
|
||||||
let fieldList
|
let fieldList
|
||||||
let schema
|
let schema
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
export let highlighted = false
|
export let highlighted = false
|
||||||
export let propertyFocus = false
|
export let propertyFocus = false
|
||||||
export let info = null
|
export let info = null
|
||||||
|
export let disableBindings = false
|
||||||
|
|
||||||
$: nullishValue = value == null || value === ""
|
$: nullishValue = value == null || value === ""
|
||||||
$: allBindings = getAllBindings(bindings, componentBindings, nested)
|
$: allBindings = getAllBindings(bindings, componentBindings, nested)
|
||||||
|
@ -99,6 +100,7 @@
|
||||||
{nested}
|
{nested}
|
||||||
{key}
|
{key}
|
||||||
{type}
|
{type}
|
||||||
|
{disableBindings}
|
||||||
{...props}
|
{...props}
|
||||||
on:drawerHide
|
on:drawerHide
|
||||||
on:drawerShow
|
on:drawerShow
|
||||||
|
|
|
@ -32,7 +32,9 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionButton on:click={modal.show}>{layoutMap[value].name}</ActionButton>
|
<ActionButton on:click={modal.show}>
|
||||||
|
{layoutMap[value || "mainSidebar"].name}
|
||||||
|
</ActionButton>
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
<ModalContent
|
<ModalContent
|
||||||
onConfirm={() => dispatch("change", selected)}
|
onConfirm={() => dispatch("change", selected)}
|
||||||
|
|
|
@ -404,7 +404,7 @@
|
||||||
datasource = $datasources.list.find(ds => ds._id === query?.datasourceId)
|
datasource = $datasources.list.find(ds => ds._id === query?.datasourceId)
|
||||||
const datasourceUrl = datasource?.config.url
|
const datasourceUrl = datasource?.config.url
|
||||||
const qs = query?.fields.queryString
|
const qs = query?.fields.queryString
|
||||||
breakQs = restUtils.breakQueryString(encodeURI(qs))
|
breakQs = restUtils.breakQueryString(encodeURI(qs ?? ""))
|
||||||
breakQs = runtimeToReadableMap(mergedBindings, breakQs)
|
breakQs = runtimeToReadableMap(mergedBindings, breakQs)
|
||||||
|
|
||||||
const path = query.fields.path
|
const path = query.fields.path
|
||||||
|
|
|
@ -179,6 +179,7 @@
|
||||||
highlighted={$store.highlightedSettingKey === setting.key}
|
highlighted={$store.highlightedSettingKey === setting.key}
|
||||||
propertyFocus={$store.propertyFocus === setting.key}
|
propertyFocus={$store.propertyFocus === setting.key}
|
||||||
info={setting.info}
|
info={setting.info}
|
||||||
|
disableBindings={setting.disableBindings}
|
||||||
props={{
|
props={{
|
||||||
// Generic settings
|
// Generic settings
|
||||||
placeholder: setting.placeholder || null,
|
placeholder: setting.placeholder || null,
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
<xml version="1.0" />
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
enable-background="new 0 0 48 48"
|
||||||
|
height="48px"
|
||||||
|
viewBox="0 0 48 48"
|
||||||
|
width="48px"
|
||||||
|
xml:space="preserve"
|
||||||
|
>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
d="M9,42H3c-0.552,0-1-0.449-1-1v-3.5C2,37.224,2.224,37,2.5,37S3,37.224,3,37.5V41h6 c0.276,0,0.5,0.224,0.5,0.5S9.276,42,9,42z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M45,42h-6c-0.276,0-0.5-0.224-0.5-0.5S38.724,41,39,41h6V13H3v27c0,0.276-0.224,0.5-0.5,0.5S2,40.276,2,40 V12h44v29C46,41.551,45.552,42,45,42z"
|
||||||
|
/>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
d="M45.5,13h-43C2.224,13,2,12.776,2,12.5v-5C2,6.673,2.673,6,3.5,6h41C45.327,6,46,6.673,46,7.5v5 C46,12.776,45.776,13,45.5,13z M3,12h42V7.5C45,7.224,44.775,7,44.5,7h-41C3.225,7,3,7.224,3,7.5V12z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
d="M16.5,11c-0.827,0-1.5-0.673-1.5-1.5S15.673,8,16.5,8S18,8.673,18,9.5S17.327,11,16.5,11z M16.5,9 C16.225,9,16,9.224,16,9.5s0.225,0.5,0.5,0.5S17,9.776,17,9.5S16.775,9,16.5,9z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
d="M11.5,11c-0.827,0-1.5-0.673-1.5-1.5S10.673,8,11.5,8S13,8.673,13,9.5S12.327,11,11.5,11z M11.5,9 C11.225,9,11,9.224,11,9.5s0.225,0.5,0.5,0.5S12,9.776,12,9.5S11.775,9,11.5,9z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
d="M6.5,11C5.673,11,5,10.327,5,9.5S5.673,8,6.5,8S8,8.673,8,9.5S7.327,11,6.5,11z M6.5,9 C6.225,9,6,9.224,6,9.5S6.225,10,6.5,10S7,9.776,7,9.5S6.775,9,6.5,9z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
d="M35.696,44H12.304c-0.728,0-1.313-0.284-1.605-0.779c-0.289-0.489-0.259-1.126,0.084-1.749L22.58,19.996 c0.709-1.285,2.132-1.285,2.839,0l11.799,21.477v0c0.343,0.623,0.373,1.26,0.084,1.749C37.01,43.716,36.424,44,35.696,44z M24,20 c-0.176,0-0.379,0.179-0.544,0.478L11.659,41.954c-0.168,0.306-0.205,0.582-0.101,0.758C11.667,42.895,11.938,43,12.304,43 h23.393c0.365,0,0.637-0.105,0.745-0.288c0.104-0.177,0.067-0.453-0.101-0.758v0L24.543,20.478C24.379,20.179,24.176,20,24,20z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
d="M24,36L24,36c-0.225,0-0.421-0.15-0.481-0.366C23.456,35.412,22,30.169,22,28c0-1.103,0.897-2,2-2 s2,0.897,2,2c0,2.232-1.457,7.417-1.519,7.636C24.42,35.851,24.224,36,24,36z M24,27c-0.552,0-1,0.449-1,1 c0,1.266,0.569,3.793,1.002,5.531C24.435,31.806,25,29.301,25,28C25,27.449,24.552,27,24,27z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
d="M24,41c-1.103,0-2-0.897-2-2s0.897-2,2-2s2,0.897,2,2S25.103,41,24,41z M24,38c-0.552,0-1,0.449-1,1 s0.448,1,1,1s1-0.449,1-1S24.552,38,24,38z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
svg {
|
||||||
|
fill: var(--ink);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,8 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import { params, goto } from "@roxi/routify"
|
import { params, goto } from "@roxi/routify"
|
||||||
import { apps, auth, sideBarCollapsed } from "stores/portal"
|
import { apps, auth, sideBarCollapsed } from "stores/portal"
|
||||||
import { ActionButton } from "@budibase/bbui"
|
import { Link, Body, ActionButton } from "@budibase/bbui"
|
||||||
import { sdk } from "@budibase/shared-core"
|
import { sdk } from "@budibase/shared-core"
|
||||||
|
import { API } from "api"
|
||||||
|
import ErrorSVG from "./ErrorSVG.svelte"
|
||||||
|
|
||||||
$: app = $apps.find(app => app.appId === $params.appId)
|
$: app = $apps.find(app => app.appId === $params.appId)
|
||||||
$: iframeUrl = getIframeURL(app)
|
$: iframeUrl = getIframeURL(app)
|
||||||
|
@ -14,6 +16,18 @@
|
||||||
}
|
}
|
||||||
return `/${app.devId}`
|
return `/${app.devId}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let noScreens = false
|
||||||
|
|
||||||
|
// Normally fetched in builder/src/pages/builder/app/[application]/_layout.svelte
|
||||||
|
const fetchScreens = async appId => {
|
||||||
|
if (!appId) return
|
||||||
|
|
||||||
|
const pkg = await API.fetchAppPackage(appId)
|
||||||
|
noScreens = pkg.screens.length === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
$: fetchScreens(app?.devId)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -45,6 +59,7 @@
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
{/if}
|
{/if}
|
||||||
<ActionButton
|
<ActionButton
|
||||||
|
disabled={noScreens}
|
||||||
quiet
|
quiet
|
||||||
icon="LinkOut"
|
icon="LinkOut"
|
||||||
on:click={() => window.open(iframeUrl, "_blank")}
|
on:click={() => window.open(iframeUrl, "_blank")}
|
||||||
|
@ -52,7 +67,19 @@
|
||||||
Fullscreen
|
Fullscreen
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
|
{#if noScreens}
|
||||||
|
<div class="noScreens">
|
||||||
|
<ErrorSVG />
|
||||||
|
<Body>You haven't added any screens to your app yet.</Body>
|
||||||
|
<Body>
|
||||||
|
<Link size="L" href={`/builder/app/${app.devId}/design`}
|
||||||
|
>Click here</Link
|
||||||
|
> to add some.
|
||||||
|
</Body>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
<iframe src={iframeUrl} title={app.name} />
|
<iframe src={iframeUrl} title={app.name} />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -64,6 +91,7 @@
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
padding: 0 var(--spacing-l) var(--spacing-l) var(--spacing-l);
|
padding: 0 var(--spacing-l) var(--spacing-l) var(--spacing-l);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
@ -71,9 +99,27 @@
|
||||||
gap: var(--spacing-xs);
|
gap: var(--spacing-xs);
|
||||||
flex: 0 0 50px;
|
flex: 0 0 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
border-radius: var(--spacing-s);
|
border-radius: var(--spacing-s);
|
||||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.noScreens {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noScreens :global(svg) {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -270,7 +270,6 @@
|
||||||
{
|
{
|
||||||
"type": "buttonConfiguration",
|
"type": "buttonConfiguration",
|
||||||
"key": "buttons",
|
"key": "buttons",
|
||||||
"nested": true,
|
|
||||||
"defaultValue": [
|
"defaultValue": [
|
||||||
{
|
{
|
||||||
"type": "cta",
|
"type": "cta",
|
||||||
|
@ -2732,6 +2731,11 @@
|
||||||
"key": "defaultValue",
|
"key": "defaultValue",
|
||||||
"supportsConditions": false
|
"supportsConditions": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Help text",
|
||||||
|
"key": "helpText"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "event",
|
||||||
"label": "On change",
|
"label": "On change",
|
||||||
|
@ -2863,6 +2867,11 @@
|
||||||
"key": "defaultValue",
|
"key": "defaultValue",
|
||||||
"supportsConditions": false
|
"supportsConditions": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Help text",
|
||||||
|
"key": "helpText"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "event",
|
||||||
"label": "On change",
|
"label": "On change",
|
||||||
|
@ -2960,6 +2969,11 @@
|
||||||
"key": "defaultValue",
|
"key": "defaultValue",
|
||||||
"supportsConditions": false
|
"supportsConditions": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Help text",
|
||||||
|
"key": "helpText"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "event",
|
||||||
"label": "On change",
|
"label": "On change",
|
||||||
|
@ -3176,6 +3190,11 @@
|
||||||
"key": "defaultValue",
|
"key": "defaultValue",
|
||||||
"supportsConditions": false
|
"supportsConditions": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Help text",
|
||||||
|
"key": "helpText"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Autocomplete",
|
"label": "Autocomplete",
|
||||||
|
@ -3336,6 +3355,11 @@
|
||||||
"key": "defaultValue",
|
"key": "defaultValue",
|
||||||
"supportsConditions": false
|
"supportsConditions": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Help text",
|
||||||
|
"key": "helpText"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "event",
|
||||||
"label": "On change",
|
"label": "On change",
|
||||||
|
@ -3560,6 +3584,11 @@
|
||||||
"key": "defaultValue",
|
"key": "defaultValue",
|
||||||
"supportsConditions": false
|
"supportsConditions": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Help text",
|
||||||
|
"key": "helpText"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "event",
|
||||||
"label": "On change",
|
"label": "On change",
|
||||||
|
@ -3658,6 +3687,11 @@
|
||||||
"key": "defaultValue",
|
"key": "defaultValue",
|
||||||
"supportsConditions": false
|
"supportsConditions": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Help text",
|
||||||
|
"key": "helpText"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "event",
|
||||||
"label": "On change",
|
"label": "On change",
|
||||||
|
@ -3800,6 +3834,11 @@
|
||||||
"key": "defaultValue",
|
"key": "defaultValue",
|
||||||
"supportsConditions": false
|
"supportsConditions": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Help text",
|
||||||
|
"key": "helpText"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "event",
|
||||||
"label": "On change",
|
"label": "On change",
|
||||||
|
@ -3895,6 +3934,11 @@
|
||||||
"key": "defaultValue",
|
"key": "defaultValue",
|
||||||
"supportsConditions": false
|
"supportsConditions": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Help text",
|
||||||
|
"key": "helpText"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Disabled",
|
"label": "Disabled",
|
||||||
|
@ -4145,6 +4189,11 @@
|
||||||
"label": "Label",
|
"label": "Label",
|
||||||
"key": "label"
|
"key": "label"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Help text",
|
||||||
|
"key": "helpText"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"label": "Extensions",
|
"label": "Extensions",
|
||||||
|
@ -4248,6 +4297,11 @@
|
||||||
"key": "defaultValue",
|
"key": "defaultValue",
|
||||||
"supportsConditions": false
|
"supportsConditions": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Help text",
|
||||||
|
"key": "helpText"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "event",
|
||||||
"label": "On change",
|
"label": "On change",
|
||||||
|
@ -4356,6 +4410,11 @@
|
||||||
"key": "defaultValue",
|
"key": "defaultValue",
|
||||||
"supportsConditions": false
|
"supportsConditions": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Help text",
|
||||||
|
"key": "helpText"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "event",
|
||||||
"label": "On change",
|
"label": "On change",
|
||||||
|
@ -6339,8 +6398,29 @@
|
||||||
"label": "High contrast",
|
"label": "High contrast",
|
||||||
"key": "stripeRows",
|
"key": "stripeRows",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Buttons",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "buttonConfiguration",
|
||||||
|
"key": "buttons",
|
||||||
|
"nested": true,
|
||||||
|
"max": 3,
|
||||||
|
"context": [
|
||||||
|
{
|
||||||
|
"label": "Clicked row",
|
||||||
|
"key": "row"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"context": {
|
||||||
|
"type": "schema"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"bbreferencefield": {
|
"bbreferencefield": {
|
||||||
"devComment": "As bb reference is only used for user subtype for now, we are using user for icon and labels",
|
"devComment": "As bb reference is only used for user subtype for now, we are using user for icon and labels",
|
||||||
|
@ -6375,6 +6455,11 @@
|
||||||
"label": "Default value",
|
"label": "Default value",
|
||||||
"key": "defaultValue"
|
"key": "defaultValue"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Help text",
|
||||||
|
"key": "helpText"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "event",
|
"type": "event",
|
||||||
"label": "On change",
|
"label": "On change",
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// NOTE: this is not a block - it's just named as such to avoid confusing users,
|
// NOTE: this is not a block - it's just named as such to avoid confusing users,
|
||||||
// because it functions similarly to one
|
// because it functions similarly to one
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
import { get } from "svelte/store"
|
||||||
import { Grid } from "@budibase/frontend-core"
|
import { Grid } from "@budibase/frontend-core"
|
||||||
|
|
||||||
// table is actually any datasource, but called table for legacy compatibility
|
// table is actually any datasource, but called table for legacy compatibility
|
||||||
|
@ -16,12 +17,21 @@
|
||||||
export let fixedRowHeight = null
|
export let fixedRowHeight = null
|
||||||
export let columns = null
|
export let columns = null
|
||||||
export let onRowClick = null
|
export let onRowClick = null
|
||||||
|
export let buttons = null
|
||||||
|
|
||||||
|
const context = getContext("context")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
const { styleable, API, builderStore, notificationStore } = getContext("sdk")
|
const {
|
||||||
|
styleable,
|
||||||
|
API,
|
||||||
|
builderStore,
|
||||||
|
notificationStore,
|
||||||
|
enrichButtonActions,
|
||||||
|
} = getContext("sdk")
|
||||||
|
|
||||||
$: columnWhitelist = columns?.map(col => col.name)
|
$: columnWhitelist = columns?.map(col => col.name)
|
||||||
$: schemaOverrides = getSchemaOverrides(columns)
|
$: schemaOverrides = getSchemaOverrides(columns)
|
||||||
|
$: enrichedButtons = enrichButtons(buttons)
|
||||||
|
|
||||||
const getSchemaOverrides = columns => {
|
const getSchemaOverrides = columns => {
|
||||||
let overrides = {}
|
let overrides = {}
|
||||||
|
@ -33,6 +43,25 @@
|
||||||
})
|
})
|
||||||
return overrides
|
return overrides
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const enrichButtons = buttons => {
|
||||||
|
if (!buttons?.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return buttons.map(settings => ({
|
||||||
|
size: "M",
|
||||||
|
text: settings.text,
|
||||||
|
type: settings.type,
|
||||||
|
onClick: async row => {
|
||||||
|
// We add a fake context binding in here, which allows us to pretend
|
||||||
|
// that the grid provides a "schema" binding - that lets us use the
|
||||||
|
// clicked row in things like save row actions
|
||||||
|
const enrichedContext = { ...get(context), [get(component).id]: row }
|
||||||
|
const fn = enrichButtonActions(settings.onClick, enrichedContext)
|
||||||
|
return await fn?.({ row })
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -58,6 +87,7 @@
|
||||||
showControls={false}
|
showControls={false}
|
||||||
notifySuccess={notificationStore.actions.success}
|
notifySuccess={notificationStore.actions.success}
|
||||||
notifyError={notificationStore.actions.error}
|
notifyError={notificationStore.actions.error}
|
||||||
|
buttons={enrichedButtons}
|
||||||
on:rowclick={e => onRowClick?.({ row: e.detail })}
|
on:rowclick={e => onRowClick?.({ row: e.detail })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
export let onChange
|
export let onChange
|
||||||
export let maximum = undefined
|
export let maximum = undefined
|
||||||
export let span
|
export let span
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
|
@ -76,6 +77,7 @@
|
||||||
{readonly}
|
{readonly}
|
||||||
{validation}
|
{validation}
|
||||||
{span}
|
{span}
|
||||||
|
{helpText}
|
||||||
type="attachment"
|
type="attachment"
|
||||||
bind:fieldState
|
bind:fieldState
|
||||||
bind:fieldApi
|
bind:fieldApi
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
export let validation
|
export let validation
|
||||||
export let defaultValue
|
export let defaultValue
|
||||||
export let onChange
|
export let onChange
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
|
@ -42,6 +43,7 @@
|
||||||
{disabled}
|
{disabled}
|
||||||
{readonly}
|
{readonly}
|
||||||
{validation}
|
{validation}
|
||||||
|
{helpText}
|
||||||
defaultValue={isTruthy(defaultValue)}
|
defaultValue={isTruthy(defaultValue)}
|
||||||
type="boolean"
|
type="boolean"
|
||||||
bind:fieldState
|
bind:fieldState
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
export let beepFrequency
|
export let beepFrequency
|
||||||
export let customFrequency
|
export let customFrequency
|
||||||
export let preferredCamera
|
export let preferredCamera
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
|
@ -38,6 +39,7 @@
|
||||||
{validation}
|
{validation}
|
||||||
{defaultValue}
|
{defaultValue}
|
||||||
{type}
|
{type}
|
||||||
|
{helpText}
|
||||||
bind:fieldState
|
bind:fieldState
|
||||||
bind:fieldApi
|
bind:fieldApi
|
||||||
>
|
>
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
export let defaultValue
|
export let defaultValue
|
||||||
export let onChange
|
export let onChange
|
||||||
export let span
|
export let span
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
|
@ -35,6 +36,7 @@
|
||||||
{validation}
|
{validation}
|
||||||
{defaultValue}
|
{defaultValue}
|
||||||
{span}
|
{span}
|
||||||
|
{helpText}
|
||||||
type="datetime"
|
type="datetime"
|
||||||
bind:fieldState
|
bind:fieldState
|
||||||
bind:fieldApi
|
bind:fieldApi
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import Placeholder from "../Placeholder.svelte"
|
import Placeholder from "../Placeholder.svelte"
|
||||||
import { getContext, onDestroy } from "svelte"
|
import { getContext, onDestroy } from "svelte"
|
||||||
|
import { Icon } from "@budibase/bbui"
|
||||||
|
|
||||||
export let label
|
export let label
|
||||||
export let field
|
export let field
|
||||||
|
@ -13,6 +14,7 @@
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let validation
|
export let validation
|
||||||
export let span = 6
|
export let span = 6
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
// Get contexts
|
// Get contexts
|
||||||
const formContext = getContext("form")
|
const formContext = getContext("form")
|
||||||
|
@ -97,7 +99,14 @@
|
||||||
{:else}
|
{:else}
|
||||||
<slot />
|
<slot />
|
||||||
{#if fieldState.error}
|
{#if fieldState.error}
|
||||||
<div class="error">{fieldState.error}</div>
|
<div class="error">
|
||||||
|
<Icon name="Alert" />
|
||||||
|
<span>{fieldState.error}</span>
|
||||||
|
</div>
|
||||||
|
{:else if helpText}
|
||||||
|
<div class="helpText">
|
||||||
|
<Icon name="HelpOutline" /> <span>{helpText}</span>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -127,13 +136,45 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
|
display: flex;
|
||||||
|
margin-top: var(--spectrum-global-dimension-size-75);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error :global(svg) {
|
||||||
|
width: 14px;
|
||||||
|
color: var(
|
||||||
|
--spectrum-semantic-negative-color-default,
|
||||||
|
var(--spectrum-global-color-red-500)
|
||||||
|
);
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error span {
|
||||||
color: var(
|
color: var(
|
||||||
--spectrum-semantic-negative-color-default,
|
--spectrum-semantic-negative-color-default,
|
||||||
var(--spectrum-global-color-red-500)
|
var(--spectrum-global-color-red-500)
|
||||||
);
|
);
|
||||||
font-size: var(--spectrum-global-dimension-font-size-75);
|
font-size: var(--spectrum-global-dimension-font-size-75);
|
||||||
|
}
|
||||||
|
|
||||||
|
.helpText {
|
||||||
|
display: flex;
|
||||||
margin-top: var(--spectrum-global-dimension-size-75);
|
margin-top: var(--spectrum-global-dimension-size-75);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.helpText :global(svg) {
|
||||||
|
width: 14px;
|
||||||
|
color: var(--grey-7);
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.helpText span {
|
||||||
|
color: var(--grey-5);
|
||||||
|
font-size: var(--spectrum-global-dimension-font-size-75);
|
||||||
}
|
}
|
||||||
.spectrum-FieldLabel--right,
|
.spectrum-FieldLabel--right,
|
||||||
.spectrum-FieldLabel--left {
|
.spectrum-FieldLabel--left {
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let defaultValue = ""
|
export let defaultValue = ""
|
||||||
export let onChange
|
export let onChange
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
const validation = [
|
const validation = [
|
||||||
|
@ -52,6 +53,7 @@
|
||||||
{readonly}
|
{readonly}
|
||||||
{validation}
|
{validation}
|
||||||
{defaultValue}
|
{defaultValue}
|
||||||
|
{helpText}
|
||||||
type="json"
|
type="json"
|
||||||
bind:fieldState
|
bind:fieldState
|
||||||
bind:fieldApi
|
bind:fieldApi
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
export let defaultValue = ""
|
export let defaultValue = ""
|
||||||
export let format = "auto"
|
export let format = "auto"
|
||||||
export let onChange
|
export let onChange
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
|
@ -62,6 +63,7 @@
|
||||||
{readonly}
|
{readonly}
|
||||||
{validation}
|
{validation}
|
||||||
{defaultValue}
|
{defaultValue}
|
||||||
|
{helpText}
|
||||||
type="longform"
|
type="longform"
|
||||||
bind:fieldState
|
bind:fieldState
|
||||||
bind:fieldApi
|
bind:fieldApi
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
export let optionsType = "select"
|
export let optionsType = "select"
|
||||||
export let direction = "vertical"
|
export let direction = "vertical"
|
||||||
export let span
|
export let span
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
|
@ -60,6 +61,7 @@
|
||||||
{readonly}
|
{readonly}
|
||||||
{validation}
|
{validation}
|
||||||
{span}
|
{span}
|
||||||
|
{helpText}
|
||||||
defaultValue={expandedDefaultValue}
|
defaultValue={expandedDefaultValue}
|
||||||
type="array"
|
type="array"
|
||||||
bind:fieldState
|
bind:fieldState
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
export let onChange
|
export let onChange
|
||||||
export let sort = true
|
export let sort = true
|
||||||
export let span
|
export let span
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
|
@ -51,6 +52,7 @@
|
||||||
{validation}
|
{validation}
|
||||||
{defaultValue}
|
{defaultValue}
|
||||||
{span}
|
{span}
|
||||||
|
{helpText}
|
||||||
type="options"
|
type="options"
|
||||||
bind:fieldState
|
bind:fieldState
|
||||||
bind:fieldApi
|
bind:fieldApi
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
export let datasourceType = "table"
|
export let datasourceType = "table"
|
||||||
export let primaryDisplay
|
export let primaryDisplay
|
||||||
export let span
|
export let span
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
|
@ -192,6 +193,7 @@
|
||||||
defaultValue={expandedDefaultValue}
|
defaultValue={expandedDefaultValue}
|
||||||
{type}
|
{type}
|
||||||
{span}
|
{span}
|
||||||
|
{helpText}
|
||||||
bind:fieldState
|
bind:fieldState
|
||||||
bind:fieldApi
|
bind:fieldApi
|
||||||
bind:fieldSchema
|
bind:fieldSchema
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
export let align
|
export let align
|
||||||
export let onChange
|
export let onChange
|
||||||
export let span
|
export let span
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
|
@ -33,6 +34,7 @@
|
||||||
{validation}
|
{validation}
|
||||||
{defaultValue}
|
{defaultValue}
|
||||||
{span}
|
{span}
|
||||||
|
{helpText}
|
||||||
type={type === "number" ? "number" : "string"}
|
type={type === "number" ? "number" : "string"}
|
||||||
bind:fieldState
|
bind:fieldState
|
||||||
bind:fieldApi
|
bind:fieldApi
|
||||||
|
@ -44,7 +46,6 @@
|
||||||
on:change={handleChange}
|
on:change={handleChange}
|
||||||
disabled={fieldState.disabled}
|
disabled={fieldState.disabled}
|
||||||
readonly={fieldState.readonly}
|
readonly={fieldState.readonly}
|
||||||
error={fieldState.error}
|
|
||||||
id={fieldState.fieldId}
|
id={fieldState.fieldId}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
{type}
|
{type}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
dndIsDragging,
|
dndIsDragging,
|
||||||
confirmationStore,
|
confirmationStore,
|
||||||
roleStore,
|
roleStore,
|
||||||
|
stateStore,
|
||||||
} from "stores"
|
} from "stores"
|
||||||
import { styleable } from "utils/styleable"
|
import { styleable } from "utils/styleable"
|
||||||
import { linkable } from "utils/linkable"
|
import { linkable } from "utils/linkable"
|
||||||
|
@ -24,9 +25,13 @@ import BlockComponent from "components/BlockComponent.svelte"
|
||||||
import { ActionTypes } from "./constants"
|
import { ActionTypes } from "./constants"
|
||||||
import { fetchDatasourceSchema } from "./utils/schema.js"
|
import { fetchDatasourceSchema } from "./utils/schema.js"
|
||||||
import { getAPIKey } from "./utils/api.js"
|
import { getAPIKey } from "./utils/api.js"
|
||||||
|
import { enrichButtonActions } from "./utils/buttonActions.js"
|
||||||
|
import { processStringSync, makePropSafe } from "@budibase/string-templates"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
API,
|
API,
|
||||||
|
|
||||||
|
// Stores
|
||||||
authStore,
|
authStore,
|
||||||
notificationStore,
|
notificationStore,
|
||||||
routeStore,
|
routeStore,
|
||||||
|
@ -41,13 +46,23 @@ export default {
|
||||||
currentRole,
|
currentRole,
|
||||||
confirmationStore,
|
confirmationStore,
|
||||||
roleStore,
|
roleStore,
|
||||||
|
stateStore,
|
||||||
|
|
||||||
|
// Utils
|
||||||
styleable,
|
styleable,
|
||||||
linkable,
|
linkable,
|
||||||
getAction,
|
getAction,
|
||||||
fetchDatasourceSchema,
|
fetchDatasourceSchema,
|
||||||
Provider,
|
|
||||||
ActionTypes,
|
|
||||||
getAPIKey,
|
getAPIKey,
|
||||||
|
enrichButtonActions,
|
||||||
|
processStringSync,
|
||||||
|
makePropSafe,
|
||||||
|
|
||||||
|
// Components
|
||||||
|
Provider,
|
||||||
Block,
|
Block,
|
||||||
BlockComponent,
|
BlockComponent,
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
ActionTypes,
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
$: style = getStyle(width, selectedUser)
|
$: style = getStyle(width, selectedUser)
|
||||||
|
|
||||||
const getStyle = (width, selectedUser) => {
|
const getStyle = (width, selectedUser) => {
|
||||||
let style = `flex: 0 0 ${width}px;`
|
let style = width === "auto" ? "width: auto;" : `flex: 0 0 ${width}px;`
|
||||||
if (selectedUser) {
|
if (selectedUser) {
|
||||||
style += `--user-color:${selectedUser.color};`
|
style += `--user-color:${selectedUser.color};`
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
<script>
|
||||||
|
import { getContext, onMount } from "svelte"
|
||||||
|
import { Button } from "@budibase/bbui"
|
||||||
|
import GridCell from "../cells/GridCell.svelte"
|
||||||
|
import GridScrollWrapper from "./GridScrollWrapper.svelte"
|
||||||
|
|
||||||
|
const {
|
||||||
|
renderedRows,
|
||||||
|
hoveredRowId,
|
||||||
|
props,
|
||||||
|
width,
|
||||||
|
rows,
|
||||||
|
focusedRow,
|
||||||
|
selectedRows,
|
||||||
|
visibleColumns,
|
||||||
|
scroll,
|
||||||
|
isDragging,
|
||||||
|
buttonColumnWidth,
|
||||||
|
} = getContext("grid")
|
||||||
|
|
||||||
|
let measureContainer
|
||||||
|
|
||||||
|
$: buttons = $props.buttons?.slice(0, 3) || []
|
||||||
|
$: columnsWidth = $visibleColumns.reduce(
|
||||||
|
(total, col) => (total += col.width),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
$: end = columnsWidth - 1 - $scroll.left
|
||||||
|
$: left = Math.min($width - $buttonColumnWidth, end)
|
||||||
|
|
||||||
|
const handleClick = async (button, row) => {
|
||||||
|
await button.onClick?.(rows.actions.cleanRow(row))
|
||||||
|
// Refresh the row in case it changed
|
||||||
|
await rows.actions.refreshRow(row._id)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
const observer = new ResizeObserver(entries => {
|
||||||
|
const width = entries?.[0]?.contentRect?.width ?? 0
|
||||||
|
buttonColumnWidth.set(width)
|
||||||
|
})
|
||||||
|
observer.observe(measureContainer)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Hidden copy of buttons to measure -->
|
||||||
|
<div class="measure" bind:this={measureContainer}>
|
||||||
|
<GridCell width="auto">
|
||||||
|
<div class="buttons">
|
||||||
|
{#each buttons as button}
|
||||||
|
<Button size="S">
|
||||||
|
{button.text || "Button"}
|
||||||
|
</Button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</GridCell>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="button-column"
|
||||||
|
style="left:{left}px"
|
||||||
|
class:hidden={$buttonColumnWidth === 0}
|
||||||
|
>
|
||||||
|
<div class="content" on:mouseleave={() => ($hoveredRowId = null)}>
|
||||||
|
<GridScrollWrapper scrollVertically attachHandlers>
|
||||||
|
{#each $renderedRows as row}
|
||||||
|
{@const rowSelected = !!$selectedRows[row._id]}
|
||||||
|
{@const rowHovered = $hoveredRowId === row._id}
|
||||||
|
{@const rowFocused = $focusedRow?._id === row._id}
|
||||||
|
<div
|
||||||
|
class="row"
|
||||||
|
on:mouseenter={$isDragging ? null : () => ($hoveredRowId = row._id)}
|
||||||
|
on:mouseleave={$isDragging ? null : () => ($hoveredRowId = null)}
|
||||||
|
>
|
||||||
|
<GridCell
|
||||||
|
width="auto"
|
||||||
|
rowIdx={row.__idx}
|
||||||
|
selected={rowSelected}
|
||||||
|
highlighted={rowHovered || rowFocused}
|
||||||
|
>
|
||||||
|
<div class="buttons">
|
||||||
|
{#each buttons as button}
|
||||||
|
<Button
|
||||||
|
newStyles
|
||||||
|
size="S"
|
||||||
|
cta={button.type === "cta"}
|
||||||
|
primary={button.type === "primary"}
|
||||||
|
secondary={button.type === "secondary"}
|
||||||
|
warning={button.type === "warning"}
|
||||||
|
overBackground={button.type === "overBackground"}
|
||||||
|
on:click={() => handleClick(button, row)}
|
||||||
|
>
|
||||||
|
{button.text || "Button"}
|
||||||
|
</Button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</GridCell>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</GridScrollWrapper>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.button-column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: var(--cell-background);
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.button-column.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
position: relative;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 var(--cell-padding);
|
||||||
|
gap: var(--cell-padding);
|
||||||
|
height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add left cell border */
|
||||||
|
.button-column :global(.cell) {
|
||||||
|
border-left: var(--cell-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hidden copy of buttons to measure width against */
|
||||||
|
.measure {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -48,6 +48,7 @@
|
||||||
export let fixedRowHeight = null
|
export let fixedRowHeight = null
|
||||||
export let notifySuccess = null
|
export let notifySuccess = null
|
||||||
export let notifyError = null
|
export let notifyError = null
|
||||||
|
export let buttons = null
|
||||||
|
|
||||||
// Unique identifier for DOM nodes inside this instance
|
// Unique identifier for DOM nodes inside this instance
|
||||||
const rand = Math.random()
|
const rand = Math.random()
|
||||||
|
@ -99,6 +100,7 @@
|
||||||
fixedRowHeight,
|
fixedRowHeight,
|
||||||
notifySuccess,
|
notifySuccess,
|
||||||
notifyError,
|
notifyError,
|
||||||
|
buttons,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Set context for children to consume
|
// Set context for children to consume
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import GridScrollWrapper from "./GridScrollWrapper.svelte"
|
import GridScrollWrapper from "./GridScrollWrapper.svelte"
|
||||||
import GridRow from "./GridRow.svelte"
|
import GridRow from "./GridRow.svelte"
|
||||||
import { BlankRowID } from "../lib/constants"
|
import { BlankRowID } from "../lib/constants"
|
||||||
|
import ButtonColumn from "./ButtonColumn.svelte"
|
||||||
|
|
||||||
const {
|
const {
|
||||||
bounds,
|
bounds,
|
||||||
|
@ -13,6 +14,7 @@
|
||||||
dispatch,
|
dispatch,
|
||||||
isDragging,
|
isDragging,
|
||||||
config,
|
config,
|
||||||
|
props,
|
||||||
} = getContext("grid")
|
} = getContext("grid")
|
||||||
|
|
||||||
let body
|
let body
|
||||||
|
@ -54,6 +56,9 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</GridScrollWrapper>
|
</GridScrollWrapper>
|
||||||
|
{#if $props.buttons?.length}
|
||||||
|
<ButtonColumn />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -314,8 +314,12 @@ export const createActions = context => {
|
||||||
|
|
||||||
// Refreshes a specific row
|
// Refreshes a specific row
|
||||||
const refreshRow = async id => {
|
const refreshRow = async id => {
|
||||||
|
try {
|
||||||
const row = await datasource.actions.getRow(id)
|
const row = await datasource.actions.getRow(id)
|
||||||
replaceRow(id, row)
|
replaceRow(id, row)
|
||||||
|
} catch {
|
||||||
|
// Do nothing - we probably just don't support refreshing individual rows
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refreshes all data
|
// Refreshes all data
|
||||||
|
|
|
@ -20,8 +20,15 @@ export const createStores = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deriveStores = context => {
|
export const deriveStores = context => {
|
||||||
const { rows, visibleColumns, stickyColumn, rowHeight, width, height } =
|
const {
|
||||||
context
|
rows,
|
||||||
|
visibleColumns,
|
||||||
|
stickyColumn,
|
||||||
|
rowHeight,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
buttonColumnWidth,
|
||||||
|
} = context
|
||||||
|
|
||||||
// Memoize store primitives
|
// Memoize store primitives
|
||||||
const stickyColumnWidth = derived(stickyColumn, $col => $col?.width || 0, 0)
|
const stickyColumnWidth = derived(stickyColumn, $col => $col?.width || 0, 0)
|
||||||
|
@ -40,9 +47,10 @@ export const deriveStores = context => {
|
||||||
|
|
||||||
// Derive horizontal limits
|
// Derive horizontal limits
|
||||||
const contentWidth = derived(
|
const contentWidth = derived(
|
||||||
[visibleColumns, stickyColumnWidth],
|
[visibleColumns, stickyColumnWidth, buttonColumnWidth],
|
||||||
([$visibleColumns, $stickyColumnWidth]) => {
|
([$visibleColumns, $stickyColumnWidth, $buttonColumnWidth]) => {
|
||||||
let width = GutterWidth + Padding + $stickyColumnWidth
|
const space = Math.max(Padding, $buttonColumnWidth - 1)
|
||||||
|
let width = GutterWidth + space + $stickyColumnWidth
|
||||||
$visibleColumns.forEach(col => {
|
$visibleColumns.forEach(col => {
|
||||||
width += col.width
|
width += col.width
|
||||||
})
|
})
|
||||||
|
|
|
@ -18,6 +18,7 @@ export const createStores = context => {
|
||||||
const previousFocusedRowId = writable(null)
|
const previousFocusedRowId = writable(null)
|
||||||
const gridFocused = writable(false)
|
const gridFocused = writable(false)
|
||||||
const isDragging = writable(false)
|
const isDragging = writable(false)
|
||||||
|
const buttonColumnWidth = writable(0)
|
||||||
|
|
||||||
// Derive the current focused row ID
|
// Derive the current focused row ID
|
||||||
const focusedRowId = derived(
|
const focusedRowId = derived(
|
||||||
|
@ -51,6 +52,7 @@ export const createStores = context => {
|
||||||
rowHeight,
|
rowHeight,
|
||||||
gridFocused,
|
gridFocused,
|
||||||
isDragging,
|
isDragging,
|
||||||
|
buttonColumnWidth,
|
||||||
selectedRows: {
|
selectedRows: {
|
||||||
...selectedRows,
|
...selectedRows,
|
||||||
actions: {
|
actions: {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
const { Curl } = require("../../curl")
|
const { Curl } = require("../../curl")
|
||||||
const fs = require("fs")
|
const fs = require("fs")
|
||||||
const path = require('path')
|
const path = require("path")
|
||||||
|
|
||||||
const getData = (file) => {
|
const getData = file => {
|
||||||
return fs.readFileSync(path.join(__dirname, `./data/${file}.txt`), "utf8")
|
return fs.readFileSync(path.join(__dirname, `./data/${file}.txt`), "utf8")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ describe("Curl Import", () => {
|
||||||
expect(supported).toBe(false)
|
expect(supported).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
const init = async (file) => {
|
const init = async file => {
|
||||||
await curl.isSupported(getData(file))
|
await curl.isSupported(getData(file))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,8 +39,7 @@ describe("Curl Import", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Returns queries", () => {
|
describe("Returns queries", () => {
|
||||||
|
const getQueries = async file => {
|
||||||
const getQueries = async (file) => {
|
|
||||||
await init(file)
|
await init(file)
|
||||||
const queries = await curl.getQueries()
|
const queries = await curl.getQueries()
|
||||||
expect(queries.length).toBe(1)
|
expect(queries.length).toBe(1)
|
||||||
|
@ -77,7 +76,10 @@ describe("Curl Import", () => {
|
||||||
|
|
||||||
it("populates headers", async () => {
|
it("populates headers", async () => {
|
||||||
await testHeaders("get", {})
|
await testHeaders("get", {})
|
||||||
await testHeaders("headers", { "x-bb-header-1" : "123", "x-bb-header-2" : "456"} )
|
await testHeaders("headers", {
|
||||||
|
"x-bb-header-1": "123",
|
||||||
|
"x-bb-header-2": "456",
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const testQuery = async (file, queryString) => {
|
const testQuery = async (file, queryString) => {
|
||||||
|
@ -91,12 +93,14 @@ describe("Curl Import", () => {
|
||||||
|
|
||||||
const testBody = async (file, body) => {
|
const testBody = async (file, body) => {
|
||||||
const queries = await getQueries(file)
|
const queries = await getQueries(file)
|
||||||
expect(queries[0].fields.requestBody).toStrictEqual(JSON.stringify(body, null, 2))
|
expect(queries[0].fields.requestBody).toStrictEqual(
|
||||||
|
JSON.stringify(body, null, 2)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
it("populates body", async () => {
|
it("populates body", async () => {
|
||||||
await testBody("get", undefined)
|
await testBody("get", undefined)
|
||||||
await testBody("post", { "key" : "val" })
|
await testBody("post", { key: "val" })
|
||||||
await testBody("empty-body", {})
|
await testBody("empty-body", {})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
const { OpenAPI2 } = require("../../openapi2")
|
const { OpenAPI2 } = require("../../openapi2")
|
||||||
const fs = require("fs")
|
const fs = require("fs")
|
||||||
const path = require('path')
|
const path = require("path")
|
||||||
|
|
||||||
const getData = (file, extension) => {
|
const getData = (file, extension) => {
|
||||||
return fs.readFileSync(path.join(__dirname, `./data/${file}/${file}.${extension}`), "utf8")
|
return fs.readFileSync(
|
||||||
|
path.join(__dirname, `./data/${file}/${file}.${extension}`),
|
||||||
|
"utf8"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("OpenAPI2 Import", () => {
|
describe("OpenAPI2 Import", () => {
|
||||||
|
@ -49,7 +52,7 @@ describe("OpenAPI2 Import", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Returns queries", () => {
|
describe("Returns queries", () => {
|
||||||
const indexQueries = (queries) => {
|
const indexQueries = queries => {
|
||||||
return queries.reduce((acc, query) => {
|
return queries.reduce((acc, query) => {
|
||||||
acc[query.name] = query
|
acc[query.name] = query
|
||||||
return acc
|
return acc
|
||||||
|
@ -72,12 +75,12 @@ describe("OpenAPI2 Import", () => {
|
||||||
|
|
||||||
it("populates verb", async () => {
|
it("populates verb", async () => {
|
||||||
const assertions = {
|
const assertions = {
|
||||||
"createEntity" : "create",
|
createEntity: "create",
|
||||||
"getEntities" : "read",
|
getEntities: "read",
|
||||||
"getEntity" : "read",
|
getEntity: "read",
|
||||||
"updateEntity" : "update",
|
updateEntity: "update",
|
||||||
"patchEntity" : "patch",
|
patchEntity: "patch",
|
||||||
"deleteEntity" : "delete"
|
deleteEntity: "delete",
|
||||||
}
|
}
|
||||||
await runTests("crud", testVerb, assertions)
|
await runTests("crud", testVerb, assertions)
|
||||||
})
|
})
|
||||||
|
@ -91,12 +94,12 @@ describe("OpenAPI2 Import", () => {
|
||||||
|
|
||||||
it("populates path", async () => {
|
it("populates path", async () => {
|
||||||
const assertions = {
|
const assertions = {
|
||||||
"createEntity" : "http://example.com/entities",
|
createEntity: "http://example.com/entities",
|
||||||
"getEntities" : "http://example.com/entities",
|
getEntities: "http://example.com/entities",
|
||||||
"getEntity" : "http://example.com/entities/{{entityId}}",
|
getEntity: "http://example.com/entities/{{entityId}}",
|
||||||
"updateEntity" : "http://example.com/entities/{{entityId}}",
|
updateEntity: "http://example.com/entities/{{entityId}}",
|
||||||
"patchEntity" : "http://example.com/entities/{{entityId}}",
|
patchEntity: "http://example.com/entities/{{entityId}}",
|
||||||
"deleteEntity" : "http://example.com/entities/{{entityId}}"
|
deleteEntity: "http://example.com/entities/{{entityId}}",
|
||||||
}
|
}
|
||||||
await runTests("crud", testPath, assertions)
|
await runTests("crud", testPath, assertions)
|
||||||
})
|
})
|
||||||
|
@ -114,22 +117,20 @@ describe("OpenAPI2 Import", () => {
|
||||||
|
|
||||||
it("populates headers", async () => {
|
it("populates headers", async () => {
|
||||||
const assertions = {
|
const assertions = {
|
||||||
"createEntity" : {
|
createEntity: {
|
||||||
...contentTypeHeader
|
...contentTypeHeader,
|
||||||
},
|
},
|
||||||
"getEntities" : {
|
getEntities: {},
|
||||||
|
getEntity: {},
|
||||||
|
updateEntity: {
|
||||||
|
...contentTypeHeader,
|
||||||
},
|
},
|
||||||
"getEntity" : {
|
patchEntity: {
|
||||||
|
...contentTypeHeader,
|
||||||
},
|
},
|
||||||
"updateEntity" : {
|
deleteEntity: {
|
||||||
...contentTypeHeader
|
|
||||||
},
|
|
||||||
"patchEntity" : {
|
|
||||||
...contentTypeHeader
|
|
||||||
},
|
|
||||||
"deleteEntity" : {
|
|
||||||
"x-api-key": "{{x-api-key}}",
|
"x-api-key": "{{x-api-key}}",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
await runTests("crud", testHeaders, assertions)
|
await runTests("crud", testHeaders, assertions)
|
||||||
|
@ -138,18 +139,20 @@ describe("OpenAPI2 Import", () => {
|
||||||
const testQuery = async (file, extension, assertions) => {
|
const testQuery = async (file, extension, assertions) => {
|
||||||
const queries = await getQueries(file, extension)
|
const queries = await getQueries(file, extension)
|
||||||
for (let [operationId, queryString] of Object.entries(assertions)) {
|
for (let [operationId, queryString] of Object.entries(assertions)) {
|
||||||
expect(queries[operationId].fields.queryString).toStrictEqual(queryString)
|
expect(queries[operationId].fields.queryString).toStrictEqual(
|
||||||
|
queryString
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
it("populates query", async () => {
|
it("populates query", async () => {
|
||||||
const assertions = {
|
const assertions = {
|
||||||
"createEntity" : "",
|
createEntity: "",
|
||||||
"getEntities" : "page={{page}}&size={{size}}",
|
getEntities: "page={{page}}&size={{size}}",
|
||||||
"getEntity" : "",
|
getEntity: "",
|
||||||
"updateEntity" : "",
|
updateEntity: "",
|
||||||
"patchEntity" : "",
|
patchEntity: "",
|
||||||
"deleteEntity" : ""
|
deleteEntity: "",
|
||||||
}
|
}
|
||||||
await runTests("crud", testQuery, assertions)
|
await runTests("crud", testQuery, assertions)
|
||||||
})
|
})
|
||||||
|
@ -163,45 +166,45 @@ describe("OpenAPI2 Import", () => {
|
||||||
|
|
||||||
it("populates parameters", async () => {
|
it("populates parameters", async () => {
|
||||||
const assertions = {
|
const assertions = {
|
||||||
"createEntity" : [],
|
createEntity: [],
|
||||||
"getEntities" : [
|
getEntities: [
|
||||||
{
|
{
|
||||||
"name" : "page",
|
name: "page",
|
||||||
"default" : "",
|
default: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name" : "size",
|
name: "size",
|
||||||
"default" : "",
|
default: "",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"getEntity" : [
|
getEntity: [
|
||||||
{
|
{
|
||||||
"name" : "entityId",
|
name: "entityId",
|
||||||
"default" : "",
|
default: "",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"updateEntity" : [
|
updateEntity: [
|
||||||
{
|
{
|
||||||
"name" : "entityId",
|
name: "entityId",
|
||||||
"default" : "",
|
default: "",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"patchEntity" : [
|
patchEntity: [
|
||||||
{
|
{
|
||||||
"name" : "entityId",
|
name: "entityId",
|
||||||
"default" : "",
|
default: "",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"deleteEntity" : [
|
deleteEntity: [
|
||||||
{
|
{
|
||||||
"name" : "entityId",
|
name: "entityId",
|
||||||
"default" : "",
|
default: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name" : "x-api-key",
|
name: "x-api-key",
|
||||||
"default" : "",
|
default: "",
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
await runTests("crud", testParameters, assertions)
|
await runTests("crud", testParameters, assertions)
|
||||||
})
|
})
|
||||||
|
@ -209,28 +212,30 @@ describe("OpenAPI2 Import", () => {
|
||||||
const testBody = async (file, extension, assertions) => {
|
const testBody = async (file, extension, assertions) => {
|
||||||
const queries = await getQueries(file, extension)
|
const queries = await getQueries(file, extension)
|
||||||
for (let [operationId, body] of Object.entries(assertions)) {
|
for (let [operationId, body] of Object.entries(assertions)) {
|
||||||
expect(queries[operationId].fields.requestBody).toStrictEqual(JSON.stringify(body, null, 2))
|
expect(queries[operationId].fields.requestBody).toStrictEqual(
|
||||||
|
JSON.stringify(body, null, 2)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
it("populates body", async () => {
|
it("populates body", async () => {
|
||||||
const assertions = {
|
const assertions = {
|
||||||
"createEntity" : {
|
createEntity: {
|
||||||
"name" : "name",
|
name: "name",
|
||||||
"type" : "type",
|
type: "type",
|
||||||
},
|
},
|
||||||
"getEntities" : undefined,
|
getEntities: undefined,
|
||||||
"getEntity" : undefined,
|
getEntity: undefined,
|
||||||
"updateEntity" : {
|
updateEntity: {
|
||||||
"id": 1,
|
id: 1,
|
||||||
"name" : "name",
|
name: "name",
|
||||||
"type" : "type",
|
type: "type",
|
||||||
},
|
},
|
||||||
"patchEntity" : {
|
patchEntity: {
|
||||||
"id": 1,
|
id: 1,
|
||||||
"name" : "name",
|
name: "name",
|
||||||
"type" : "type",
|
type: "type",
|
||||||
},
|
},
|
||||||
"deleteEntity" : undefined
|
deleteEntity: undefined,
|
||||||
}
|
}
|
||||||
await runTests("crud", testBody, assertions)
|
await runTests("crud", testBody, assertions)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
const TestConfig = require("../../../../../tests/utilities/TestConfiguration")
|
const TestConfig = require("../../../../../tests/utilities/TestConfiguration")
|
||||||
const { RestImporter } = require("../index")
|
const { RestImporter } = require("../index")
|
||||||
const fs = require("fs")
|
const fs = require("fs")
|
||||||
const path = require('path')
|
const path = require("path")
|
||||||
const { events } = require("@budibase/backend-core")
|
const { events } = require("@budibase/backend-core")
|
||||||
|
|
||||||
const getData = (file) => {
|
const getData = file => {
|
||||||
return fs.readFileSync(path.join(__dirname, `../sources/tests/${file}`), "utf8")
|
return fs.readFileSync(
|
||||||
|
path.join(__dirname, `../sources/tests/${file}`),
|
||||||
|
"utf8"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// openapi2 (swagger)
|
// openapi2 (swagger)
|
||||||
|
@ -35,7 +38,7 @@ const datasets = {
|
||||||
oapi3PetstoreJson,
|
oapi3PetstoreJson,
|
||||||
oapi3PetstoreYaml,
|
oapi3PetstoreYaml,
|
||||||
// curl
|
// curl
|
||||||
curl
|
curl,
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("Rest Importer", () => {
|
describe("Rest Importer", () => {
|
||||||
|
@ -47,7 +50,7 @@ describe("Rest Importer", () => {
|
||||||
|
|
||||||
let restImporter
|
let restImporter
|
||||||
|
|
||||||
const init = async (data) => {
|
const init = async data => {
|
||||||
restImporter = new RestImporter(data)
|
restImporter = new RestImporter(data)
|
||||||
await restImporter.init()
|
await restImporter.init()
|
||||||
}
|
}
|
||||||
|
@ -69,35 +72,35 @@ describe("Rest Importer", () => {
|
||||||
it("gets info", async () => {
|
it("gets info", async () => {
|
||||||
const assertions = {
|
const assertions = {
|
||||||
// openapi2 (swagger)
|
// openapi2 (swagger)
|
||||||
"oapi2CrudJson" : {
|
oapi2CrudJson: {
|
||||||
name: "CRUD",
|
name: "CRUD",
|
||||||
},
|
},
|
||||||
"oapi2CrudYaml" : {
|
oapi2CrudYaml: {
|
||||||
name: "CRUD",
|
name: "CRUD",
|
||||||
},
|
},
|
||||||
"oapi2PetstoreJson" : {
|
oapi2PetstoreJson: {
|
||||||
name: "Swagger Petstore",
|
name: "Swagger Petstore",
|
||||||
},
|
},
|
||||||
"oapi2PetstoreYaml" :{
|
oapi2PetstoreYaml: {
|
||||||
name: "Swagger Petstore",
|
name: "Swagger Petstore",
|
||||||
},
|
},
|
||||||
// openapi3
|
// openapi3
|
||||||
"oapi3CrudJson" : {
|
oapi3CrudJson: {
|
||||||
name: "CRUD",
|
name: "CRUD",
|
||||||
},
|
},
|
||||||
"oapi3CrudYaml" : {
|
oapi3CrudYaml: {
|
||||||
name: "CRUD",
|
name: "CRUD",
|
||||||
},
|
},
|
||||||
"oapi3PetstoreJson" : {
|
oapi3PetstoreJson: {
|
||||||
name: "Swagger Petstore - OpenAPI 3.0",
|
name: "Swagger Petstore - OpenAPI 3.0",
|
||||||
},
|
},
|
||||||
"oapi3PetstoreYaml" :{
|
oapi3PetstoreYaml: {
|
||||||
name: "Swagger Petstore - OpenAPI 3.0",
|
name: "Swagger Petstore - OpenAPI 3.0",
|
||||||
},
|
},
|
||||||
// curl
|
// curl
|
||||||
"curl": {
|
curl: {
|
||||||
name: "example.com",
|
name: "example.com",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
await runTest(testGetInfo, assertions)
|
await runTest(testGetInfo, assertions)
|
||||||
})
|
})
|
||||||
|
@ -109,7 +112,11 @@ describe("Rest Importer", () => {
|
||||||
expect(importResult.errorQueries.length).toBe(0)
|
expect(importResult.errorQueries.length).toBe(0)
|
||||||
expect(importResult.queries.length).toBe(assertions[key].count)
|
expect(importResult.queries.length).toBe(assertions[key].count)
|
||||||
expect(events.query.imported).toBeCalledTimes(1)
|
expect(events.query.imported).toBeCalledTimes(1)
|
||||||
expect(events.query.imported).toBeCalledWith(datasource, assertions[key].source, assertions[key].count)
|
expect(events.query.imported).toBeCalledWith(
|
||||||
|
datasource,
|
||||||
|
assertions[key].source,
|
||||||
|
assertions[key].count
|
||||||
|
)
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,44 +125,44 @@ describe("Rest Importer", () => {
|
||||||
// makes it through the importer
|
// makes it through the importer
|
||||||
const assertions = {
|
const assertions = {
|
||||||
// openapi2 (swagger)
|
// openapi2 (swagger)
|
||||||
"oapi2CrudJson" : {
|
oapi2CrudJson: {
|
||||||
count: 6,
|
count: 6,
|
||||||
source: "openapi2.0",
|
source: "openapi2.0",
|
||||||
},
|
},
|
||||||
"oapi2CrudYaml" :{
|
oapi2CrudYaml: {
|
||||||
count: 6,
|
count: 6,
|
||||||
source: "openapi2.0"
|
source: "openapi2.0",
|
||||||
},
|
},
|
||||||
"oapi2PetstoreJson" : {
|
oapi2PetstoreJson: {
|
||||||
count: 20,
|
count: 20,
|
||||||
source: "openapi2.0"
|
source: "openapi2.0",
|
||||||
},
|
},
|
||||||
"oapi2PetstoreYaml" :{
|
oapi2PetstoreYaml: {
|
||||||
count: 20,
|
count: 20,
|
||||||
source: "openapi2.0"
|
source: "openapi2.0",
|
||||||
},
|
},
|
||||||
// openapi3
|
// openapi3
|
||||||
"oapi3CrudJson" : {
|
oapi3CrudJson: {
|
||||||
count: 6,
|
count: 6,
|
||||||
source: "openapi3.0"
|
source: "openapi3.0",
|
||||||
},
|
},
|
||||||
"oapi3CrudYaml" :{
|
oapi3CrudYaml: {
|
||||||
count: 6,
|
count: 6,
|
||||||
source: "openapi3.0"
|
source: "openapi3.0",
|
||||||
},
|
},
|
||||||
"oapi3PetstoreJson" : {
|
oapi3PetstoreJson: {
|
||||||
count: 19,
|
count: 19,
|
||||||
source: "openapi3.0"
|
source: "openapi3.0",
|
||||||
},
|
},
|
||||||
"oapi3PetstoreYaml" :{
|
oapi3PetstoreYaml: {
|
||||||
count: 19,
|
count: 19,
|
||||||
source: "openapi3.0"
|
source: "openapi3.0",
|
||||||
},
|
},
|
||||||
// curl
|
// curl
|
||||||
"curl": {
|
curl: {
|
||||||
count: 1,
|
count: 1,
|
||||||
source: "curl"
|
source: "curl",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
await runTest(testImportQueries, assertions)
|
await runTest(testImportQueries, assertions)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ValidFileExtensions } from "@budibase/shared-core"
|
import { InvalidFileExtensions } from "@budibase/shared-core"
|
||||||
|
|
||||||
require("svelte/register")
|
require("svelte/register")
|
||||||
|
|
||||||
|
@ -91,7 +91,10 @@ export const uploadFile = async function (
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!env.SELF_HOSTED && !ValidFileExtensions.includes(extension)) {
|
if (
|
||||||
|
!env.SELF_HOSTED &&
|
||||||
|
InvalidFileExtensions.includes(extension.toLowerCase())
|
||||||
|
) {
|
||||||
throw new BadRequestError(
|
throw new BadRequestError(
|
||||||
`File "${file.name}" has an invalid extension: "${extension}"`
|
`File "${file.name}" has an invalid extension: "${extension}"`
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,65 +1,75 @@
|
||||||
const viewTemplate = require("../viewBuilder").default;
|
const viewTemplate = require("../viewBuilder").default
|
||||||
|
|
||||||
describe("viewBuilder", () => {
|
describe("viewBuilder", () => {
|
||||||
|
|
||||||
describe("Filter", () => {
|
describe("Filter", () => {
|
||||||
it("creates a view with multiple filters and conjunctions", () => {
|
it("creates a view with multiple filters and conjunctions", () => {
|
||||||
expect(viewTemplate({
|
expect(
|
||||||
"name": "Test View",
|
viewTemplate({
|
||||||
"tableId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
name: "Test View",
|
||||||
"filters": [{
|
tableId: "14f1c4e94d6a47b682ce89d35d4c78b0",
|
||||||
"value": "Test",
|
filters: [
|
||||||
"condition": "EQUALS",
|
{
|
||||||
"key": "Name"
|
value: "Test",
|
||||||
}, {
|
condition: "EQUALS",
|
||||||
"value": "Value",
|
key: "Name",
|
||||||
"condition": "MT",
|
},
|
||||||
"key": "Yes",
|
{
|
||||||
"conjunction": "OR"
|
value: "Value",
|
||||||
}]
|
condition: "MT",
|
||||||
})).toMatchSnapshot()
|
key: "Yes",
|
||||||
|
conjunction: "OR",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Calculate", () => {
|
describe("Calculate", () => {
|
||||||
it("creates a view with the calculation statistics schema", () => {
|
it("creates a view with the calculation statistics schema", () => {
|
||||||
expect(viewTemplate({
|
expect(
|
||||||
"name": "Calculate View",
|
viewTemplate({
|
||||||
"field": "myField",
|
name: "Calculate View",
|
||||||
"calculation": "stats",
|
field: "myField",
|
||||||
"tableId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
calculation: "stats",
|
||||||
"filters": []
|
tableId: "14f1c4e94d6a47b682ce89d35d4c78b0",
|
||||||
})).toMatchSnapshot()
|
filters: [],
|
||||||
|
})
|
||||||
|
).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Group By", () => {
|
describe("Group By", () => {
|
||||||
it("creates a view emitting the group by field", () => {
|
it("creates a view emitting the group by field", () => {
|
||||||
expect(viewTemplate({
|
expect(
|
||||||
"name": "Test Scores Grouped By Age",
|
viewTemplate({
|
||||||
"tableId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
name: "Test Scores Grouped By Age",
|
||||||
"groupBy": "age",
|
tableId: "14f1c4e94d6a47b682ce89d35d4c78b0",
|
||||||
"field": "score",
|
groupBy: "age",
|
||||||
"filters": [],
|
field: "score",
|
||||||
})).toMatchSnapshot()
|
filters: [],
|
||||||
|
})
|
||||||
|
).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Calculate and filter", () => {
|
describe("Calculate and filter", () => {
|
||||||
it("creates a view with the calculation statistics and filter schema", () => {
|
it("creates a view with the calculation statistics and filter schema", () => {
|
||||||
expect(viewTemplate({
|
expect(
|
||||||
"name": "Calculate View",
|
viewTemplate({
|
||||||
"field": "myField",
|
name: "Calculate View",
|
||||||
"calculation": "stats",
|
field: "myField",
|
||||||
"tableId": "14f1c4e94d6a47b682ce89d35d4c78b0",
|
calculation: "stats",
|
||||||
"filters": [
|
tableId: "14f1c4e94d6a47b682ce89d35d4c78b0",
|
||||||
|
filters: [
|
||||||
{
|
{
|
||||||
"value": 17,
|
value: 17,
|
||||||
"condition": "MT",
|
condition: "MT",
|
||||||
"key": "age",
|
key: "age",
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
})).toMatchSnapshot()
|
})
|
||||||
|
).toMatchSnapshot()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
});
|
|
|
@ -117,7 +117,7 @@ write.push(
|
||||||
* /applications/{appId}/publish:
|
* /applications/{appId}/publish:
|
||||||
* post:
|
* post:
|
||||||
* operationId: appPublish
|
* operationId: appPublish
|
||||||
* summary: Unpublish an application
|
* summary: Publish an application
|
||||||
* tags:
|
* tags:
|
||||||
* - applications
|
* - applications
|
||||||
* parameters:
|
* parameters:
|
||||||
|
|
|
@ -30,5 +30,4 @@ describe("/metrics", () => {
|
||||||
.expect(403)
|
.expect(403)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -40,7 +40,10 @@ describe("/static", () => {
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(events.serve.servedAppPreview).toBeCalledTimes(1)
|
expect(events.serve.servedAppPreview).toBeCalledTimes(1)
|
||||||
expect(events.serve.servedAppPreview).toBeCalledWith(config.getApp(), timezone)
|
expect(events.serve.servedAppPreview).toBeCalledWith(
|
||||||
|
config.getApp(),
|
||||||
|
timezone
|
||||||
|
)
|
||||||
expect(events.serve.servedApp).not.toBeCalled()
|
expect(events.serve.servedApp).not.toBeCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -55,7 +58,11 @@ describe("/static", () => {
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(events.serve.servedApp).toBeCalledTimes(1)
|
expect(events.serve.servedApp).toBeCalledTimes(1)
|
||||||
expect(events.serve.servedApp).toBeCalledWith(config.getProdApp(), timezone, undefined)
|
expect(events.serve.servedApp).toBeCalledWith(
|
||||||
|
config.getProdApp(),
|
||||||
|
timezone,
|
||||||
|
undefined
|
||||||
|
)
|
||||||
expect(events.serve.servedAppPreview).not.toBeCalled()
|
expect(events.serve.servedAppPreview).not.toBeCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -70,7 +77,11 @@ describe("/static", () => {
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(events.serve.servedApp).toBeCalledTimes(1)
|
expect(events.serve.servedApp).toBeCalledTimes(1)
|
||||||
expect(events.serve.servedApp).toBeCalledWith(config.getProdApp(), timezone, true)
|
expect(events.serve.servedApp).toBeCalledWith(
|
||||||
|
config.getProdApp(),
|
||||||
|
timezone,
|
||||||
|
true
|
||||||
|
)
|
||||||
expect(events.serve.servedAppPreview).not.toBeCalled()
|
expect(events.serve.servedAppPreview).not.toBeCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -38,7 +38,7 @@ describe("/api/keys", () => {
|
||||||
const res = await request
|
const res = await request
|
||||||
.put(`/api/keys/TEST`)
|
.put(`/api/keys/TEST`)
|
||||||
.send({
|
.send({
|
||||||
value: "test"
|
value: "test",
|
||||||
})
|
})
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
|
|
|
@ -35,6 +35,17 @@ describe("/api/applications/:appId/sync", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should reject an upload with a malicious uppercase file extension", async () => {
|
||||||
|
await config.withEnv({ SELF_HOSTED: undefined }, async () => {
|
||||||
|
let resp = (await config.api.attachment.process(
|
||||||
|
"OHNO.EXE",
|
||||||
|
Buffer.from([0]),
|
||||||
|
{ expectStatus: 400 }
|
||||||
|
)) as unknown as APIError
|
||||||
|
expect(resp.message).toContain("invalid extension")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it("should reject an upload with no file", async () => {
|
it("should reject an upload with no file", async () => {
|
||||||
let resp = (await config.api.attachment.process(
|
let resp = (await config.api.attachment.process(
|
||||||
undefined as any,
|
undefined as any,
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
const setup = require("./utilities")
|
const setup = require("./utilities")
|
||||||
const { events } = require("@budibase/backend-core")
|
const { events } = require("@budibase/backend-core")
|
||||||
|
|
||||||
|
|
||||||
describe("/dev", () => {
|
describe("/dev", () => {
|
||||||
let request = setup.getRequest()
|
let request = setup.getRequest()
|
||||||
let config = setup.getConfig()
|
let config = setup.getConfig()
|
||||||
|
@ -32,9 +31,9 @@ describe("/dev", () => {
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.version).toBe('0.0.0+jest')
|
expect(res.body.version).toBe("0.0.0+jest")
|
||||||
expect(events.installation.versionChecked).toBeCalledTimes(1)
|
expect(events.installation.versionChecked).toBeCalledTimes(1)
|
||||||
expect(events.installation.versionChecked).toBeCalledWith('0.0.0+jest')
|
expect(events.installation.versionChecked).toBeCalledWith("0.0.0+jest")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
|
@ -14,7 +14,10 @@ describe("/metadata", () => {
|
||||||
automation = await config.createAutomation()
|
automation = await config.createAutomation()
|
||||||
})
|
})
|
||||||
|
|
||||||
async function createMetadata(data, type = MetadataTypes.AUTOMATION_TEST_INPUT) {
|
async function createMetadata(
|
||||||
|
data,
|
||||||
|
type = MetadataTypes.AUTOMATION_TEST_INPUT
|
||||||
|
) {
|
||||||
const res = await request
|
const res = await request
|
||||||
.post(`/api/metadata/${type}/${automation._id}`)
|
.post(`/api/metadata/${type}/${automation._id}`)
|
||||||
.send(data)
|
.send(data)
|
||||||
|
@ -53,7 +56,9 @@ describe("/metadata", () => {
|
||||||
describe("destroy", () => {
|
describe("destroy", () => {
|
||||||
it("should be able to delete some test inputs", async () => {
|
it("should be able to delete some test inputs", async () => {
|
||||||
const res = await request
|
const res = await request
|
||||||
.delete(`/api/metadata/${MetadataTypes.AUTOMATION_TEST_INPUT}/${automation._id}`)
|
.delete(
|
||||||
|
`/api/metadata/${MetadataTypes.AUTOMATION_TEST_INPUT}/${automation._id}`
|
||||||
|
)
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
|
@ -14,7 +14,7 @@ jest.mock("@budibase/backend-core", () => {
|
||||||
db: {
|
db: {
|
||||||
...core.db,
|
...core.db,
|
||||||
isProdAppID: jest.fn(),
|
isProdAppID: jest.fn(),
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const setup = require("./utilities")
|
const setup = require("./utilities")
|
||||||
|
@ -31,7 +31,6 @@ describe("/queries", () => {
|
||||||
afterAll(setup.afterAll)
|
afterAll(setup.afterAll)
|
||||||
|
|
||||||
const setupTest = async () => {
|
const setupTest = async () => {
|
||||||
|
|
||||||
await config.init()
|
await config.init()
|
||||||
datasource = await config.createDatasource()
|
datasource = await config.createDatasource()
|
||||||
query = await config.createQuery()
|
query = await config.createQuery()
|
||||||
|
@ -52,7 +51,7 @@ describe("/queries", () => {
|
||||||
return { datasource, query }
|
return { datasource, query }
|
||||||
}
|
}
|
||||||
|
|
||||||
const createQuery = async (query) => {
|
const createQuery = async query => {
|
||||||
return request
|
return request
|
||||||
.post(`/api/queries`)
|
.post(`/api/queries`)
|
||||||
.send(query)
|
.send(query)
|
||||||
|
@ -76,7 +75,7 @@ describe("/queries", () => {
|
||||||
_id: res.body._id,
|
_id: res.body._id,
|
||||||
...query,
|
...query,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
updatedAt: new Date().toISOString()
|
updatedAt: new Date().toISOString(),
|
||||||
})
|
})
|
||||||
expect(events.query.created).toBeCalledTimes(1)
|
expect(events.query.created).toBeCalledTimes(1)
|
||||||
expect(events.query.updated).not.toBeCalled()
|
expect(events.query.updated).not.toBeCalled()
|
||||||
|
@ -101,7 +100,7 @@ describe("/queries", () => {
|
||||||
_id: res.body._id,
|
_id: res.body._id,
|
||||||
...query,
|
...query,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
updatedAt: new Date().toISOString()
|
updatedAt: new Date().toISOString(),
|
||||||
})
|
})
|
||||||
expect(events.query.created).not.toBeCalled()
|
expect(events.query.created).not.toBeCalled()
|
||||||
expect(events.query.updated).toBeCalledTimes(1)
|
expect(events.query.updated).toBeCalledTimes(1)
|
||||||
|
@ -237,8 +236,8 @@ describe("/queries", () => {
|
||||||
.expect(200)
|
.expect(200)
|
||||||
// these responses come from the mock
|
// these responses come from the mock
|
||||||
expect(res.body.schemaFields).toEqual({
|
expect(res.body.schemaFields).toEqual({
|
||||||
"a": "string",
|
a: "string",
|
||||||
"b": "number",
|
b: "number",
|
||||||
})
|
})
|
||||||
expect(res.body.rows.length).toEqual(1)
|
expect(res.body.rows.length).toEqual(1)
|
||||||
expect(events.query.previewed).toBeCalledTimes(1)
|
expect(events.query.previewed).toBeCalledTimes(1)
|
||||||
|
@ -302,9 +301,9 @@ describe("/queries", () => {
|
||||||
})
|
})
|
||||||
// these responses come from the mock
|
// these responses come from the mock
|
||||||
expect(res.body.schemaFields).toEqual({
|
expect(res.body.schemaFields).toEqual({
|
||||||
"opts": "json",
|
opts: "json",
|
||||||
"url": "string",
|
url: "string",
|
||||||
"value": "string",
|
value: "string",
|
||||||
})
|
})
|
||||||
expect(res.body.rows[0].url).toEqual("http://www.google.com?test=1")
|
expect(res.body.rows[0].url).toEqual("http://www.google.com?test=1")
|
||||||
})
|
})
|
||||||
|
@ -316,9 +315,9 @@ describe("/queries", () => {
|
||||||
queryString: "test={{ variable3 }}",
|
queryString: "test={{ variable3 }}",
|
||||||
})
|
})
|
||||||
expect(res.body.schemaFields).toEqual({
|
expect(res.body.schemaFields).toEqual({
|
||||||
"opts": "json",
|
opts: "json",
|
||||||
"url": "string",
|
url: "string",
|
||||||
"value": "string"
|
value: "string",
|
||||||
})
|
})
|
||||||
expect(res.body.rows[0].url).toContain("doctype%20html")
|
expect(res.body.rows[0].url).toContain("doctype%20html")
|
||||||
})
|
})
|
||||||
|
@ -339,9 +338,9 @@ describe("/queries", () => {
|
||||||
queryString: "test={{ variable3 }}",
|
queryString: "test={{ variable3 }}",
|
||||||
})
|
})
|
||||||
expect(res.body.schemaFields).toEqual({
|
expect(res.body.schemaFields).toEqual({
|
||||||
"fails": "number",
|
fails: "number",
|
||||||
"opts": "json",
|
opts: "json",
|
||||||
"url": "string"
|
url: "string",
|
||||||
})
|
})
|
||||||
expect(res.body.rows[0].fails).toEqual(1)
|
expect(res.body.rows[0].fails).toEqual(1)
|
||||||
})
|
})
|
||||||
|
@ -371,13 +370,19 @@ describe("/queries", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Current User Request Mapping", () => {
|
describe("Current User Request Mapping", () => {
|
||||||
|
|
||||||
async function previewGet(datasource, fields, params) {
|
async function previewGet(datasource, fields, params) {
|
||||||
return config.previewQuery(request, config, datasource, fields, params)
|
return config.previewQuery(request, config, datasource, fields, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function previewPost(datasource, fields, params) {
|
async function previewPost(datasource, fields, params) {
|
||||||
return config.previewQuery(request, config, datasource, fields, params, "create")
|
return config.previewQuery(
|
||||||
|
request,
|
||||||
|
config,
|
||||||
|
datasource,
|
||||||
|
fields,
|
||||||
|
params,
|
||||||
|
"create"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
it("should parse global and query level header mappings", async () => {
|
it("should parse global and query level header mappings", async () => {
|
||||||
|
@ -385,27 +390,29 @@ describe("/queries", () => {
|
||||||
|
|
||||||
const datasource = await config.restDatasource({
|
const datasource = await config.restDatasource({
|
||||||
defaultHeaders: {
|
defaultHeaders: {
|
||||||
"test": "headerVal",
|
test: "headerVal",
|
||||||
"emailHdr": "{{[user].[email]}}"
|
emailHdr: "{{[user].[email]}}",
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
const res = await previewGet(datasource, {
|
const res = await previewGet(datasource, {
|
||||||
path: "www.google.com",
|
path: "www.google.com",
|
||||||
queryString: "email={{[user].[email]}}",
|
queryString: "email={{[user].[email]}}",
|
||||||
headers: {
|
headers: {
|
||||||
queryHdr: "{{[user].[firstName]}}",
|
queryHdr: "{{[user].[firstName]}}",
|
||||||
secondHdr : "1234"
|
secondHdr: "1234",
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const parsedRequest = JSON.parse(res.body.extra.raw)
|
const parsedRequest = JSON.parse(res.body.extra.raw)
|
||||||
expect(parsedRequest.opts.headers).toEqual({
|
expect(parsedRequest.opts.headers).toEqual({
|
||||||
"test": "headerVal",
|
test: "headerVal",
|
||||||
"emailHdr": userDetails.email,
|
emailHdr: userDetails.email,
|
||||||
"queryHdr": userDetails.firstName,
|
queryHdr: userDetails.firstName,
|
||||||
"secondHdr" : "1234"
|
secondHdr: "1234",
|
||||||
})
|
})
|
||||||
expect(res.body.rows[0].url).toEqual("http://www.google.com?email=" + userDetails.email.replace("@", "%40"))
|
expect(res.body.rows[0].url).toEqual(
|
||||||
|
"http://www.google.com?email=" + userDetails.email.replace("@", "%40")
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should bind the current user to query parameters", async () => {
|
it("should bind the current user to query parameters", async () => {
|
||||||
|
@ -413,92 +420,130 @@ describe("/queries", () => {
|
||||||
|
|
||||||
const datasource = await config.restDatasource()
|
const datasource = await config.restDatasource()
|
||||||
|
|
||||||
const res = await previewGet(datasource, {
|
const res = await previewGet(
|
||||||
|
datasource,
|
||||||
|
{
|
||||||
path: "www.google.com",
|
path: "www.google.com",
|
||||||
queryString: "test={{myEmail}}&testName={{myName}}&testParam={{testParam}}",
|
queryString:
|
||||||
}, {
|
"test={{myEmail}}&testName={{myName}}&testParam={{testParam}}",
|
||||||
"myEmail" : "{{[user].[email]}}",
|
},
|
||||||
"myName" : "{{[user].[firstName]}}",
|
{
|
||||||
"testParam" : "1234"
|
myEmail: "{{[user].[email]}}",
|
||||||
})
|
myName: "{{[user].[firstName]}}",
|
||||||
|
testParam: "1234",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
expect(res.body.rows[0].url).toEqual("http://www.google.com?test=" + userDetails.email.replace("@", "%40") +
|
expect(res.body.rows[0].url).toEqual(
|
||||||
"&testName=" + userDetails.firstName + "&testParam=1234")
|
"http://www.google.com?test=" +
|
||||||
|
userDetails.email.replace("@", "%40") +
|
||||||
|
"&testName=" +
|
||||||
|
userDetails.firstName +
|
||||||
|
"&testParam=1234"
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should bind the current user the request body - plain text", async () => {
|
it("should bind the current user the request body - plain text", async () => {
|
||||||
const userDetails = config.getUserDetails()
|
const userDetails = config.getUserDetails()
|
||||||
const datasource = await config.restDatasource()
|
const datasource = await config.restDatasource()
|
||||||
|
|
||||||
const res = await previewPost(datasource, {
|
const res = await previewPost(
|
||||||
|
datasource,
|
||||||
|
{
|
||||||
path: "www.google.com",
|
path: "www.google.com",
|
||||||
queryString: "testParam={{testParam}}",
|
queryString: "testParam={{testParam}}",
|
||||||
requestBody: "This is plain text and this is my email: {{[user].[email]}}. This is a test param: {{testParam}}",
|
requestBody:
|
||||||
bodyType: "text"
|
"This is plain text and this is my email: {{[user].[email]}}. This is a test param: {{testParam}}",
|
||||||
}, {
|
bodyType: "text",
|
||||||
"testParam" : "1234"
|
},
|
||||||
})
|
{
|
||||||
|
testParam: "1234",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const parsedRequest = JSON.parse(res.body.extra.raw)
|
const parsedRequest = JSON.parse(res.body.extra.raw)
|
||||||
expect(parsedRequest.opts.body).toEqual(`This is plain text and this is my email: ${userDetails.email}. This is a test param: 1234`)
|
expect(parsedRequest.opts.body).toEqual(
|
||||||
expect(res.body.rows[0].url).toEqual("http://www.google.com?testParam=1234")
|
`This is plain text and this is my email: ${userDetails.email}. This is a test param: 1234`
|
||||||
|
)
|
||||||
|
expect(res.body.rows[0].url).toEqual(
|
||||||
|
"http://www.google.com?testParam=1234"
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should bind the current user the request body - json", async () => {
|
it("should bind the current user the request body - json", async () => {
|
||||||
const userDetails = config.getUserDetails()
|
const userDetails = config.getUserDetails()
|
||||||
const datasource = await config.restDatasource()
|
const datasource = await config.restDatasource()
|
||||||
|
|
||||||
const res = await previewPost(datasource, {
|
const res = await previewPost(
|
||||||
|
datasource,
|
||||||
|
{
|
||||||
path: "www.google.com",
|
path: "www.google.com",
|
||||||
queryString: "testParam={{testParam}}",
|
queryString: "testParam={{testParam}}",
|
||||||
requestBody: "{\"email\":\"{{[user].[email]}}\",\"queryCode\":{{testParam}},\"userRef\":\"{{userRef}}\"}",
|
requestBody:
|
||||||
bodyType: "json"
|
'{"email":"{{[user].[email]}}","queryCode":{{testParam}},"userRef":"{{userRef}}"}',
|
||||||
}, {
|
bodyType: "json",
|
||||||
"testParam" : "1234",
|
},
|
||||||
"userRef" : "{{[user].[firstName]}}"
|
{
|
||||||
})
|
testParam: "1234",
|
||||||
|
userRef: "{{[user].[firstName]}}",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const parsedRequest = JSON.parse(res.body.extra.raw)
|
const parsedRequest = JSON.parse(res.body.extra.raw)
|
||||||
const test = `{"email":"${userDetails.email}","queryCode":1234,"userRef":"${userDetails.firstName}"}`
|
const test = `{"email":"${userDetails.email}","queryCode":1234,"userRef":"${userDetails.firstName}"}`
|
||||||
expect(parsedRequest.opts.body).toEqual(test)
|
expect(parsedRequest.opts.body).toEqual(test)
|
||||||
expect(res.body.rows[0].url).toEqual("http://www.google.com?testParam=1234")
|
expect(res.body.rows[0].url).toEqual(
|
||||||
|
"http://www.google.com?testParam=1234"
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should bind the current user the request body - xml", async () => {
|
it("should bind the current user the request body - xml", async () => {
|
||||||
const userDetails = config.getUserDetails()
|
const userDetails = config.getUserDetails()
|
||||||
const datasource = await config.restDatasource()
|
const datasource = await config.restDatasource()
|
||||||
|
|
||||||
const res = await previewPost(datasource, {
|
const res = await previewPost(
|
||||||
|
datasource,
|
||||||
|
{
|
||||||
path: "www.google.com",
|
path: "www.google.com",
|
||||||
queryString: "testParam={{testParam}}",
|
queryString: "testParam={{testParam}}",
|
||||||
requestBody: "<note> <email>{{[user].[email]}}</email> <code>{{testParam}}</code> " +
|
requestBody:
|
||||||
|
"<note> <email>{{[user].[email]}}</email> <code>{{testParam}}</code> " +
|
||||||
"<ref>{{userId}}</ref> <somestring>testing</somestring> </note>",
|
"<ref>{{userId}}</ref> <somestring>testing</somestring> </note>",
|
||||||
bodyType: "xml"
|
bodyType: "xml",
|
||||||
}, {
|
},
|
||||||
"testParam" : "1234",
|
{
|
||||||
"userId" : "{{[user].[firstName]}}"
|
testParam: "1234",
|
||||||
})
|
userId: "{{[user].[firstName]}}",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const parsedRequest = JSON.parse(res.body.extra.raw)
|
const parsedRequest = JSON.parse(res.body.extra.raw)
|
||||||
const test = `<note> <email>${userDetails.email}</email> <code>1234</code> <ref>${userDetails.firstName}</ref> <somestring>testing</somestring> </note>`
|
const test = `<note> <email>${userDetails.email}</email> <code>1234</code> <ref>${userDetails.firstName}</ref> <somestring>testing</somestring> </note>`
|
||||||
|
|
||||||
expect(parsedRequest.opts.body).toEqual(test)
|
expect(parsedRequest.opts.body).toEqual(test)
|
||||||
expect(res.body.rows[0].url).toEqual("http://www.google.com?testParam=1234")
|
expect(res.body.rows[0].url).toEqual(
|
||||||
|
"http://www.google.com?testParam=1234"
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should bind the current user the request body - form-data", async () => {
|
it("should bind the current user the request body - form-data", async () => {
|
||||||
const userDetails = config.getUserDetails()
|
const userDetails = config.getUserDetails()
|
||||||
const datasource = await config.restDatasource()
|
const datasource = await config.restDatasource()
|
||||||
|
|
||||||
const res = await previewPost(datasource, {
|
const res = await previewPost(
|
||||||
|
datasource,
|
||||||
|
{
|
||||||
path: "www.google.com",
|
path: "www.google.com",
|
||||||
queryString: "testParam={{testParam}}",
|
queryString: "testParam={{testParam}}",
|
||||||
requestBody: "{\"email\":\"{{[user].[email]}}\",\"queryCode\":{{testParam}},\"userRef\":\"{{userRef}}\"}",
|
requestBody:
|
||||||
bodyType: "form"
|
'{"email":"{{[user].[email]}}","queryCode":{{testParam}},"userRef":"{{userRef}}"}',
|
||||||
}, {
|
bodyType: "form",
|
||||||
"testParam" : "1234",
|
},
|
||||||
"userRef" : "{{[user].[firstName]}}"
|
{
|
||||||
})
|
testParam: "1234",
|
||||||
|
userRef: "{{[user].[firstName]}}",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const parsedRequest = JSON.parse(res.body.extra.raw)
|
const parsedRequest = JSON.parse(res.body.extra.raw)
|
||||||
|
|
||||||
|
@ -511,28 +556,34 @@ describe("/queries", () => {
|
||||||
const userRef = parsedRequest.opts.body._streams[7]
|
const userRef = parsedRequest.opts.body._streams[7]
|
||||||
expect(userRef).toEqual(userDetails.firstName)
|
expect(userRef).toEqual(userDetails.firstName)
|
||||||
|
|
||||||
expect(res.body.rows[0].url).toEqual("http://www.google.com?testParam=1234")
|
expect(res.body.rows[0].url).toEqual(
|
||||||
|
"http://www.google.com?testParam=1234"
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should bind the current user the request body - encoded", async () => {
|
it("should bind the current user the request body - encoded", async () => {
|
||||||
const userDetails = config.getUserDetails()
|
const userDetails = config.getUserDetails()
|
||||||
const datasource = await config.restDatasource()
|
const datasource = await config.restDatasource()
|
||||||
|
|
||||||
const res = await previewPost(datasource, {
|
const res = await previewPost(
|
||||||
|
datasource,
|
||||||
|
{
|
||||||
path: "www.google.com",
|
path: "www.google.com",
|
||||||
queryString: "testParam={{testParam}}",
|
queryString: "testParam={{testParam}}",
|
||||||
requestBody: "{\"email\":\"{{[user].[email]}}\",\"queryCode\":{{testParam}},\"userRef\":\"{{userRef}}\"}",
|
requestBody:
|
||||||
bodyType: "encoded"
|
'{"email":"{{[user].[email]}}","queryCode":{{testParam}},"userRef":"{{userRef}}"}',
|
||||||
}, {
|
bodyType: "encoded",
|
||||||
"testParam" : "1234",
|
},
|
||||||
"userRef" : "{{[user].[firstName]}}"
|
{
|
||||||
})
|
testParam: "1234",
|
||||||
|
userRef: "{{[user].[firstName]}}",
|
||||||
|
}
|
||||||
|
)
|
||||||
const parsedRequest = JSON.parse(res.body.extra.raw)
|
const parsedRequest = JSON.parse(res.body.extra.raw)
|
||||||
|
|
||||||
expect(parsedRequest.opts.body.email).toEqual(userDetails.email)
|
expect(parsedRequest.opts.body.email).toEqual(userDetails.email)
|
||||||
expect(parsedRequest.opts.body.queryCode).toEqual("1234")
|
expect(parsedRequest.opts.body.queryCode).toEqual("1234")
|
||||||
expect(parsedRequest.opts.body.userRef).toEqual(userDetails.firstName)
|
expect(parsedRequest.opts.body.userRef).toEqual(userDetails.firstName)
|
||||||
})
|
})
|
||||||
|
})
|
||||||
});
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -170,7 +170,7 @@ describe("/roles", () => {
|
||||||
.get("/api/roles/accessible")
|
.get("/api/roles/accessible")
|
||||||
.set({
|
.set({
|
||||||
...config.defaultHeaders(),
|
...config.defaultHeaders(),
|
||||||
"x-budibase-role": "CUSTOM_ROLE"
|
"x-budibase-role": "CUSTOM_ROLE",
|
||||||
})
|
})
|
||||||
.expect(200)
|
.expect(200)
|
||||||
expect(res.body.length).toBe(3)
|
expect(res.body.length).toBe(3)
|
||||||
|
|
|
@ -37,19 +37,23 @@ describe("/routing", () => {
|
||||||
await runInProd(async () => {
|
await runInProd(async () => {
|
||||||
return request
|
return request
|
||||||
.get(`/api/routing/client`)
|
.get(`/api/routing/client`)
|
||||||
.set(await config.roleHeaders({
|
.set(
|
||||||
|
await config.roleHeaders({
|
||||||
roleId: BUILTIN_ROLE_IDS.BASIC,
|
roleId: BUILTIN_ROLE_IDS.BASIC,
|
||||||
prodApp: false
|
prodApp: false,
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
.expect(302)
|
.expect(302)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
it("returns the correct routing for basic user", async () => {
|
it("returns the correct routing for basic user", async () => {
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/routing/client`)
|
.get(`/api/routing/client`)
|
||||||
.set(await config.roleHeaders({
|
.set(
|
||||||
roleId: BUILTIN_ROLE_IDS.BASIC
|
await config.roleHeaders({
|
||||||
}))
|
roleId: BUILTIN_ROLE_IDS.BASIC,
|
||||||
|
})
|
||||||
|
)
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
expect(res.body.routes).toBeDefined()
|
expect(res.body.routes).toBeDefined()
|
||||||
|
@ -57,18 +61,20 @@ describe("/routing", () => {
|
||||||
subpaths: {
|
subpaths: {
|
||||||
[route]: {
|
[route]: {
|
||||||
screenId: basic._id,
|
screenId: basic._id,
|
||||||
roleId: basic.routing.roleId
|
roleId: basic.routing.roleId,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns the correct routing for power user", async () => {
|
it("returns the correct routing for power user", async () => {
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/routing/client`)
|
.get(`/api/routing/client`)
|
||||||
.set(await config.roleHeaders({
|
.set(
|
||||||
roleId: BUILTIN_ROLE_IDS.POWER
|
await config.roleHeaders({
|
||||||
}))
|
roleId: BUILTIN_ROLE_IDS.POWER,
|
||||||
|
})
|
||||||
|
)
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
expect(res.body.routes).toBeDefined()
|
expect(res.body.routes).toBeDefined()
|
||||||
|
@ -76,9 +82,9 @@ describe("/routing", () => {
|
||||||
subpaths: {
|
subpaths: {
|
||||||
[route]: {
|
[route]: {
|
||||||
screenId: power._id,
|
screenId: power._id,
|
||||||
roleId: power.routing.roleId
|
roleId: power.routing.roleId,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -87,10 +93,12 @@ describe("/routing", () => {
|
||||||
it("should fetch all routes for builder", async () => {
|
it("should fetch all routes for builder", async () => {
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/routing`)
|
.get(`/api/routing`)
|
||||||
.set(await config.roleHeaders({
|
.set(
|
||||||
|
await config.roleHeaders({
|
||||||
roleId: BUILTIN_ROLE_IDS.POWER,
|
roleId: BUILTIN_ROLE_IDS.POWER,
|
||||||
builder: true,
|
builder: true,
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
expect(res.body.routes).toBeDefined()
|
expect(res.body.routes).toBeDefined()
|
||||||
|
|
|
@ -36,7 +36,7 @@ describe("/screens", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("save", () => {
|
describe("save", () => {
|
||||||
const saveScreen = async (screen) => {
|
const saveScreen = async screen => {
|
||||||
const res = await request
|
const res = await request
|
||||||
.post(`/api/screens`)
|
.post(`/api/screens`)
|
||||||
.send(screen)
|
.send(screen)
|
||||||
|
|
|
@ -36,13 +36,13 @@ describe("/views", () => {
|
||||||
table = await config.createTable(priceTable())
|
table = await config.createTable(priceTable())
|
||||||
})
|
})
|
||||||
|
|
||||||
const saveView = async (view) => {
|
const saveView = async view => {
|
||||||
const viewToSave = {
|
const viewToSave = {
|
||||||
name: "TestView",
|
name: "TestView",
|
||||||
field: "Price",
|
field: "Price",
|
||||||
calculation: "stats",
|
calculation: "stats",
|
||||||
tableId: table._id,
|
tableId: table._id,
|
||||||
...view
|
...view,
|
||||||
}
|
}
|
||||||
return request
|
return request
|
||||||
.post(`/api/views`)
|
.post(`/api/views`)
|
||||||
|
@ -53,7 +53,6 @@ describe("/views", () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("create", () => {
|
describe("create", () => {
|
||||||
|
|
||||||
it("returns a success message when the view is successfully created", async () => {
|
it("returns a success message when the view is successfully created", async () => {
|
||||||
const res = await saveView()
|
const res = await saveView()
|
||||||
expect(res.body.tableId).toBe(table._id)
|
expect(res.body.tableId).toBe(table._id)
|
||||||
|
@ -81,11 +80,13 @@ describe("/views", () => {
|
||||||
|
|
||||||
const res = await saveView({
|
const res = await saveView({
|
||||||
calculation: null,
|
calculation: null,
|
||||||
filters: [{
|
filters: [
|
||||||
|
{
|
||||||
value: "1",
|
value: "1",
|
||||||
condition: "EQUALS",
|
condition: "EQUALS",
|
||||||
key: "price"
|
key: "price",
|
||||||
}],
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(res.body.tableId).toBe(table._id)
|
expect(res.body.tableId).toBe(table._id)
|
||||||
|
@ -199,18 +200,26 @@ describe("/views", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("updates a view filter", async () => {
|
it("updates a view filter", async () => {
|
||||||
await saveView({ filters: [{
|
await saveView({
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
value: "1",
|
value: "1",
|
||||||
condition: "EQUALS",
|
condition: "EQUALS",
|
||||||
key: "price"
|
key: "price",
|
||||||
}] })
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
|
|
||||||
await saveView({ filters: [{
|
await saveView({
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
value: "2",
|
value: "2",
|
||||||
condition: "EQUALS",
|
condition: "EQUALS",
|
||||||
key: "price"
|
key: "price",
|
||||||
}] })
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
expect(events.view.created).not.toBeCalled()
|
expect(events.view.created).not.toBeCalled()
|
||||||
expect(events.view.updated).toBeCalledTimes(1)
|
expect(events.view.updated).toBeCalledTimes(1)
|
||||||
|
@ -223,11 +232,15 @@ describe("/views", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("deletes a view filter", async () => {
|
it("deletes a view filter", async () => {
|
||||||
await saveView({ filters: [{
|
await saveView({
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
value: "1",
|
value: "1",
|
||||||
condition: "EQUALS",
|
condition: "EQUALS",
|
||||||
key: "price"
|
key: "price",
|
||||||
}] })
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
|
|
||||||
await saveView({ filters: [] })
|
await saveView({ filters: [] })
|
||||||
|
@ -344,7 +357,6 @@ describe("/views", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("exportView", () => {
|
describe("exportView", () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
|
@ -362,14 +374,14 @@ describe("/views", () => {
|
||||||
.expect(200)
|
.expect(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
const assertJsonExport = (res) => {
|
const assertJsonExport = res => {
|
||||||
const rows = JSON.parse(res.text)
|
const rows = JSON.parse(res.text)
|
||||||
expect(rows.length).toBe(1)
|
expect(rows.length).toBe(1)
|
||||||
expect(rows[0].name).toBe("test-name")
|
expect(rows[0].name).toBe("test-name")
|
||||||
expect(rows[0].description).toBe("ùúûü")
|
expect(rows[0].description).toBe("ùúûü")
|
||||||
}
|
}
|
||||||
|
|
||||||
const assertCSVExport = (res) => {
|
const assertCSVExport = res => {
|
||||||
expect(res.text).toBe(`"name","description"\n"test-name","ùúûü"`)
|
expect(res.text).toBe(`"name","description"\n"test-name","ùúûü"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,26 +9,25 @@ describe("test the bash action", () => {
|
||||||
afterAll(setup.afterAll)
|
afterAll(setup.afterAll)
|
||||||
|
|
||||||
it("should be able to execute a script", async () => {
|
it("should be able to execute a script", async () => {
|
||||||
|
let res = await setup.runStep(
|
||||||
let res = await setup.runStep("EXECUTE_BASH",
|
"EXECUTE_BASH",
|
||||||
inputs = {
|
(inputs = {
|
||||||
code: "echo 'test'"
|
code: "echo 'test'",
|
||||||
}
|
})
|
||||||
|
|
||||||
)
|
)
|
||||||
expect(res.stdout).toEqual("test\n")
|
expect(res.stdout).toEqual("test\n")
|
||||||
expect(res.success).toEqual(true)
|
expect(res.success).toEqual(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should handle a null value", async () => {
|
it("should handle a null value", async () => {
|
||||||
|
let res = await setup.runStep(
|
||||||
let res = await setup.runStep("EXECUTE_BASH",
|
"EXECUTE_BASH",
|
||||||
inputs = {
|
(inputs = {
|
||||||
code: null
|
code: null,
|
||||||
}
|
})
|
||||||
|
)
|
||||||
|
expect(res.stdout).toEqual(
|
||||||
|
"Budibase bash automation failed: Invalid inputs"
|
||||||
)
|
)
|
||||||
expect(res.stdout).toEqual("Budibase bash automation failed: Invalid inputs")
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const setup = require("./utilities")
|
const setup = require("./utilities")
|
||||||
|
|
||||||
// need real Date for this test
|
// need real Date for this test
|
||||||
const tk = require('timekeeper');
|
const tk = require("timekeeper")
|
||||||
tk.reset()
|
tk.reset()
|
||||||
|
|
||||||
describe("test the delay logic", () => {
|
describe("test the delay logic", () => {
|
||||||
|
|
|
@ -23,5 +23,4 @@ describe("test the outgoing webhook action", () => {
|
||||||
expect(res.response.method).toEqual("post")
|
expect(res.response.method).toEqual("post")
|
||||||
expect(res.success).toEqual(true)
|
expect(res.success).toEqual(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue