merge master

This commit is contained in:
Keviin Åberg Kultalahti 2021-01-29 13:13:43 +01:00
commit f8f15b7b4b
41 changed files with 690 additions and 1108 deletions

View File

@ -34,3 +34,14 @@ jobs:
CI: true CI: true
name: Budibase CI name: Budibase CI
- run: yarn test:e2e:ci - run: yarn test:e2e:ci
- name: Build and Push Staging Docker Image
# Only run on push
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
run: |
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
yarn build:docker:staging
env:
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}

View File

@ -1,9 +1,15 @@
#!/bin/bash #!/bin/bash
tag=$1
tag=${tag:-latest}
pushd ../../build pushd ../../build
docker-compose build --force app-service docker-compose build --force app-service
docker-compose build --force worker-service docker-compose build --force worker-service
docker tag build_app-service budibase/budibase-apps:latest
docker tag build_app-service budibase/budibase-apps:$tag
docker tag build_worker-service budibase/budibase-worker:$tag
docker push budibase/budibase-apps docker push budibase/budibase-apps
docker tag build_worker-service budibase/budibase-worker:latest
docker push budibase/budibase-worker docker push budibase/budibase-worker
popd popd

View File

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

View File

@ -33,6 +33,7 @@
"format": "prettier --write \"{,!(node_modules)/**/}*.{js,jsx,svelte}\"", "format": "prettier --write \"{,!(node_modules)/**/}*.{js,jsx,svelte}\"",
"test:e2e": "lerna run cy:test", "test:e2e": "lerna run cy:test",
"test:e2e:ci": "lerna run cy:ci", "test:e2e:ci": "lerna run cy:ci",
"build:docker": "cd hosting/scripts/linux/ && ./release-to-docker-hub.sh && cd -" "build:docker": "cd hosting/scripts/linux/ && ./release-to-docker-hub.sh && cd -",
"build:docker:staging": "cd hosting/scripts/linux/ && ./release-to-docker-hub.sh staging && cd -"
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/builder", "name": "@budibase/builder",
"version": "0.5.3", "version": "0.6.2",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
@ -63,10 +63,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^1.55.1", "@budibase/bbui": "^1.55.2",
"@budibase/client": "^0.5.3", "@budibase/client": "^0.6.2",
"@budibase/colorpicker": "^1.0.1", "@budibase/colorpicker": "^1.0.1",
"@budibase/string-templates": "^0.5.3", "@budibase/string-templates": "^0.6.2",
"@budibase/svelte-ag-grid": "^0.0.16", "@budibase/svelte-ag-grid": "^0.0.16",
"@sentry/browser": "5.19.1", "@sentry/browser": "5.19.1",
"@svelteschool/svelte-forms": "^0.7.0", "@svelteschool/svelte-forms": "^0.7.0",

View File

@ -106,6 +106,16 @@ function highlightFeedbackIcon() {
return isFeedbackTimeElapsed(firstRunStr) return isFeedbackTimeElapsed(firstRunStr)
} }
// Opt In/Out
const ifAnalyticsEnabled = func => () => {
if (analyticsEnabled && process.env.POSTHOG_TOKEN) {
return func()
}
}
const disabled = () => posthog.has_opted_out_capturing()
const optIn = () => posthog.opt_in_capturing()
const optOut = () => posthog.opt_out_capturing()
export default { export default {
activate, activate,
identify, identify,
@ -115,4 +125,7 @@ export default {
requestFeedbackOnDeploy, requestFeedbackOnDeploy,
submitFeedback, submitFeedback,
highlightFeedbackIcon, highlightFeedbackIcon,
disabled: ifAnalyticsEnabled(disabled),
optIn: ifAnalyticsEnabled(optIn),
optOut: ifAnalyticsEnabled(optOut),
} }

View File

@ -43,6 +43,7 @@
<Input <Input
value={field.name} value={field.name}
secondary secondary
placeholder="Enter field name"
on:change={fieldNameChanged(field.name)} /> on:change={fieldNameChanged(field.name)} />
<Select <Select
secondary secondary

View File

@ -0,0 +1,41 @@
<script>
export let width = "100"
export let height = "100"
</script>
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
{width}
{height}
viewBox="0 0 2900 2000">
<g id="layer101">
<path
fill="#5b350f"
d="M735 1983 c-137 -19 -322 -95 -431 -175 -138 -103 -250 -315 -284 -542 -24 -161 22 -298 129 -382 123 -97 318 -180 577 -243 l130 -32 29 -67 c41 -94 135 -231 199 -289 292 -267 732 -319 1094 -128 148 79 224 151 418 400 137 176 173 242 228 414 41 130 70 301 70 416 0 236 -146 421 -382 484 -206 56 -392 19 -733 -144 -96 -47 -181 -85 -189 -85 -7 0 -61 46 -119 103 -163 159 -283 231 -433 261 -71 15 -229 19 -303 9z" />
</g>
<g id="layer102">
<path
fill="#406f23"
d="M735 1983 c-137 -19 -322 -95 -431 -175 -138 -103 -250 -315 -284 -542 -24 -161 22 -298 129 -382 123 -97 318 -180 577 -243 l130 -32 29 -67 c41 -94 135 -231 199 -289 292 -267 732 -319 1094 -128 148 79 224 151 418 400 137 176 173 242 228 414 41 130 70 301 70 416 0 236 -146 421 -382 484 -206 56 -392 19 -733 -144 -96 -47 -181 -85 -189 -85 -7 0 -61 46 -119 103 -163 159 -283 231 -433 261 -71 15 -229 19 -303 9z m460 -643 c163 -51 296 -150 329 -246 23 -69 20 -93 -20 -174 -51 -100 -126 -173 -238 -230 l-89 -45 -131 0 c-151 0 -222 18 -323 84 -138 89 -243 264 -243 406 0 62 12 76 120 134 160 85 229 102 405 96 87 -2 139 -9 190 -25z" />
</g>
<g id="layer103">
<path
fill="#c6d821"
d="M2215 1789 c-27 -4 -68 -15 -90 -23 -42 -15 -264 -116 -397 -180 l-77 -38 78 -102 c102 -134 144 -208 175 -305 37 -116 49 -200 44 -309 -7 -157 -46 -218 -175 -278 -94 -44 -178 -56 -313 -43 -108 10 -401 53 -479 70 -24 5 -45 8 -47 6 -2 -2 20 -44 49 -92 67 -115 176 -225 281 -284 87 -49 235 -103 335 -121 91 -16 253 -14 346 5 175 37 269 95 419 264 307 347 422 563 457 859 18 148 9 247 -29 325 -88 179 -340 286 -577 246z" />
<path
fill="#c6d821"
d="M549 1680 c-272 -34 -426 -142 -495 -346 -22 -68 -26 -91 -22 -164 6 -115 32 -173 112 -248 70 -65 236 -153 370 -196 241 -79 722 -172 985 -192 180 -14 343 55 404 170 34 64 30 168 -11 271 -88 223 -245 373 -568 542 -222 116 -379 161 -584 168 -69 2 -155 0 -191 -5z m646 -340 c163 -51 296 -150 329 -246 23 -69 20 -93 -20 -174 -50 -100 -126 -173 -235 -229 -84 -42 -90 -44 -200 -48 -190 -8 -312 37 -434 161 -98 99 -155 221 -155 331 0 62 12 76 120 134 160 85 229 102 405 96 87 -2 139 -9 190 -25z" />
</g>
<g id="layer104">
<path
fill="#f6f654"
d="M2190 1721 c-57 -19 -469 -184 -494 -198 -12 -7 -5 -21 34 -72 28 -35 71 -96 97 -137 l46 -74 69 0 c38 0 83 -6 102 -14 52 -22 105 -75 139 -140 28 -55 32 -70 32 -147 0 -84 -2 -90 -48 -183 -96 -187 -234 -296 -376 -296 -44 0 -80 7 -110 20 -28 13 -65 20 -105 20 -72 0 -264 22 -426 49 -63 10 -117 17 -118 15 -10 -8 59 -113 110 -169 106 -115 238 -183 448 -231 155 -35 313 -29 447 17 78 27 210 117 279 191 82 89 277 353 336 456 92 159 137 344 125 513 -9 117 -37 189 -101 258 -109 116 -332 172 -486 122z" />
<path
fill="#f6f654"
d="M490 1624 c-153 -33 -275 -125 -339 -257 -54 -109 -59 -201 -17 -301 32 -76 164 -189 288 -245 51 -23 260 -91 281 -91 3 0 -21 25 -54 56 -94 91 -150 193 -170 313 -14 90 1 111 123 172 161 81 195 91 343 96 111 5 143 2 215 -16 221 -55 380 -192 380 -327 0 -52 -46 -143 -106 -212 -75 -85 -233 -170 -323 -173 -25 -1 -25 -1 4 -10 140 -43 411 -62 527 -38 142 30 238 125 238 234 0 88 -58 230 -139 337 -105 139 -431 343 -676 423 -161 52 -429 71 -575 39z" />
</g>
</svg>

View File

@ -0,0 +1,83 @@
<script>
export let width = "100"
export let height = "100"
</script>
<svg
{width}
{height}
viewBox="0 0 147 147"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M128.244 27.5625H83.3444L74.1569
45.9375H22.9688V124.031H137.812V27.5625H128.244ZM128.625
45.9375H92.7478L97.5621 36.75H128.625V45.9375Z"
fill="#2A4B59" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M73.5827 67.597C72.6671 67.5874 71.7543 67.6986 70.8678
67.9278V68.061H70.9964C71.6302 69.0199 72.3383 69.9277 73.1141
70.7759C73.647 71.837 74.111 72.889 74.6393 73.9502L74.7679 73.8169C75.2581
73.4258 75.6415 72.917 75.8825 72.3379C76.1234 71.7589 76.2141 71.1283
76.146 70.5048C75.8564 70.0602 75.5911 69.6001 75.3513 69.1267C74.9562
68.4652 74.0926 68.1345 73.5597 67.6062"
fill="white" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M139.319 125.345C134.95 125.032 130.564 125.658 126.457 127.183C125.464
127.578 123.875 127.578 123.742 128.836C124.275 129.365 124.339 130.214
124.808 130.959C125.714 132.492 126.878 133.858 128.248 134.997C129.626
136.058 131.028 137.11 132.488 138.038C135.07 139.632 138.001 140.555
140.495 142.144C141.956 143.063 143.408 144.262 144.873 145.259C145.599
145.787 146.058 146.637 146.986 146.977V146.775C146.712 146.039 146.374
145.329 145.976 144.653C145.314 143.996 144.653 143.394 143.986
142.737C142.043 140.171 139.72 137.917 137.096 136.053C134.974 134.592
130.338 132.608 129.479 130.164L129.346 130.031C130.906 129.811 132.443
129.454 133.94 128.965C136.186 128.372 138.24 128.506 140.56 127.913C141.621
127.647 143.541 126.994 143.541 126.994V125.961C142.356 124.785 141.51
123.204 140.266 122.097C136.876 119.139 133.264 116.447 129.461
114.045C127.426 112.735 124.808 111.885 122.649 110.769C121.868 110.374
120.558 110.181 120.099 109.524C119.022 107.943 118.121 106.248 117.412
104.471C115.475 100.744 113.684 96.944 112.042 93.0786C111.076 90.527
109.961 88.0344 108.702 85.6138C102.629 75.3049 93.8692 66.8402 83.3582
61.1245C80.7056 59.8389 77.8847 58.9342 74.9792 58.4372C73.3392 58.3683
71.7038 58.2396 70.0685 58.1753C69.0107 57.4823 68.0036 56.7147 67.055
55.8784C63.3202 53.5218 53.7009 48.4136 50.9493 55.1572C49.1807 59.4156
53.5677 63.6051 55.0836 65.7688C56.3514 67.2941 57.4683 68.9387 58.4187
70.6795C58.8781 71.7912 59.0067 72.9764 59.4707 74.1524C60.4281 77.1443
61.5648 80.0758 62.8746 82.931C63.5769 84.3429 64.3863 85.699 65.2956
86.9873C65.8238 87.7131 66.738 88.0347 66.9355 89.2199C66.2106 90.78 65.7036
92.4324 65.4288 94.1306C64.2846 97.7426 63.8637 101.545 64.19 105.32C64.5163
109.094 65.5835 112.768 67.3306 116.13C68.378 117.765 70.8678 121.372
74.2212 119.993C77.1658 118.817 76.5181 115.083 77.3633 111.812C77.5609
111.022 77.4276 110.503 77.8227 109.974V110.618C77.8227 110.618 79.4948
114.293 80.3217 116.171C82.5698 119.591 85.3631 122.618 88.5905
125.134C89.8767 126.245 90.9145 127.614 91.6361
129.153V130.339H93.1153C93.0837 129.805 92.9362 129.285 92.6832
128.815C92.4301 128.344 92.0775 127.935 91.6499 127.614C90.4391 126.367
89.33 125.024 88.3332 123.6C85.6231 119.742 83.234 115.669 81.1899
111.421C80.1655 109.34 79.2743 107.071 78.4337 104.99C78.0524 104.191
78.0524 102.983 77.4139 102.583C76.2312 103.981 75.2053 105.505 74.3544
107.126C73.3203 110.403 72.7195 113.8 72.5675 117.233C72.3148 117.301
72.4388 117.233 72.3148 117.366C70.2752 116.833 69.5586 114.61 68.8052
112.772C66.8219 107.009 66.6218 100.781 68.231 94.9023C68.6903 93.5242
70.5967 89.0821 69.825 87.7453C69.4391 86.4682 68.1666 85.7378 67.4638
84.7318C66.5711 83.3765 65.8024 81.9436 65.1669 80.4504C63.6372 76.7065
62.8701 72.5538 61.2117 68.8098C60.2956 67.0053 59.2292 65.2811 58.0236
63.6557C56.6915 62.031 55.5163 60.2837 54.514 58.4372C54.2707 58.0251
54.1215 57.5643 54.077 57.0878C54.0326 56.6113 54.0939 56.1309 54.2568
55.6809C54.2928 55.4547 54.4023 55.2467 54.5684 55.0889C54.7344 54.9312
54.9478 54.8325 55.1755 54.8081C56.0024 54.0731 58.3636 55.0056 59.1905
55.4099C61.4133 56.2841 63.5306 57.4059 65.5023 58.7541C66.421 59.4248
68.4974 61.1245 68.4974 61.1245H69.1176C71.2353 61.5839 73.624 61.2531
75.6085 61.8503C78.9646 62.967 82.1671 64.5011 85.1406 66.4165C93.9149
72.002 101.049 79.8174 105.812 89.0637C106.607 90.5842 106.942 91.9761
107.65 93.561C109.028 96.8133 110.764 100.125 112.152 103.3C113.372 106.43
114.951 109.408 116.856 112.175C117.848 113.553 121.822 114.293 123.609
115.023C125.17 115.557 126.703 116.17 128.202 116.86C130.453 118.239 132.7
119.842 134.822 121.367C135.879 122.162 139.191 123.815 139.388 125.143"
fill="#F3FDFF" />
</svg>

View File

@ -6,6 +6,8 @@ import CouchDB from "./CouchDB.svelte"
import S3 from "./S3.svelte" import S3 from "./S3.svelte"
import Airtable from "./Airtable.svelte" import Airtable from "./Airtable.svelte"
import SqlServer from "./SQLServer.svelte" import SqlServer from "./SQLServer.svelte"
import MySQL from "./MySQL.svelte"
import ArangoDB from "./ArangoDB.svelte"
export default { export default {
POSTGRES: Postgres, POSTGRES: Postgres,
@ -16,4 +18,6 @@ export default {
SQL_SERVER: SqlServer, SQL_SERVER: SqlServer,
S3: S3, S3: S3,
AIRTABLE: Airtable, AIRTABLE: Airtable,
MYSQL: MySQL,
ARANGODB: ArangoDB,
} }

View File

@ -20,12 +20,9 @@
</script> </script>
<div on:click|stopPropagation bind:this={anchor}> <div on:click|stopPropagation bind:this={anchor}>
<TextButton <TextButton text on:click={dropdown.show} active={false}>
text <Icon name="add" />
on:click={dropdown.show} Add Parameters
active={false}>
<Icon name="add" />
Add Parameters
</TextButton> </TextButton>
<DropdownMenu align="right" {anchor} bind:this={dropdown}> <DropdownMenu align="right" {anchor} bind:this={dropdown}>
<div class="wrapper"> <div class="wrapper">
@ -39,4 +36,4 @@
padding: var(--spacing-xl); padding: var(--spacing-xl);
min-width: 600px; min-width: 600px;
} }
</style> </style>

