query and datasource unit tests
This commit is contained in:
parent
83910f0aab
commit
882823671a
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
|
import api from "builderStore/api"
|
||||||
import { Input, TextArea, Spacer } from "@budibase/bbui"
|
import { Input, TextArea, Spacer } from "@budibase/bbui"
|
||||||
import ICONS from "../icons"
|
import ICONS from "../icons"
|
||||||
|
|
||||||
|
|
|
@ -35,8 +35,6 @@
|
||||||
notifier.success(`Datasource ${name} created successfully.`)
|
notifier.success(`Datasource ${name} created successfully.`)
|
||||||
analytics.captureEvent("Datasource Created", { name })
|
analytics.captureEvent("Datasource Created", { name })
|
||||||
|
|
||||||
console.log(response)
|
|
||||||
|
|
||||||
// Navigate to new datasource
|
// Navigate to new datasource
|
||||||
$goto(`./datasource/${response._id}`)
|
$goto(`./datasource/${response._id}`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
{#each $backendUiStore.tables as table, idx}
|
{#each $backendUiStore.tables as table, idx}
|
||||||
<NavItem
|
<NavItem
|
||||||
border={idx > 0}
|
border={idx > 0}
|
||||||
icon={table.integration?.type ? 'ri-database-2-line' : `ri-${table._id === TableNames.USERS ? 'user' : 'table'}-line`}
|
icon={`ri-${table._id === TableNames.USERS ? 'user' : 'table'}-line`}
|
||||||
text={table.name}
|
text={table.name}
|
||||||
selected={selectedView === `all_${table._id}`}
|
selected={selectedView === `all_${table._id}`}
|
||||||
on:click={() => selectTable(table)}>
|
on:click={() => selectTable(table)}>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
import { backendUiStore, store } from "builderStore"
|
import { backendUiStore, store } from "builderStore"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import { Input, Label, ModalContent, Button, Spacer } from "@budibase/bbui"
|
import { Input, Label, ModalContent, Button, Spacer, Toggle } from "@budibase/bbui"
|
||||||
import TableDataImport from "../TableDataImport.svelte"
|
import TableDataImport from "../TableDataImport.svelte"
|
||||||
import analytics from "analytics"
|
import analytics from "analytics"
|
||||||
import screenTemplates from "builderStore/store/screenTemplates"
|
import screenTemplates from "builderStore/store/screenTemplates"
|
||||||
|
@ -19,7 +19,6 @@
|
||||||
let modal
|
let modal
|
||||||
let name
|
let name
|
||||||
let dataImport
|
let dataImport
|
||||||
let integration
|
|
||||||
let error = ""
|
let error = ""
|
||||||
let createAutoscreens = true
|
let createAutoscreens = true
|
||||||
|
|
||||||
|
|
|
@ -1,59 +1,162 @@
|
||||||
<script>
|
<script>
|
||||||
import cm from "./codemirror"
|
import CodeMirror from "./codemirror"
|
||||||
import { onMount, createEventDispatcher } from "svelte"
|
import { onMount, createEventDispatcher } from "svelte"
|
||||||
|
import { themeStore } from "builderStore"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let value
|
const THEMES = {
|
||||||
export let mode = "sql"
|
DARK: "tomorrow-night-eighties",
|
||||||
|
LIGHT: "default",
|
||||||
|
}
|
||||||
|
|
||||||
let editor
|
export let value = ""
|
||||||
let codemirror
|
export let readOnly = false
|
||||||
|
export let lineNumbers = true
|
||||||
|
export let tab = true
|
||||||
|
export let mode
|
||||||
|
|
||||||
$: {
|
let width
|
||||||
if (codemirror) {
|
let height
|
||||||
const { left, top } = codemirror.getScrollInfo()
|
|
||||||
codemirror.setValue(value)
|
// We have to expose set and update methods, rather
|
||||||
codemirror.scrollTo(left, top)
|
// than making this state-driven through props,
|
||||||
|
// because it's difficult to update an editor
|
||||||
|
// without resetting scroll otherwise
|
||||||
|
export async function set(new_value, new_mode) {
|
||||||
|
if (new_mode !== mode) {
|
||||||
|
await createEditor((mode = new_mode))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
value = new_value
|
||||||
|
updating_externally = true
|
||||||
|
if (editor) editor.setValue(value)
|
||||||
|
updating_externally = false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function update(new_value) {
|
||||||
|
value = new_value
|
||||||
|
|
||||||
|
if (editor) {
|
||||||
|
const { left, top } = editor.getScrollInfo()
|
||||||
|
editor.setValue((value = new_value))
|
||||||
|
editor.scrollTo(left, top)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resize() {
|
||||||
|
editor.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function focus() {
|
||||||
|
editor.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
const modes = {
|
||||||
|
js: {
|
||||||
|
name: "javascript",
|
||||||
|
json: false,
|
||||||
|
},
|
||||||
|
json: {
|
||||||
|
name: "javascript",
|
||||||
|
json: true,
|
||||||
|
},
|
||||||
|
sql: {
|
||||||
|
name: "sql",
|
||||||
|
},
|
||||||
|
svelte: {
|
||||||
|
name: "handlebars",
|
||||||
|
base: "text/html",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const refs = {}
|
||||||
|
let editor
|
||||||
|
let updating_externally = false
|
||||||
|
let marker
|
||||||
|
let error_line
|
||||||
|
let destroyed = false
|
||||||
|
|
||||||
|
$: if (editor && width && height) {
|
||||||
|
editor.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (codemirror) codemirror.toTextArea()
|
createEditor(mode).then(() => {
|
||||||
|
if (editor) editor.setValue(value || "")
|
||||||
codemirror = cm.fromTextArea(editor, {
|
|
||||||
lineNumbers: true,
|
|
||||||
mode,
|
|
||||||
lineWrapping: true,
|
|
||||||
indentUnit: 2,
|
|
||||||
tabSize: 2,
|
|
||||||
autoCloseBrackets: true,
|
|
||||||
autoCloseTags: true,
|
|
||||||
extraKeys: {
|
|
||||||
"Ctrl-/": "toggleComment",
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
codemirror.on("change", instance => {
|
|
||||||
const code = instance.getValue()
|
|
||||||
dispatch("change", code)
|
|
||||||
})
|
|
||||||
|
|
||||||
codemirror.setValue(value || "")
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (codemirror) codemirror.toTextArea()
|
destroyed = true
|
||||||
|
if (editor) editor.toTextArea()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let first = true
|
||||||
|
|
||||||
|
async function createEditor(mode) {
|
||||||
|
if (destroyed || !CodeMirror) return
|
||||||
|
|
||||||
|
if (editor) editor.toTextArea()
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
lineNumbers,
|
||||||
|
lineWrapping: true,
|
||||||
|
indentWithTabs: true,
|
||||||
|
indentUnit: 2,
|
||||||
|
tabSize: 2,
|
||||||
|
value: "",
|
||||||
|
mode: modes[mode] || {
|
||||||
|
name: mode,
|
||||||
|
},
|
||||||
|
readOnly,
|
||||||
|
autoCloseBrackets: true,
|
||||||
|
autoCloseTags: true,
|
||||||
|
theme: $themeStore.darkMode ? THEMES.DARK : THEMES.LIGHT,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tab)
|
||||||
|
opts.extraKeys = {
|
||||||
|
Tab: tab,
|
||||||
|
"Shift-Tab": tab,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creating a text editor is a lot of work, so we yield
|
||||||
|
// the main thread for a moment. This helps reduce jank
|
||||||
|
if (first) await sleep(50)
|
||||||
|
|
||||||
|
if (destroyed) return
|
||||||
|
|
||||||
|
editor = CodeMirror.fromTextArea(refs.editor, opts)
|
||||||
|
|
||||||
|
editor.on("change", instance => {
|
||||||
|
if (!updating_externally) {
|
||||||
|
const value = instance.getValue()
|
||||||
|
dispatch("change", { value })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (first) await sleep(50)
|
||||||
|
editor.refresh()
|
||||||
|
|
||||||
|
first = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise(fulfil => setTimeout(fulfil, ms))
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<textarea bind:value bind:this={editor} />
|
<textarea tabindex="0" bind:this={refs.editor} readonly {value} />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
textarea {
|
textarea {
|
||||||
background: var(--background);
|
visibility: hidden;
|
||||||
border-radius: var(--border-radius-m);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.CodeMirror) {
|
:global(.CodeMirror) {
|
||||||
|
height: auto !important;
|
||||||
border-radius: var(--border-radius-m);
|
border-radius: var(--border-radius-m);
|
||||||
|
font-family: var(--font-sans) !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
Label,
|
Label,
|
||||||
Input,
|
Input,
|
||||||
Heading,
|
Heading,
|
||||||
Spacer,
|
|
||||||
Select
|
Select
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
|
|
||||||
|
@ -13,53 +12,62 @@
|
||||||
export let schema
|
export let schema
|
||||||
export let editable
|
export let editable
|
||||||
|
|
||||||
let customSchema = {}
|
|
||||||
let draftField = {}
|
let draftField = {}
|
||||||
|
|
||||||
|
$: fieldKeys = Object.keys(fields)
|
||||||
|
$: schemaKeys = Object.keys(schema.fields)
|
||||||
|
|
||||||
|
$: console.log({ fields, schema })
|
||||||
|
|
||||||
function addField() {
|
function addField() {
|
||||||
// Add the new field to custom fields for the query
|
// Add the new field to custom fields for the query
|
||||||
customSchema[draftField.name] = {
|
schema.fields[draftField.name] = {
|
||||||
type: draftField.type
|
type: draftField.type
|
||||||
}
|
}
|
||||||
// reset the draft field
|
// reset the draft field
|
||||||
draftField = {}
|
draftField = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeField(field) {
|
||||||
|
delete fields[field]
|
||||||
|
fields = fields
|
||||||
|
|
||||||
|
delete schema.fields[field]
|
||||||
|
schema = schema
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form on:submit|preventDefault>
|
<form on:submit|preventDefault>
|
||||||
{#each Object.keys(schema.fields) as field}
|
{#each schemaKeys as field}
|
||||||
<Label extraSmall grey>{field}</Label>
|
<Label extraSmall grey>{field}</Label>
|
||||||
<Input
|
<div class="field">
|
||||||
disabled={!editable}
|
<Input
|
||||||
type={schema.fields[field]?.type}
|
disabled={!editable}
|
||||||
required={schema.fields[field]?.required}
|
type={schema.fields[field]?.type}
|
||||||
bind:value={fields[field]} />
|
required={schema.fields[field]?.required}
|
||||||
<Spacer medium />
|
bind:value={fields[field]} />
|
||||||
|
{#if !schema.fields[field]?.required}
|
||||||
|
<i class="ri-close-circle-line" on:click={() => removeField(field)} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
{#if schema.customisable && editable}
|
|
||||||
<Spacer large />
|
|
||||||
<Label>Add Custom Field</Label>
|
|
||||||
{#each Object.keys(customSchema) as field}
|
|
||||||
<Label extraSmall grey>{field}</Label>
|
|
||||||
<Input
|
|
||||||
thin
|
|
||||||
type={customSchema[field]?.type}
|
|
||||||
bind:value={fields[field]}
|
|
||||||
/>
|
|
||||||
<Spacer medium />
|
|
||||||
{/each}
|
|
||||||
<div class="new-field">
|
|
||||||
<Label extraSmall grey>Name</Label>
|
|
||||||
<Label extraSmall grey>Type</Label>
|
|
||||||
<Input thin bind:value={draftField.name} />
|
|
||||||
<Select thin secondary bind:value={draftField.name}>
|
|
||||||
<option value={"text"}>String</option>
|
|
||||||
<option value={"number"}>Number</option>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<Button small thin secondary on:click={addField}>Add Field</Button>
|
|
||||||
{/if}
|
|
||||||
</form>
|
</form>
|
||||||
|
{#if schema.customisable && editable}
|
||||||
|
<div>
|
||||||
|
<Label>Add Custom Field</Label>
|
||||||
|
<div class="new-field">
|
||||||
|
<Label extraSmall grey>Name</Label>
|
||||||
|
<Label extraSmall grey>Type</Label>
|
||||||
|
<Input thin bind:value={draftField.name} />
|
||||||
|
<Select thin secondary bind:value={draftField.type}>
|
||||||
|
<option value={"text"}>String</option>
|
||||||
|
<option value={"number"}>Number</option>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<Button small thin secondary on:click={addField}>Add Field</Button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.new-field {
|
.new-field {
|
||||||
|
@ -69,4 +77,23 @@
|
||||||
margin-top: var(--spacing-m);
|
margin-top: var(--spacing-m);
|
||||||
margin-bottom: var(--spacing-m);
|
margin-bottom: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
margin-bottom: var(--spacing-m);
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 2%;
|
||||||
|
grid-gap: var(--spacing-m);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
i:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
|
@ -1,162 +0,0 @@
|
||||||
<script>
|
|
||||||
import CodeMirror from "./codemirror"
|
|
||||||
import { onMount, createEventDispatcher } from "svelte"
|
|
||||||
import { themeStore } from "builderStore"
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
|
|
||||||
const THEMES = {
|
|
||||||
DARK: "tomorrow-night-eighties",
|
|
||||||
LIGHT: "default",
|
|
||||||
}
|
|
||||||
|
|
||||||
export let value = ""
|
|
||||||
export let readOnly = false
|
|
||||||
export let lineNumbers = true
|
|
||||||
export let tab = true
|
|
||||||
export let mode
|
|
||||||
|
|
||||||
let width
|
|
||||||
let height
|
|
||||||
|
|
||||||
// We have to expose set and update methods, rather
|
|
||||||
// than making this state-driven through props,
|
|
||||||
// because it's difficult to update an editor
|
|
||||||
// without resetting scroll otherwise
|
|
||||||
export async function set(new_value, new_mode) {
|
|
||||||
if (new_mode !== mode) {
|
|
||||||
await createEditor((mode = new_mode))
|
|
||||||
}
|
|
||||||
|
|
||||||
value = new_value
|
|
||||||
updating_externally = true
|
|
||||||
if (editor) editor.setValue(value)
|
|
||||||
updating_externally = false
|
|
||||||
}
|
|
||||||
|
|
||||||
export function update(new_value) {
|
|
||||||
value = new_value
|
|
||||||
|
|
||||||
if (editor) {
|
|
||||||
const { left, top } = editor.getScrollInfo()
|
|
||||||
editor.setValue((value = new_value))
|
|
||||||
editor.scrollTo(left, top)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resize() {
|
|
||||||
editor.refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function focus() {
|
|
||||||
editor.focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
const modes = {
|
|
||||||
js: {
|
|
||||||
name: "javascript",
|
|
||||||
json: false,
|
|
||||||
},
|
|
||||||
json: {
|
|
||||||
name: "javascript",
|
|
||||||
json: true,
|
|
||||||
},
|
|
||||||
sql: {
|
|
||||||
name: "sql",
|
|
||||||
},
|
|
||||||
svelte: {
|
|
||||||
name: "handlebars",
|
|
||||||
base: "text/html",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const refs = {}
|
|
||||||
let editor
|
|
||||||
let updating_externally = false
|
|
||||||
let marker
|
|
||||||
let error_line
|
|
||||||
let destroyed = false
|
|
||||||
|
|
||||||
$: if (editor && width && height) {
|
|
||||||
editor.refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
createEditor(mode).then(() => {
|
|
||||||
if (editor) editor.setValue(value || "")
|
|
||||||
})
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
destroyed = true
|
|
||||||
if (editor) editor.toTextArea()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
let first = true
|
|
||||||
|
|
||||||
async function createEditor(mode) {
|
|
||||||
if (destroyed || !CodeMirror) return
|
|
||||||
|
|
||||||
if (editor) editor.toTextArea()
|
|
||||||
|
|
||||||
const opts = {
|
|
||||||
lineNumbers,
|
|
||||||
lineWrapping: true,
|
|
||||||
indentWithTabs: true,
|
|
||||||
indentUnit: 2,
|
|
||||||
tabSize: 2,
|
|
||||||
value: "",
|
|
||||||
mode: modes[mode] || {
|
|
||||||
name: mode,
|
|
||||||
},
|
|
||||||
readOnly,
|
|
||||||
autoCloseBrackets: true,
|
|
||||||
autoCloseTags: true,
|
|
||||||
theme: $themeStore.darkMode ? THEMES.DARK : THEMES.LIGHT,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tab)
|
|
||||||
opts.extraKeys = {
|
|
||||||
Tab: tab,
|
|
||||||
"Shift-Tab": tab,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creating a text editor is a lot of work, so we yield
|
|
||||||
// the main thread for a moment. This helps reduce jank
|
|
||||||
if (first) await sleep(50)
|
|
||||||
|
|
||||||
if (destroyed) return
|
|
||||||
|
|
||||||
editor = CodeMirror.fromTextArea(refs.editor, opts)
|
|
||||||
|
|
||||||
editor.on("change", instance => {
|
|
||||||
if (!updating_externally) {
|
|
||||||
const value = instance.getValue()
|
|
||||||
dispatch("change", { value })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (first) await sleep(50)
|
|
||||||
editor.refresh()
|
|
||||||
|
|
||||||
first = false
|
|
||||||
}
|
|
||||||
|
|
||||||
function sleep(ms) {
|
|
||||||
return new Promise(fulfil => setTimeout(fulfil, ms))
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<textarea tabindex="0" bind:this={refs.editor} readonly {value} />
|
|
||||||
|
|
||||||
<style>
|
|
||||||
textarea {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.CodeMirror) {
|
|
||||||
height: auto !important;
|
|
||||||
border-radius: var(--border-radius-m);
|
|
||||||
font-family: var(--font-sans) !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { TextArea, Label, Input, Heading, Spacer } from "@budibase/bbui"
|
import { TextArea, Label, Input, Heading, Spacer } from "@budibase/bbui"
|
||||||
import Editor from "./SvelteEditor.svelte"
|
import Editor from "./QueryEditor.svelte"
|
||||||
import ParameterBuilder from "./QueryParameterBuilder.svelte"
|
import ParameterBuilder from "./QueryParameterBuilder.svelte"
|
||||||
import FieldsBuilder from "./QueryFieldsBuilder.svelte"
|
import FieldsBuilder from "./QueryFieldsBuilder.svelte"
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
||||||
SQL: "sql",
|
SQL: "sql",
|
||||||
JSON: "json",
|
JSON: "json",
|
||||||
FIELDS: "fields",
|
FIELDS: "fields",
|
||||||
|
LIST: "list",
|
||||||
}
|
}
|
||||||
|
|
||||||
export let query
|
export let query
|
||||||
|
@ -26,7 +27,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Heading extraSmall black>Query</Heading>
|
<Heading extraSmall black>Query</Heading>
|
||||||
<Spacer large />
|
<Spacer medium />
|
||||||
|
|
||||||
{#if schema}
|
{#if schema}
|
||||||
{#if schema.type === QueryTypes.SQL}
|
{#if schema.type === QueryTypes.SQL}
|
||||||
|
@ -46,5 +47,5 @@
|
||||||
value={query.fields.json} />
|
value={query.fields.json} />
|
||||||
{:else if schema.type === QueryTypes.FIELDS}
|
{:else if schema.type === QueryTypes.FIELDS}
|
||||||
<FieldsBuilder bind:fields={query.fields} {schema} {editable} />
|
<FieldsBuilder bind:fields={query.fields} {schema} {editable} />
|
||||||
{/if}
|
{:else if schema.type === QueryTypes.LIST}LIST STUFF{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -25,20 +25,6 @@
|
||||||
parameters: [],
|
parameters: [],
|
||||||
fields: {},
|
fields: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
// $: {
|
|
||||||
// if ($params.query !== "new") {
|
|
||||||
// query = $backendUiStore.queries.find(query => query._id === $params.query)
|
|
||||||
// } else {
|
|
||||||
// // New query
|
|
||||||
// query = {
|
|
||||||
// datasourceId: $params.selectedDatasource,
|
|
||||||
// name: "New Query",
|
|
||||||
// parameters: [],
|
|
||||||
// fields: {},
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
|
|
@ -32,7 +32,7 @@ exports.save = async function(ctx) {
|
||||||
datasource._rev = response.rev
|
datasource._rev = response.rev
|
||||||
|
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.message = "Datasource created successfully."
|
ctx.message = "Datasource saved successfully."
|
||||||
ctx.body = datasource
|
ctx.body = datasource
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(err.status, err)
|
ctx.throw(err.status, err)
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
const {
|
||||||
|
supertest,
|
||||||
|
createApplication,
|
||||||
|
defaultHeaders,
|
||||||
|
builderEndpointShouldBlockNormalUsers,
|
||||||
|
getDocument,
|
||||||
|
insertDocument
|
||||||
|
} = require("./couchTestUtils")
|
||||||
|
let { generateDatasourceID, generateQueryID } = require("../../../db/utils")
|
||||||
|
|
||||||
|
const DATASOURCE_ID = generateDatasourceID()
|
||||||
|
const TEST_DATASOURCE = {
|
||||||
|
_id: DATASOURCE_ID,
|
||||||
|
type: "datasource",
|
||||||
|
name: "Test",
|
||||||
|
source: "POSTGRES",
|
||||||
|
config: {},
|
||||||
|
type: "datasource",
|
||||||
|
}
|
||||||
|
|
||||||
|
const TEST_QUERY = {
|
||||||
|
_id: generateQueryID(DATASOURCE_ID),
|
||||||
|
datasourceId: DATASOURCE_ID,
|
||||||
|
name:"New Query",
|
||||||
|
parameters:[],
|
||||||
|
fields:{},
|
||||||
|
schema:{},
|
||||||
|
queryVerb:"read",
|
||||||
|
queryType:"Table",
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("/datasources", () => {
|
||||||
|
let request
|
||||||
|
let server
|
||||||
|
let app
|
||||||
|
let appId
|
||||||
|
let datasource
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
({ request, server } = await supertest())
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
server.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
app = await createApplication(request)
|
||||||
|
appId = app.instance._id
|
||||||
|
});
|
||||||
|
|
||||||
|
async function createDatasource() {
|
||||||
|
return await insertDocument(appId, TEST_DATASOURCE)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createQuery() {
|
||||||
|
return await insertDocument(appId, TEST_QUERY)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("create", () => {
|
||||||
|
it("should create a new datasource", async () => {
|
||||||
|
const res = await request
|
||||||
|
.post(`/api/datasources`)
|
||||||
|
.send(TEST_DATASOURCE)
|
||||||
|
.set(defaultHeaders(appId))
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
expect(res.res.statusMessage).toEqual("Datasource saved successfully.");
|
||||||
|
expect(res.body.name).toEqual("Test");
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("fetch", () => {
|
||||||
|
let datasource
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
datasource = await createDatasource()
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
delete datasource._rev
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns all the datasources from the server", async () => {
|
||||||
|
const res = await request
|
||||||
|
.get(`/api/datasources`)
|
||||||
|
.set(defaultHeaders(appId))
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
const datasources = res.body;
|
||||||
|
expect(datasources).toEqual([
|
||||||
|
{
|
||||||
|
"_rev": datasources[0]._rev,
|
||||||
|
...TEST_DATASOURCE
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should apply authorization to endpoint", async () => {
|
||||||
|
await builderEndpointShouldBlockNormalUsers({
|
||||||
|
request,
|
||||||
|
method: "GET",
|
||||||
|
url: `/api/datasources`,
|
||||||
|
appId: appId,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("destroy", () => {
|
||||||
|
let datasource;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
datasource = await createDatasource()
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
delete datasource._rev
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deletes queries for the datasource after deletion and returns a success message", async () => {
|
||||||
|
await createQuery(datasource.id)
|
||||||
|
|
||||||
|
await request
|
||||||
|
.delete(`/api/datasources/${datasource.id}/${datasource.rev}`)
|
||||||
|
.set(defaultHeaders(appId))
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
const res = await request
|
||||||
|
.get(`/api/datasources`)
|
||||||
|
.set(defaultHeaders(appId))
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
expect(res.body).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should apply authorization to endpoint", async () => {
|
||||||
|
await builderEndpointShouldBlockNormalUsers({
|
||||||
|
request,
|
||||||
|
method: "DELETE",
|
||||||
|
url: `/api/datasources/${datasource._id}/${datasource._rev}`,
|
||||||
|
appId: appId,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,153 @@
|
||||||
|
const {
|
||||||
|
supertest,
|
||||||
|
createApplication,
|
||||||
|
defaultHeaders,
|
||||||
|
builderEndpointShouldBlockNormalUsers,
|
||||||
|
getDocument,
|
||||||
|
insertDocument
|
||||||
|
} = require("./couchTestUtils")
|
||||||
|
let { generateDatasourceID, generateQueryID } = require("../../../db/utils")
|
||||||
|
|
||||||
|
const DATASOURCE_ID = generateDatasourceID()
|
||||||
|
const TEST_DATASOURCE = {
|
||||||
|
_id: DATASOURCE_ID,
|
||||||
|
type: "datasource",
|
||||||
|
name: "Test",
|
||||||
|
source: "POSTGRES",
|
||||||
|
config: {},
|
||||||
|
type: "datasource",
|
||||||
|
}
|
||||||
|
|
||||||
|
const TEST_QUERY = {
|
||||||
|
_id: generateQueryID(DATASOURCE_ID),
|
||||||
|
datasourceId: DATASOURCE_ID,
|
||||||
|
name:"New Query",
|
||||||
|
parameters:[],
|
||||||
|
fields:{},
|
||||||
|
schema:{},
|
||||||
|
queryVerb:"read",
|
||||||
|
queryType:"Table",
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("/queries", () => {
|
||||||
|
let request
|
||||||
|
let server
|
||||||
|
let app
|
||||||
|
let appId
|
||||||
|
let datasource
|
||||||
|
let query
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
({ request, server } = await supertest())
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
server.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
app = await createApplication(request)
|
||||||
|
appId = app.instance._id
|
||||||
|
});
|
||||||
|
|
||||||
|
async function createDatasource() {
|
||||||
|
return await insertDocument(appId, TEST_DATASOURCE)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createQuery() {
|
||||||
|
return await insertDocument(appId, TEST_QUERY)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("create", () => {
|
||||||
|
it("should create a new query", async () => {
|
||||||
|
const res = await request
|
||||||
|
.post(`/api/queries`)
|
||||||
|
.send(TEST_QUERY)
|
||||||
|
.set(defaultHeaders(appId))
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
expect(res.res.statusMessage).toEqual(`Query ${TEST_QUERY.name} saved successfully.`);
|
||||||
|
expect(res.body).toEqual({
|
||||||
|
_rev: res.body._rev,
|
||||||
|
...TEST_QUERY,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("fetch", () => {
|
||||||
|
let datasource
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
datasource = await createDatasource()
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
delete datasource._rev
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns all the queries from the server", async () => {
|
||||||
|
const query = await createQuery()
|
||||||
|
const res = await request
|
||||||
|
.get(`/api/queries`)
|
||||||
|
.set(defaultHeaders(appId))
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
const queries = res.body;
|
||||||
|
expect(queries).toEqual([
|
||||||
|
{
|
||||||
|
"_rev": query.rev,
|
||||||
|
...TEST_QUERY
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should apply authorization to endpoint", async () => {
|
||||||
|
await builderEndpointShouldBlockNormalUsers({
|
||||||
|
request,
|
||||||
|
method: "GET",
|
||||||
|
url: `/api/datasources`,
|
||||||
|
appId: appId,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("destroy", () => {
|
||||||
|
let datasource;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
datasource = await createDatasource()
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
delete datasource._rev
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deletes a query and returns a success message", async () => {
|
||||||
|
const query = await createQuery()
|
||||||
|
|
||||||
|
await request
|
||||||
|
.delete(`/api/queries/${query.id}/${query.rev}`)
|
||||||
|
.set(defaultHeaders(appId))
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
const res = await request
|
||||||
|
.get(`/api/queries`)
|
||||||
|
.set(defaultHeaders(appId))
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
expect(res.body).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should apply authorization to endpoint", async () => {
|
||||||
|
await builderEndpointShouldBlockNormalUsers({
|
||||||
|
request,
|
||||||
|
method: "DELETE",
|
||||||
|
url: `/api/datasources/${datasource._id}/${datasource._rev}`,
|
||||||
|
appId: appId,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue