Browse Source

Merge pull request #2168 from Budibase/develop

Develop
master
Martin McKeaveney 5 years ago
committed by GitHub
parent
commit
ac3bf81f72
  1. 2
      packages/bbui/src/Form/Core/Switch.svelte
  2. 3
      packages/bbui/src/Form/Toggle.svelte
  3. 6
      packages/builder/cypress/integration/createAutomation.spec.js
  4. 23
      packages/builder/src/components/automation/SetupPanel/SetupPanel.svelte
  5. 14
      packages/builder/src/components/design/PropertiesPanel/PropertyControls/ConditionalUIDrawer.svelte
  6. 35
      packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/ClearForm.svelte
  7. 5
      packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js
  8. 4
      packages/builder/src/components/start/AppCard.svelte
  9. 2
      packages/builder/src/components/start/AppRow.svelte
  10. 111
      packages/builder/src/components/start/UpdateAppModal.svelte
  11. 10
      packages/builder/src/pages/builder/portal/apps/index.svelte
  12. 4
      packages/builder/src/pages/builder/portal/manage/users/_components/UpdateRolesModal.svelte
  13. 21
      packages/builder/src/stores/portal/apps.js
  14. 1
      packages/client/src/constants.js
  15. 9
      packages/client/src/utils/buttonActions.js
  16. 3
      packages/standard-components/manifest.json
  17. 27
      packages/standard-components/src/forms/InnerForm.svelte

2
packages/bbui/src/Form/Core/Switch.svelte