View File

@ -1,6 +1,6 @@
<script> <script>
import { notificationStore } from "builderStore/store/notifications" import { notificationStore } from "builderStore/store/notifications"
import { flip } from 'svelte/animate'; import { flip } from "svelte/animate"
import { fly } from "svelte/transition" import { fly } from "svelte/transition"
export let themes = { export let themes = {

View File

@ -26,10 +26,9 @@
<div class="drawer-contents"> <div class="drawer-contents">
<div class="container" data-cy="binding-dropdown-modal"> <div class="container" data-cy="binding-dropdown-modal">
<div class="list"> <div class="list">
<Heading extraSmall>Objects</Heading>
<Spacer medium />
{#if context} {#if context}
<Heading extraSmall>Tables</Heading> <Heading extraSmall>Columns</Heading>
<Spacer small />
<ul> <ul>
{#each context as { readableBinding }} {#each context as { readableBinding }}
<li on:click={() => addToText(readableBinding)}> <li on:click={() => addToText(readableBinding)}>
@ -53,8 +52,8 @@
<TextArea <TextArea
thin thin
bind:value bind:value
placeholder="Add text, or click the objects on the left to add them to the placeholder="Add text, or click the objects on the left to add them to
textbox." /> the textbox." />
</div> </div>
</div> </div>
</div> </div>
@ -63,15 +62,20 @@
.container { .container {
height: 100%; height: 100%;
display: grid; display: grid;
grid-template-columns: auto 1fr; grid-template-columns: 260px 1fr;
} }
.list { .list {
width: 150px; border-right: var(--border-light);
border-right: 1.5px solid var(--grey-4); padding: var(--spacing-l);
padding: var(--spacing-s); overflow: auto;
} }
.list::-webkit-scrollbar {
display: none;
}
.text { .text {
padding: var(--spacing-s); padding: var(--spacing-xl);
font-family: var(--font-sans); font-family: var(--font-sans);
} }
.text :global(p) { .text :global(p) {
@ -89,10 +93,12 @@
font-family: var(--font-sans); font-family: var(--font-sans);
font-size: var(--font-size-xs); font-size: var(--font-size-xs);
color: var(--grey-7); color: var(--grey-7);
padding: var(--spacing-s) 0; padding: var(--spacing-m) 0;
margin: auto 0px; margin: auto 0px;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
border: var(--border-light);
border-width: 1px 0 1px 0;
} }
li:hover { li:hover {

View File

@ -1,10 +1,10 @@
<script> <script>
import { import {
Button, Button,
TextButton,
Body, Body,
DropdownMenu, DropdownMenu,
ModalContent, ModalContent,
Spacer,
} from "@budibase/bbui" } from "@budibase/bbui"
import { AddIcon, ArrowDownIcon } from "components/common/Icons/" import { AddIcon, ArrowDownIcon } from "components/common/Icons/"
import actionTypes from "./actions" import actionTypes from "./actions"
@ -33,6 +33,9 @@
parameters: {}, parameters: {},
[EVENT_TYPE_KEY]: actionType.name, [EVENT_TYPE_KEY]: actionType.name,
} }
if (!actions) {
actions = []
}
actions.push(newAction) actions.push(newAction)
selectedAction = newAction selectedAction = newAction
actions = actions actions = actions
@ -48,12 +51,11 @@
<div class="actions-list"> <div class="actions-list">
<div> <div>
<div bind:this={addActionButton}> <div bind:this={addActionButton}>
<TextButton text small blue on:click={addActionDropdown.show}> <Spacer small />
<div style="height: 20px; width: 20px;"> <Button wide secondary on:click={addActionDropdown.show}>
<AddIcon />
</div>
Add Action Add Action
</TextButton> </Button>
<Spacer medium />
</div> </div>
<DropdownMenu <DropdownMenu
bind:this={addActionDropdown} bind:this={addActionDropdown}
@ -80,7 +82,7 @@
</div> </div>
<i <i
class="ri-close-fill" class="ri-close-fill"
style="margin-left: var(--spacing-m);" style="margin-left: auto;"
on:click={() => deleteAction(index)} /> on:click={() => deleteAction(index)} />
</div> </div>
{/each} {/each}
@ -107,7 +109,7 @@
.action-header > span { .action-header > span {
margin-bottom: var(--spacing-m); margin-bottom: var(--spacing-m);
font-size: var(--font-size-s); font-size: var(--font-size-xs);
} }
.action-header > span:hover, .action-header > span:hover,
@ -117,13 +119,13 @@
} }
.actions-list { .actions-list {
border: var(--border-light); border-right: var(--border-light);
padding: var(--spacing-s); padding: var(--spacing-s);
} }
.available-action { .available-action {
padding: var(--spacing-s); padding: var(--spacing-s);
font-size: var(--font-size-m); font-size: var(--font-size-xs);
cursor: pointer; cursor: pointer;
} }
@ -135,7 +137,7 @@
height: 40vh; height: 40vh;
display: grid; display: grid;
grid-gap: var(--spacing-m); grid-gap: var(--spacing-m);
grid-template-columns: 15% 1fr; grid-template-columns: 260px 1fr;
grid-auto-flow: column; grid-auto-flow: column;
min-height: 0; min-height: 0;
padding-top: 0; padding-top: 0;
@ -143,15 +145,13 @@
} }
.action-container { .action-container {
border: var(--border-light); border-top: var(--border-light);
border-width: 1px 0 0 0;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.selected-action-container { .selected-action-container {
padding-bottom: var(--spacing-s); padding: var(--spacing-xl);
padding-top: var(--spacing-s);
} }
a { a {
@ -164,4 +164,9 @@
a:hover { a:hover {
color: var(--blue); color: var(--blue);
} }
i:hover {
color: var(--red);
cursor: pointer;
}
</style> </style>

View File

@ -156,7 +156,7 @@
} }
.available-action { .available-action {
padding: var(--spacing-s); padding: var(--spacing-m);
font-size: var(--font-size-m); font-size: var(--font-size-m);
cursor: pointer; cursor: pointer;
} }

View File

@ -34,15 +34,17 @@
})) }))
return [...acc, ...viewsArr] return [...acc, ...viewsArr]
}, []) }, [])
$: queries = $backendUiStore.queries.map(query => ({ $: queries = $backendUiStore.queries
label: query.name, .filter(query => query.queryVerb === "read")
name: query.name, .map(query => ({
tableId: query._id, label: query.name,
...query, name: query.name,
schema: query.schema, tableId: query._id,
parameters: query.parameters, ...query,
type: "query", schema: query.schema,
})) parameters: query.parameters,
type: "query",
}))
$: bindableProperties = getBindableProperties( $: bindableProperties = getBindableProperties(
$currentAsset.props, $currentAsset.props,
$store.selectedComponentId $store.selectedComponentId

View File

@ -24,8 +24,8 @@
</script> </script>
<form on:submit|preventDefault> <form on:submit|preventDefault>
<div class="field"> <div class="field">
{#each schemaKeys as field} {#each schemaKeys as field}
<Input <Input
placeholder="Enter {field} name" placeholder="Enter {field} name"
outline outline
@ -33,8 +33,8 @@
type={schema.fields[field]?.type} type={schema.fields[field]?.type}
required={schema.fields[field]?.required} required={schema.fields[field]?.required}
bind:value={fields[field]} /> bind:value={fields[field]} />
{/each} {/each}
</div> </div>
</form> </form>
{#if schema.customisable} {#if schema.customisable}
<Editor <Editor

View File

@ -135,17 +135,22 @@
<Input placeholder="✎ Edit Query Name" bind:value={query.name} /> <Input placeholder="✎ Edit Query Name" bind:value={query.name} />
</div> </div>
{#if config} {#if config}
<div class="props"> <div class="props">
<div class="query-type">Query type: <span class="query-type-span">{config[query.queryVerb].type}</span></div> <div class="query-type">
<div class="select"> Query type:
<Select primary thin bind:value={query.queryVerb}> <span class="query-type-span">{config[query.queryVerb].type}</span>
{#each Object.keys(config) as queryVerb} </div>
<option value={queryVerb}>{queryVerb}</option> <div class="select">
{/each} <Select primary thin bind:value={query.queryVerb}>
</Select> {#each Object.keys(config) as queryVerb}
</div> <option value={queryVerb}>{queryVerb}</option>
{/each}
</Select>
</div>
</div> </div>
<EditQueryParamsPopover bind:parameters={query.parameters} bindable={false} /> <EditQueryParamsPopover
bind:parameters={query.parameters}
bindable={false} />
{/if} {/if}
</header> </header>
<Spacer extraLarge /> <Spacer extraLarge />
@ -164,7 +169,7 @@
<div class="viewer-controls"> <div class="viewer-controls">
<Button <Button
blue blue
disabled={data.length === 0} disabled={data.length === 0 || !query.name}
on:click={saveQuery}> on:click={saveQuery}>
Save Query Save Query
</Button> </Button>
@ -182,7 +187,11 @@
{#each fields as field, idx} {#each fields as field, idx}
<Spacer small /> <Spacer small />
<div class="field"> <div class="field">
<Input outline placeholder="Field Name" type={'text'} bind:value={field.name} /> <Input
outline
placeholder="Field Name"
type={'text'}
bind:value={field.name} />
<Select thin border bind:value={field.type}> <Select thin border bind:value={field.type}>
<option value={''}>Select a field type</option> <option value={''}>Select a field type</option>
<option value={'STRING'}>Text</option> <option value={'STRING'}>Text</option>
@ -195,8 +204,8 @@
on:click={() => deleteField(idx)} /> on:click={() => deleteField(idx)} />
</div> </div>
{/each} {/each}
<Spacer small /> <Spacer small />
<Button thin secondary on:click={newField}>Add Field</Button> <Button thin secondary on:click={newField}>Add Field</Button>
{/if} {/if}
</Switcher> </Switcher>
{/if} {/if}
@ -206,7 +215,6 @@
{/if} {/if}
<style> <style>
.input { .input {
width: 300px; width: 300px;
} }

View File

@ -20,7 +20,6 @@
} }
</script> </script>
{#if schema} {#if schema}
{#key query._id} {#key query._id}
{#if schema.type === QueryTypes.SQL} {#if schema.type === QueryTypes.SQL}

View File

@ -1,6 +1,7 @@
<script> <script>
import { Input, Label } from "@budibase/bbui" import { Input, Label, TextButton } from "@budibase/bbui"
import api from "builderStore/api" import api from "builderStore/api"
import { notifier } from "builderStore/store/notifications"
import { backendUiStore } from "builderStore" import { backendUiStore } from "builderStore"
import analytics from "analytics" import analytics from "analytics"
@ -10,7 +11,7 @@
if (key === "budibase") { if (key === "budibase") {
const isValid = await analytics.identifyByApiKey(value) const isValid = await analytics.identifyByApiKey(value)
if (!isValid) { if (!isValid) {
// TODO: add validation message notifier.danger("Your API Key is invalid.")
keys = { ...keys } keys = { ...keys }
return return
} }
@ -38,7 +39,10 @@
thin thin
edit edit
value={keys.budibase} value={keys.budibase}
label="Budibase API Key" /> label="Budibase Cloud API Key" />
<TextButton text medium blue href="https://portal.budi.live">
Log in to the Budibase Hosting Portal to get your API Key. →
</TextButton>
<div> <div>
<Label extraSmall grey>Instance ID (Webhooks)</Label> <Label extraSmall grey>Instance ID (Webhooks)</Label>
<span>{$backendUiStore.selectedDatabase._id}</span> <span>{$backendUiStore.selectedDatabase._id}</span>

View File

@ -1,6 +1,7 @@
<script> <script>
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import { hostingStore } from "builderStore" import { hostingStore } from "builderStore"
import { HostingTypes } from "constants/backend"
import { Input, ModalContent, Toggle } from "@budibase/bbui" import { Input, ModalContent, Toggle } from "@budibase/bbui"
import ThemeEditor from "components/settings/ThemeEditor.svelte" import ThemeEditor from "components/settings/ThemeEditor.svelte"
import analytics from "analytics" import analytics from "analytics"
@ -9,8 +10,10 @@
let hostingInfo let hostingInfo
let selfhosted = false let selfhosted = false
$: analyticsDisabled = analytics.disabled()
async function save() { async function save() {
hostingInfo.type = selfhosted ? "self" : "cloud" hostingInfo.type = selfhosted ? HostingTypes.SELF : HostingTypes.CLOUD
if (!selfhosted && hostingInfo._rev) { if (!selfhosted && hostingInfo._rev) {
hostingInfo = { hostingInfo = {
type: hostingInfo.type, type: hostingInfo.type,
@ -27,13 +30,21 @@
} }
function updateSelfHosting(event) { function updateSelfHosting(event) {
if (hostingInfo.type === "cloud" && event.target.checked) { if (hostingInfo.type === HostingTypes.CLOUD && event.target.checked) {
hostingInfo.hostingUrl = "localhost:10000" hostingInfo.hostingUrl = "localhost:10000"
hostingInfo.useHttps = false hostingInfo.useHttps = false
hostingInfo.selfHostKey = "budibase" hostingInfo.selfHostKey = "budibase"
} }
} }
function toggleAnalytics() {
if (analyticsDisabled) {
analytics.optIn()
} else {
analytics.optOut()
}
}
onMount(async () => { onMount(async () => {
hostingInfo = await hostingStore.actions.fetch() hostingInfo = await hostingStore.actions.fetch()
selfhosted = hostingInfo.type === "self" selfhosted = hostingInfo.type === "self"
@ -58,6 +69,16 @@
<Input bind:value={hostingInfo.selfHostKey} label="Hosting Key" /> <Input bind:value={hostingInfo.selfHostKey} label="Hosting Key" />
<Toggle thin text="HTTPS" bind:checked={hostingInfo.useHttps} /> <Toggle thin text="HTTPS" bind:checked={hostingInfo.useHttps} />
{/if} {/if}
<h5>Analytics</h5>
<p>
If you would like to send analytics that help us make budibase better,
please let us know below.
</p>
<Toggle
thin
text="Send Analytics To Budibase"
checked={!analyticsDisabled}
on:change={toggleAnalytics} />
</ModalContent> </ModalContent>
<style> <style>

View File

@ -22,36 +22,11 @@
//Move this to context="module" once svelte-forms is updated so that it can bind to stores correctly //Move this to context="module" once svelte-forms is updated so that it can bind to stores correctly
const createAppStore = writable({ currentStep: 0, values: {} }) const createAppStore = writable({ currentStep: 0, values: {} })
export let hasKey
export let template export let template
let isApiKeyValid
let lastApiKey let lastApiKey
let fetchApiKeyPromise let fetchApiKeyPromise
const validateApiKey = async apiKey => {
if (isApiKeyValid) return true
if (!apiKey) return false
// make sure we only fetch once, unless API Key is changed
if (isApiKeyValid === undefined || apiKey !== lastApiKey) {
lastApiKey = apiKey
// svelte reactivity was causing a requst to get fired mutiple times
// so, we make everything await the same promise, if one exists
if (!fetchApiKeyPromise) {
fetchApiKeyPromise = analytics.identifyByApiKey(apiKey)
}
isApiKeyValid = await fetchApiKeyPromise
fetchApiKeyPromise = undefined
}
return isApiKeyValid
}
const apiValidation = {
apiKey: string()
.required("Please enter your API key.")
.test("valid-apikey", "This API key is invalid", validateApiKey),
}
const infoValidation = { const infoValidation = {
applicationName: string().required("Your application must have a name."), applicationName: string().required("Your application must have a name."),
} }
@ -66,7 +41,7 @@
let submitting = false let submitting = false
let errors = {} let errors = {}
let validationErrors = {} let validationErrors = {}
let validationSchemas = [apiValidation, infoValidation, userValidation] let validationSchemas = [infoValidation, userValidation]
function buildStep(component) { function buildStep(component) {
return { return {
@ -76,7 +51,7 @@
} }
// steps need to be initialized for cypress from the get go // steps need to be initialized for cypress from the get go
let steps = [buildStep(API), buildStep(Info), buildStep(User)] let steps = [buildStep(Info), buildStep(User)]
onMount(async () => { onMount(async () => {
let hostingInfo = await hostingStore.actions.fetch() let hostingInfo = await hostingStore.actions.fetch()
@ -87,17 +62,11 @@
infoValidation.applicationName = string() infoValidation.applicationName = string()
.required("Your application must have a name.") .required("Your application must have a name.")
.notOneOf(existingAppNames) .notOneOf(existingAppNames)
isApiKeyValid = true
steps = [buildStep(Info), buildStep(User)] steps = [buildStep(Info), buildStep(User)]
validationSchemas = [infoValidation, userValidation] validationSchemas = [infoValidation, userValidation]
} }
}) })
if (hasKey) {
validationSchemas.shift()
steps.shift()
}
// Handles form navigation // Handles form navigation
const back = () => { const back = () => {
if ($createAppStore.currentStep > 0) { if ($createAppStore.currentStep > 0) {
@ -145,11 +114,6 @@
async function createNewApp() { async function createNewApp() {
submitting = true submitting = true
try { try {
// Add API key if there is none.
if (!hasKey) {
await updateKey(["budibase", $createAppStore.values.apiKey])
}
// Create App // Create App
const appResp = await post("/api/applications", { const appResp = await post("/api/applications", {
name: $createAppStore.values.applicationName, name: $createAppStore.values.applicationName,

View File

@ -87,3 +87,8 @@ export const FILE_TYPES = {
CODE: ["js", "rs", "py", "java", "rb", "hs", "yml"], CODE: ["js", "rs", "py", "java", "rb", "hs", "yml"],
DOCUMENT: ["odf", "docx", "doc", "pdf", "csv"], DOCUMENT: ["odf", "docx", "doc", "pdf", "csv"],
} }
export const HostingTypes = {
CLOUD: "cloud",
SELF: "self",
}

View File

@ -51,7 +51,6 @@
<div class="query-list-item" on:click={() => onClickQuery(query)}> <div class="query-list-item" on:click={() => onClickQuery(query)}>
<p class="query-name">{query.name}</p> <p class="query-name">{query.name}</p>
<p>{query.queryVerb}</p> <p>{query.queryVerb}</p>
<p>4000 records</p>
<p></p> <p></p>
</div> </div>
{/each} {/each}
@ -59,7 +58,6 @@
</div> </div>
</div> </div>
</section> </section>
{/if} {/if}
<style> <style>
@ -115,7 +113,7 @@
background: var(--background); background: var(--background);
border: var(--border-grey); border: var(--border-grey);
display: grid; display: grid;
grid-template-columns: 2fr 0.75fr 0.75fr 1fr 20px; grid-template-columns: 2fr 0.75fr 20px;
align-items: center; align-items: center;
padding: var(--spacing-m) var(--layout-xs); padding: var(--spacing-m) var(--layout-xs);
gap: var(--layout-xs); gap: var(--layout-xs);

View File

@ -1,7 +1,7 @@
<script> <script>
import { onMount } from "svelte" import { onMount } from "svelte"
import { Button, Spacer, Modal } from "@budibase/bbui" import { Button, Spacer, Modal } from "@budibase/bbui"
import { store } from "builderStore" import { store, hostingStore } from "builderStore"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import api from "builderStore/api" import api from "builderStore/api"
import Spinner from "components/common/Spinner.svelte" import Spinner from "components/common/Spinner.svelte"
@ -17,6 +17,18 @@
$: appId = $store.appId $: appId = $store.appId
async function deployApp() { async function deployApp() {
// Must have cloud or self host API key to deploy
if (!$hostingStore.hostingInfo?.selfHostKey) {
const response = await api.get(`/api/keys/`)
const userKeys = await response.json()
if (!userKeys.budibase) {
notifier.danger(
"No budibase API Keys configured. You must set either a self hosted or cloud API key to deploy your budibase app."
)
}
return
}
const DEPLOY_URL = `/api/deploy` const DEPLOY_URL = `/api/deploy`
try { try {
@ -29,6 +41,7 @@
analytics.captureEvent("Deployed App", { analytics.captureEvent("Deployed App", {
appId, appId,
hostingType: $hostingStore.hostingInfo?.type,
}) })
if (analytics.requestFeedbackOnDeploy()) { if (analytics.requestFeedbackOnDeploy()) {
@ -37,6 +50,7 @@
} catch (err) { } catch (err) {
analytics.captureEvent("Deploy App Failed", { analytics.captureEvent("Deploy App Failed", {
appId, appId,
hostingType: $hostingStore.hostingInfo?.type,
}) })
analytics.captureException(err) analytics.captureException(err)
notifier.danger("Deployment unsuccessful. Please try again later.") notifier.danger("Deployment unsuccessful. Please try again later.")

View File

@ -1,4 +1,5 @@
<script> <script>
import { store } from "builderStore"
import api from "builderStore/api" import api from "builderStore/api"
import AppList from "components/start/AppList.svelte" import AppList from "components/start/AppList.svelte"
import { get } from "builderStore/api" import { get } from "builderStore/api"
@ -35,10 +36,6 @@
hasKey = true hasKey = true
analytics.identify(keys.userId) analytics.identify(keys.userId)
} }
if (!keys.budibase) {
modal.show()
}
} }
function selectTemplate(newTemplate) { function selectTemplate(newTemplate) {

View File

@ -842,10 +842,10 @@
lodash "^4.17.19" lodash "^4.17.19"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@budibase/bbui@^1.55.1": "@budibase/bbui@^1.55.2":
version "1.55.1" version "1.55.2"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.55.1.tgz#291fb6fa10479b49f078d3a911ad0ed42c2e6b12" resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.55.2.tgz#be636e8b86b7e516a08eb626bb50c4b1f9774bf8"
integrity sha512-bxsHBwkOqCtuFz89e0hAXwvwycfS4xPPrEge5PxK1Lh3uqetO4bXoIxYaIDjfi2Ku7CYIzEmOwSloNaQWeTF4g== integrity sha512-CevH/olxDFIko9uwYUpUTevPmpShrLwJSR7+wn/JetZERwhTwbLhOXzpsyXaK226qQ8vWhm0U31HRSKI1HwDDg==
dependencies: dependencies:
markdown-it "^12.0.2" markdown-it "^12.0.2"
quill "^1.3.7" quill "^1.3.7"

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/client", "name": "@budibase/client",
"version": "0.5.3", "version": "0.6.2",
"license": "MPL-2.0", "license": "MPL-2.0",
"main": "dist/budibase-client.js", "main": "dist/budibase-client.js",
"module": "dist/budibase-client.js", "module": "dist/budibase-client.js",
@ -9,14 +9,14 @@
"dev:builder": "rollup -cw" "dev:builder": "rollup -cw"
}, },
"dependencies": { "dependencies": {
"@budibase/string-templates": "^0.5.3", "@budibase/string-templates": "^0.6.2",
"deep-equal": "^2.0.1", "deep-equal": "^2.0.1",
"regexparam": "^1.3.0", "regexparam": "^1.3.0",
"shortid": "^2.2.15", "shortid": "^2.2.15",
"svelte-spa-router": "^3.0.5" "svelte-spa-router": "^3.0.5"
}, },
"devDependencies": { "devDependencies": {
"@budibase/standard-components": "^0.5.3", "@budibase/standard-components": "^0.6.2",
"@rollup/plugin-commonjs": "^16.0.0", "@rollup/plugin-commonjs": "^16.0.0",
"@rollup/plugin-node-resolve": "^10.0.0", "@rollup/plugin-node-resolve": "^10.0.0",
"fs-extra": "^8.1.0", "fs-extra": "^8.1.0",

View File

@ -2,9 +2,14 @@
import { writable } from "svelte/store" import { writable } from "svelte/store"
import { setContext, onMount } from "svelte" import { setContext, onMount } from "svelte"
import Component from "./Component.svelte" import Component from "./Component.svelte"
import NotificationDisplay from './NotificationDisplay.svelte' import NotificationDisplay from "./NotificationDisplay.svelte"
import SDK from "../sdk" import SDK from "../sdk"
import { createDataStore, initialise, screenStore, notificationStore } from "../store" import {
createDataStore,
initialise,
screenStore,
notificationStore,
} from "../store"
// Provide contexts // Provide contexts
setContext("sdk", SDK) setContext("sdk", SDK)
@ -24,4 +29,4 @@
{#if loaded && $screenStore.activeLayout} {#if loaded && $screenStore.activeLayout}
<Component definition={$screenStore.activeLayout.props} /> <Component definition={$screenStore.activeLayout.props} />
{/if} {/if}
<NotificationDisplay /> <NotificationDisplay />

View File

@ -1,60 +1,59 @@
<script> <script>
import { flip } from 'svelte/animate'; import { flip } from "svelte/animate"
import { fly } from "svelte/transition" import { fly } from "svelte/transition"
import { getContext } from "svelte" import { getContext } from "svelte"
const { notifications } = getContext("sdk") const { notifications } = getContext("sdk")
export let themes = { export let themes = {
danger: "#E26D69", danger: "#E26D69",
success: "#84C991", success: "#84C991",
warning: "#f0ad4e", warning: "#f0ad4e",
info: "#5bc0de", info: "#5bc0de",
default: "#aaaaaa", default: "#aaaaaa",
} }
</script> </script>
<div class="notifications"> <div class="notifications">
{#each $notifications as notification (notification.id)} {#each $notifications as notification (notification.id)}
<div <div
animate:flip animate:flip
class="toast" class="toast"
style="background: {themes[notification.type]};" style="background: {themes[notification.type]};"
transition:fly={{ y: -30 }}> transition:fly={{ y: -30 }}>
<div class="content">{notification.message}</div> <div class="content">{notification.message}</div>
{#if notification.icon}<i class={notification.icon} />{/if} {#if notification.icon}<i class={notification.icon} />{/if}
</div> </div>
{/each} {/each}
</div> </div>
<style> <style>
.notifications { .notifications {
position: fixed; position: fixed;
top: 10px; top: 10px;
left: 0; left: 0;
right: 0; right: 0;
margin: 0 auto; margin: 0 auto;
padding: 0; padding: 0;
z-index: 9999; z-index: 9999;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
pointer-events: none; pointer-events: none;
} }
.toast { .toast {
flex: 0 0 auto; flex: 0 0 auto;
margin-bottom: 10px; margin-bottom: 10px;
border-radius: var(--border-radius-s); border-radius: var(--border-radius-s);
/* The toasts now support being auto sized, so this static width could be removed */ /* The toasts now support being auto sized, so this static width could be removed */
width: 40vw; width: 40vw;
} }
.content { .content {
padding: 10px; padding: 10px;
display: block; display: block;
color: white; color: white;
font-weight: 500; font-weight: 500;
} }
</style> </style>

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/server", "name": "@budibase/server",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "0.5.3", "version": "0.6.2",
"description": "Budibase Web Server", "description": "Budibase Web Server",
"main": "src/electron.js", "main": "src/electron.js",
"repository": { "repository": {
@ -49,13 +49,14 @@
"author": "Budibase", "author": "Budibase",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@budibase/client": "^0.6.2",
"@budibase/string-templates": "^0.6.2",
"@elastic/elasticsearch": "^7.10.0", "@elastic/elasticsearch": "^7.10.0",
"@budibase/client": "^0.5.3",
"@budibase/string-templates": "^0.5.3",
"@koa/router": "^8.0.0", "@koa/router": "^8.0.0",
"@sendgrid/mail": "^7.1.1", "@sendgrid/mail": "^7.1.1",
"@sentry/node": "^5.19.2", "@sentry/node": "^5.19.2",
"airtable": "^0.10.1", "airtable": "^0.10.1",
"arangojs": "^7.2.0",
"aws-sdk": "^2.767.0", "aws-sdk": "^2.767.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"chmodr": "^1.2.0", "chmodr": "^1.2.0",
@ -82,6 +83,7 @@
"lodash": "^4.17.13", "lodash": "^4.17.13",
"mongodb": "^3.6.3", "mongodb": "^3.6.3",
"mssql": "^6.2.3", "mssql": "^6.2.3",
"mysql": "^2.18.1",
"node-fetch": "^2.6.0", "node-fetch": "^2.6.0",
"open": "^7.3.0", "open": "^7.3.0",
"pg": "^8.5.1", "pg": "^8.5.1",

View File

@ -77,11 +77,21 @@ exports.getLinkDocuments = async function({
} }
params.include_docs = !!includeDocs params.include_docs = !!includeDocs
try { try {
const response = await db.query(getQueryIndex(ViewNames.LINK), params) let linkRows = (await db.query(getQueryIndex(ViewNames.LINK), params)).rows
// filter to get unique entries
const foundIds = []
linkRows = linkRows.filter(link => {
const unique = foundIds.indexOf(link.id) === -1
if (unique) {
foundIds.push(link.id)
}
return unique
})
if (includeDocs) { if (includeDocs) {
return response.rows.map(row => row.doc) return linkRows.map(row => row.doc)
} else { } else {
return response.rows.map(row => row.value) return linkRows.map(row => row.value)
} }
} catch (err) { } catch (err) {
// check if the view doesn't exist, it should for all new instances // check if the view doesn't exist, it should for all new instances

View File

@ -0,0 +1,83 @@
const { Database, aql } = require("arangojs")
const { FIELD_TYPES, QUERY_TYPES } = require("./Integration")
const SCHEMA = {
docs: "https://github.com/arangodb/arangojs",
datasource: {
url: {
type: FIELD_TYPES.STRING,
default: "http://localhost:8529",
required: true,
},
username: {
type: FIELD_TYPES.STRING,
default: "root",
required: true,
},
password: {
type: FIELD_TYPES.PASSWORD,
required: true,
},
databaseName: {
type: FIELD_TYPES.STRING,
default: "_system",
required: true,
},
collection: {
type: FIELD_TYPES.STRING,
required: true,
},
},
query: {
read: {
type: QUERY_TYPES.SQL,
},
create: {
type: QUERY_TYPES.JSON,
},
},
}
class ArangoDBIntegration {
constructor(config) {
config.auth = {
username: config.username,
password: config.password,
}
this.config = config
this.client = new Database(config)
}
async read(query) {
try {
const result = await this.client.query(query.sql)
return result.all()
} catch (err) {
console.error("Error querying arangodb", err.message)
throw err
} finally {
this.client.close()
}
}
async create(query) {
const clc = this.client.collection(this.config.collection)
try {
const result = await this.client.query(
aql`INSERT ${query.json} INTO ${clc} RETURN NEW`
)
return result.all()
} catch (err) {
console.error("Error querying arangodb", err.message)
throw err
} finally {
this.client.close()
}
}
}
module.exports = {
schema: SCHEMA,
integration: ArangoDBIntegration,
}

View File

@ -3,10 +3,11 @@ const dynamodb = require("./dynamodb")
const mongodb = require("./mongodb") const mongodb = require("./mongodb")
const elasticsearch = require("./elasticsearch") const elasticsearch = require("./elasticsearch")
const couchdb = require("./couchdb") const couchdb = require("./couchdb")
// const redis = require("./redis")
const sqlServer = require("./microsoftSqlServer") const sqlServer = require("./microsoftSqlServer")
const s3 = require("./s3") const s3 = require("./s3")
const airtable = require("./airtable") const airtable = require("./airtable")
const mysql = require("./mysql")
const arangodb = require("./arangodb")
const DEFINITIONS = { const DEFINITIONS = {
POSTGRES: postgres.schema, POSTGRES: postgres.schema,
@ -17,6 +18,8 @@ const DEFINITIONS = {
SQL_SERVER: sqlServer.schema, SQL_SERVER: sqlServer.schema,
S3: s3.schema, S3: s3.schema,
AIRTABLE: airtable.schema, AIRTABLE: airtable.schema,
MYSQL: mysql.schema,
ARANGODB: arangodb.schema,
} }
const INTEGRATIONS = { const INTEGRATIONS = {
@ -28,6 +31,8 @@ const INTEGRATIONS = {
S3: s3.integration, S3: s3.integration,
SQL_SERVER: sqlServer.integration, SQL_SERVER: sqlServer.integration,
AIRTABLE: airtable.integration, AIRTABLE: airtable.integration,
MYSQL: mysql.integration,
ARANGODB: arangodb.integration,
} }
module.exports = { module.exports = {

View File

@ -0,0 +1,80 @@
const mysql = require("mysql")
const SCHEMA = {
docs: "https://github.com/mysqljs/mysql",
datasource: {
host: {
type: "string",
default: "localhost",
required: true,
},
user: {
type: "string",
default: "root",
required: true,
},
password: {
type: "password",
default: "root",
required: true,
},
database: {
type: "string",
required: true,
},
},
query: {
create: {
type: "sql",
},
read: {
type: "sql",
},
update: {
type: "sql",
},
delete: {
type: "sql",
},
},
}
class MySQLIntegration {
constructor(config) {
this.config = config
this.client = mysql.createConnection(config)
}
query(query) {
// Node MySQL is callback based, so we must wrap our call in a promise
return new Promise((resolve, reject) => {
this.client.connect()
return this.client.query(query.sql, (error, results) => {
if (error) return reject(error)
resolve(results)
this.client.end()
})
})
}
create(query) {
return this.query(query)
}
read(query) {
return this.query(query)
}
update(query) {
return this.query(query)
}
delete(query) {
return this.query(query)
}
}
module.exports = {
schema: SCHEMA,
integration: MySQLIntegration,
}

View File

@ -58,7 +58,7 @@ class PostgresIntegration {
async create({ sql }) { async create({ sql }) {
const response = await this.client.query(sql) const response = await this.client.query(sql)
return response.rows return response.rows.length ? response.rows : [{ created: true }]
} }
async read({ sql }) { async read({ sql }) {
@ -68,12 +68,12 @@ class PostgresIntegration {
async update({ sql }) { async update({ sql }) {
const response = await this.client.query(sql) const response = await this.client.query(sql)
return response.rows return response.rows.length ? response.rows : [{ updated: true }]
} }
async delete({ sql }) { async delete({ sql }) {
const response = await this.client.query(sql) const response = await this.client.query(sql)
return response.rows return response.rows.length ? response.rows : [{ deleted: true }]
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +0,0 @@
*
!dist/*
!components.json

View File

@ -10,6 +10,11 @@
"start:dev": "sirv public --single --dev", "start:dev": "sirv public --single --dev",
"dev:builder": "rollup -cw" "dev:builder": "rollup -cw"
}, },
"files": [
"manifest.json",
"package.json",
"dist"
],
"devDependencies": { "devDependencies": {
"@rollup/plugin-alias": "^3.1.1", "@rollup/plugin-alias": "^3.1.1",
"@rollup/plugin-commonjs": "^16.0.0", "@rollup/plugin-commonjs": "^16.0.0",
@ -29,7 +34,7 @@
"keywords": [ "keywords": [
"svelte" "svelte"
], ],
"version": "0.5.3", "version": "0.6.2",
"license": "MIT", "license": "MIT",
"gitHead": "62ebf3cedcd7e9b2494b4f8cbcfb90927609b491", "gitHead": "62ebf3cedcd7e9b2494b4f8cbcfb90927609b491",
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/string-templates", "name": "@budibase/string-templates",
"version": "0.5.3", "version": "0.6.2",
"description": "Handlebars wrapper for Budibase templating.", "description": "Handlebars wrapper for Budibase templating.",
"main": "dist/bundle.js", "main": "dist/bundle.js",
"module": "dist/bundle.js", "module": "dist/bundle.js",

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/deployment", "name": "@budibase/deployment",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "0.5.3", "version": "0.6.2",
"description": "Budibase Deployment Server", "description": "Budibase Deployment Server",
"main": "src/index.js", "main": "src/index.js",
"repository": { "repository": {