@ -6,6 +6,7 @@
export let id = null export let id = null
export let text = null export let text = null
export let disabled = false export let disabled = false
export let dataCy = null
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const onChange = event => { const onChange = event => {
@ -15,6 +16,7 @@
<div class="spectrum-Switch spectrum-Switch--emphasized"> <div class="spectrum-Switch spectrum-Switch--emphasized">
<input <input
data-cy={dataCy}
checked={value} checked={value}
{disabled} {disabled}
on:change={onChange} on:change={onChange}

3
packages/bbui/src/Form/Toggle.svelte

@ -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 dataCy = null
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const onChange = e => { const onChange = e => {
@ -18,5 +19,5 @@
</script> </script>
<Field {label} {labelPosition} {error}> <Field {label} {labelPosition} {error}>
<Switch {error} {disabled} {text} {value} on:change={onChange} /> <Switch {dataCy} {error} {disabled} {text} {value} on:change={onChange} />
</Field> </Field>

6
packages/builder/cypress/integration/createAutomation.spec.js

@ -29,10 +29,10 @@ context("Create a automation", () => {
cy.get(".setup").within(() => { cy.get(".setup").within(() => {
cy.get(".spectrum-Picker-label").click() cy.get(".spectrum-Picker-label").click()
cy.contains("dog").click() cy.contains("dog").click()
cy.get("input") cy.get(".spectrum-Textfield-input")
.first() .first()
.type("goodboy") .type("goodboy")
cy.get("input") cy.get(".spectrum-Textfield-input")
.eq(1) .eq(1)
.type("11") .type("11")
}) })
@ -41,7 +41,7 @@ context("Create a automation", () => {
cy.contains("Save Automation").click() cy.contains("Save Automation").click()
// Activate Automation // Activate Automation
cy.get("[aria-label=PlayCircle]").click() cy.get("[data-cy=activate-automation]").click()
}) })
it("should add row when a new row is added", () => { it("should add row when a new row is added", () => {

23
packages/builder/src/components/automation/SetupPanel/SetupPanel.svelte

@ -1,7 +1,7 @@
<script> <script>
import { automationStore } from "builderStore" import { automationStore } from "builderStore"
import { database } from "stores/backend" import { database } from "stores/backend"
import { notifications, Icon, Button, Modal, Heading } from "@budibase/bbui" import { notifications, Button, Modal, Heading, Toggle } from "@budibase/bbui"
import AutomationBlockSetup from "./AutomationBlockSetup.svelte" import AutomationBlockSetup from "./AutomationBlockSetup.svelte"
import CreateWebookModal from "../Shared/CreateWebhookModal.svelte" import CreateWebookModal from "../Shared/CreateWebhookModal.svelte"
@ -12,7 +12,7 @@
$: automationLive = automation?.live $: automationLive = automation?.live
function setAutomationLive(live) { function setAutomationLive(live) {
if (automation.live === live) { if (automationLive === live) {
return return
} }
automation.live = live automation.live = live
@ -48,20 +48,11 @@
<div class="title"> <div class="title">
<Heading size="S">Setup</Heading> <Heading size="S">Setup</Heading>
<Icon <Toggle
l value={automationLive}
disabled={!automationLive} on:change={() => setAutomationLive(!automationLive)}
hoverable={automationLive} dataCy="activate-automation"
name="PauseCircle" text="Live"
on:click={() => setAutomationLive(false)}
/>
<Icon
l
name="PlayCircle"
disabled={automationLive}
hoverable={!automationLive}
data-cy="activate-automation"
on:click={() => setAutomationLive(true)}
/> />
</div> </div>
{#if $automationStore.selectedBlock} {#if $automationStore.selectedBlock}

14
packages/builder/src/components/design/PropertiesPanel/PropertyControls/ConditionalUIDrawer.svelte

@ -101,6 +101,12 @@
conditions = conditions.filter(link => link.id !== id) conditions = conditions.filter(link => link.id !== id)
} }
const duplicateCondition = id => {
const condition = conditions.find(link => link.id === id)
const duplicate = { ...condition, id: generate() }
conditions = [...conditions, duplicate]
}
const handleFinalize = e => { const handleFinalize = e => {
updateConditions(e) updateConditions(e)
dragDisabled = true dragDisabled = true
@ -235,6 +241,12 @@
bind:value={condition.referenceValue} bind:value={condition.referenceValue}
/> />
{/if} {/if}
<Icon
name="Duplicate"
hoverable
size="S"
on:click={() => duplicateCondition(condition.id)}
/>
<Icon <Icon
name="Close" name="Close"
hoverable hoverable
@ -273,7 +285,7 @@
gap: var(--spacing-l); gap: var(--spacing-l);
display: grid; display: grid;
align-items: center; align-items: center;
grid-template-columns: auto 1fr auto 1fr 1fr 1fr 1fr auto; grid-template-columns: auto 1fr auto 1fr 1fr 1fr 1fr auto auto;
border-radius: var(--border-radius-s); border-radius: var(--border-radius-s);
transition: background-color ease-in-out 130ms; transition: background-color ease-in-out 130ms;
} }

35
packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/ClearForm.svelte

@ -0,0 +1,35 @@
<script>
import { Select, Label } from "@budibase/bbui"
import { currentAsset, store } from "builderStore"
import { getActionProviderComponents } from "builderStore/dataBinding"
export let parameters
$: actionProviders = getActionProviderComponents(
$currentAsset,
$store.selectedComponentId,
"ClearForm"
)
</script>
<div class="root">
<Label small>Form</Label>
<Select
bind:value={parameters.componentId}
options={actionProviders}
getOptionLabel={x => x._instanceName}
getOptionValue={x => x._id}
/>
</div>
<style>
.root {
display: grid;
column-gap: var(--spacing-l);
row-gap: var(--spacing-s);
grid-template-columns: 60px 1fr;
align-items: center;
max-width: 800px;
margin: 0 auto;
}
</style>

5
packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js

@ -5,6 +5,7 @@ import ExecuteQuery from "./ExecuteQuery.svelte"
import TriggerAutomation from "./TriggerAutomation.svelte" import TriggerAutomation from "./TriggerAutomation.svelte"
import ValidateForm from "./ValidateForm.svelte" import ValidateForm from "./ValidateForm.svelte"
import LogOut from "./LogOut.svelte" import LogOut from "./LogOut.svelte"
import ClearForm from "./ClearForm.svelte"
// Defines which actions are available to configure in the front end. // Defines which actions are available to configure in the front end.
// Unfortunately the "name" property is used as the identifier so please don't // Unfortunately the "name" property is used as the identifier so please don't
@ -42,4 +43,8 @@ export default [
name: "Log Out", name: "Log Out",
component: LogOut, component: LogOut,
}, },
{
name: "Clear Form",
component: ClearForm,
},
] ]

4
packages/builder/src/components/start/AppCard.svelte

@ -15,6 +15,7 @@
export let exportApp export let exportApp
export let viewApp export let viewApp
export let editApp export let editApp
export let updateApp
export let deleteApp export let deleteApp
export let unpublishApp export let unpublishApp
export let releaseLock export let releaseLock
@ -53,6 +54,9 @@
</MenuItem> </MenuItem>
{/if} {/if}
{#if !app.deployed} {#if !app.deployed}
<MenuItem on:click={() => updateApp(app)} icon="Edit">
Update
</MenuItem>
<MenuItem on:click={() => deleteApp(app)} icon="Delete"> <MenuItem on:click={() => deleteApp(app)} icon="Delete">
Delete Delete
</MenuItem> </MenuItem>

2
packages/builder/src/components/start/AppRow.svelte

@ -14,6 +14,7 @@
export let exportApp export let exportApp
export let viewApp export let viewApp
export let editApp export let editApp
export let updateApp
export let deleteApp export let deleteApp
export let unpublishApp export let unpublishApp
export let releaseLock export let releaseLock
@ -82,6 +83,7 @@
</MenuItem> </MenuItem>
{/if} {/if}
{#if !app.deployed} {#if !app.deployed}
<MenuItem on:click={() => updateApp(app)} icon="Edit">Update</MenuItem>
<MenuItem on:click={() => deleteApp(app)} icon="Delete">Delete</MenuItem> <MenuItem on:click={() => deleteApp(app)} icon="Delete">Delete</MenuItem>
{/if} {/if}
</ActionMenu> </ActionMenu>

111
packages/builder/src/components/start/UpdateAppModal.svelte

@ -0,0 +1,111 @@
<script>
import { writable, get as svelteGet } from "svelte/store"
import {
notifications,
Input,
Modal,
ModalContent,
Body,
} from "@budibase/bbui"
import { hostingStore } from "builderStore"
import { apps } from "stores/portal"
import { string, object } from "yup"
import { onMount } from "svelte"
import { capitalise } from "helpers"
const values = writable({ name: null })
const errors = writable({})
const touched = writable({})
const validator = {
name: string().required("Your application must have a name"),
}
export let app
let modal
let valid = false
let dirty = false
$: checkValidity($values, validator)
$: {
// prevent validation by setting name to undefined without an app
if (app) {
$values.name = app?.name
}
}
onMount(async () => {
await hostingStore.actions.fetchDeployedApps()
const existingAppNames = svelteGet(hostingStore).deployedAppNames
validator.name = string()
.required("Your application must have a name")
.test(
"non-existing-app-name",
"Another app with the same name already exists",
value => {
return !existingAppNames.some(
appName => dirty && appName.toLowerCase() === value.toLowerCase()
)
}
)
})
const checkValidity = async (values, validator) => {
const obj = object().shape(validator)
Object.keys(validator).forEach(key => ($errors[key] = null))
try {
await obj.validate(values, { abortEarly: false })
} catch (validationErrors) {
validationErrors.inner.forEach(error => {
$errors[error.path] = capitalise(error.message)
})
}
valid = await obj.isValid(values)
}
async function updateApp() {
try {
// Update App
await apps.update(app.instance._id, $values.name)
hide()
} catch (error) {
console.error(error)
notifications.error(error)
}
}
export const show = () => {
modal.show()
}
export const hide = () => {
modal.hide()
}
const onCancel = () => {
hide()
}
const onShow = () => {
dirty = false
}
</script>
<Modal bind:this={modal} on:hide={onCancel} on:show={onShow}>
<ModalContent
title={"Update app"}
confirmText={"Update app"}
onConfirm={updateApp}
disabled={!(valid && dirty)}
>
<Body size="S">
Give your new app a name, and choose which groups have access (paid plans
only).
</Body>
<Input
bind:value={$values.name}
error={$touched.name && $errors.name}
on:blur={() => ($touched.name = true)}
on:change={() => (dirty = true)}
label="Name"
/>
</ModalContent>
</Modal>

10
packages/builder/src/pages/builder/portal/apps/index.svelte

@ -14,6 +14,7 @@
Body, Body,
} from "@budibase/bbui" } from "@budibase/bbui"
import CreateAppModal from "components/start/CreateAppModal.svelte" import CreateAppModal from "components/start/CreateAppModal.svelte"
import UpdateAppModal from "components/start/UpdateAppModal.svelte"
import api, { del } from "builderStore/api" import api, { del } from "builderStore/api"
import analytics from "analytics" import analytics from "analytics"
import { onMount } from "svelte" import { onMount } from "svelte"
@ -30,6 +31,7 @@
let template let template
let selectedApp let selectedApp
let creationModal let creationModal
let updatingModal
let deletionModal let deletionModal
let unpublishModal let unpublishModal
let creatingApp = false let creatingApp = false
@ -164,6 +166,11 @@
selectedApp = null selectedApp = null
} }
const updateApp = async app => {
selectedApp = app
updatingModal.show()
}
const releaseLock = async app => { const releaseLock = async app => {
try { try {
const response = await del(`/api/dev/${app.devId}/lock`) const response = await del(`/api/dev/${app.devId}/lock`)
@ -236,6 +243,7 @@
{editApp} {editApp}
{exportApp} {exportApp}
{deleteApp} {deleteApp}
{updateApp}
/> />
{/each} {/each}
</div> </div>
@ -289,6 +297,8 @@
Are you sure you want to unpublish the app <b>{selectedApp?.name}</b>? Are you sure you want to unpublish the app <b>{selectedApp?.name}</b>?
</ConfirmDialog> </ConfirmDialog>
<UpdateAppModal app={selectedApp} bind:this={updatingModal} />
<style> <style>
.title, .title,
.filter { .filter {

4
packages/builder/src/pages/builder/portal/manage/users/_components/UpdateRolesModal.svelte

@ -48,7 +48,7 @@
on:change on:change
{options} {options}
label="Role" label="Role"
getOptionLabel={role => role.name} getOptionLabel={role => role.label}
getOptionValue={role => role._id} getOptionValue={role => role.value}
/> />
</ModalContent> </ModalContent>

21
packages/builder/src/stores/portal/apps.js

@ -1,6 +1,7 @@
import { writable } from "svelte/store" import { writable } from "svelte/store"
import { get } from "builderStore/api" import { get } from "builderStore/api"
import { AppStatus } from "../../constants" import { AppStatus } from "../../constants"
import api from "../../builderStore/api"
export function createAppStore() { export function createAppStore() {
const store = writable([]) const store = writable([])
@ -53,9 +54,29 @@ export function createAppStore() {
} }
} }
async function update(appId, name) {
const response = await api.put(`/api/applications/${appId}`, { name })
if (response.status === 200) {
store.update(state => {
const updatedAppIndex = state.findIndex(
app => app.instance._id === appId
)
if (updatedAppIndex !== -1) {
const updatedApp = state[updatedAppIndex]
updatedApp.name = name
state.apps = state.splice(updatedAppIndex, 1, updatedApp)
}
return state
})
} else {
throw new Error("Error updating name")
}
}
return { return {
subscribe: store.subscribe, subscribe: store.subscribe,
load, load,
update,
} }
} }

1
packages/client/src/constants.js

@ -6,6 +6,7 @@ export const ActionTypes = {
ValidateForm: "ValidateForm", ValidateForm: "ValidateForm",
RefreshDatasource: "RefreshDatasource", RefreshDatasource: "RefreshDatasource",
SetDataProviderQuery: "SetDataProviderQuery", SetDataProviderQuery: "SetDataProviderQuery",
ClearForm: "ClearForm",
} }
export const ApiVersion = "1" export const ApiVersion = "1"

9
packages/client/src/utils/buttonActions.js

@ -86,6 +86,14 @@ const logoutHandler = async () => {
await authStore.actions.logOut() await authStore.actions.logOut()
} }
const clearFormHandler = async (action, context) => {
return await executeActionHandler(
context,
action.parameters.componentId,
ActionTypes.ClearForm
)
}
const handlerMap = { const handlerMap = {
["Save Row"]: saveRowHandler, ["Save Row"]: saveRowHandler,
["Delete Row"]: deleteRowHandler, ["Delete Row"]: deleteRowHandler,
@ -95,6 +103,7 @@ const handlerMap = {
["Validate Form"]: validateFormHandler, ["Validate Form"]: validateFormHandler,
["Refresh Datasource"]: refreshDatasourceHandler, ["Refresh Datasource"]: refreshDatasourceHandler,
["Log Out"]: logoutHandler, ["Log Out"]: logoutHandler,
["Clear Form"]: clearFormHandler,
} }
const confirmTextMap = { const confirmTextMap = {

3
packages/standard-components/manifest.json

@ -1647,7 +1647,8 @@
"hasChildren": true, "hasChildren": true,
"illegalChildren": ["section"], "illegalChildren": ["section"],
"actions": [ "actions": [
"ValidateForm" "ValidateForm",
"ClearForm"
], ],
"styles": ["size"], "styles": ["size"],
"settings": [ "settings": [

27
packages/standard-components/src/forms/InnerForm.svelte

@ -64,6 +64,13 @@
}) })
return get(formState).valid return get(formState).valid
}, },
clear: () => {
const fields = Object.keys(fieldMap)
fields.forEach(field => {
const { fieldApi } = fieldMap[field]
fieldApi.clearValue()
})
},
} }
// Provide both form API and state to children // Provide both form API and state to children
@ -72,6 +79,7 @@
// Action context to pass to children // Action context to pass to children
const actions = [ const actions = [
{ type: ActionTypes.ValidateForm, callback: formApi.validate }, { type: ActionTypes.ValidateForm, callback: formApi.validate },
{ type: ActionTypes.ClearForm, callback: formApi.clear },
] ]
// Creates an API for a specific field // Creates an API for a specific field
@ -108,8 +116,27 @@
return !newError return !newError
} }
const clearValue = () => {
const { fieldState } = fieldMap[field]
const newValue = initialValues[field] ?? defaultValue
fieldState.update(state => {
state.value = newValue
state.error = null
return state
})
formState.update(state => {
state.values = { ...state.values, [field]: newValue }
delete state.errors[field]
state.valid = Object.keys(state.errors).length === 0
return state
})
}
return { return {
setValue, setValue,
clearValue,
validate: () => { validate: () => {
const { fieldState } = fieldMap[field] const { fieldState } = fieldMap[field]
setValue(get(fieldState).value, true) setValue(get(fieldState).value, true)

Loading…
Cancel
Save