Adding query save, fixing routing to handle existing rest queries, adding in full saving of enabled headers functionality, fixing various issues discovered while testing.
This commit is contained in:
parent
b31cd5b6f7
commit
c0512fa242
|
@ -0,0 +1,37 @@
|
|||
<script>
|
||||
import { Label, Select } from "@budibase/bbui"
|
||||
import { permissions, roles } from "stores/backend"
|
||||
|
||||
export let query
|
||||
export let saveId
|
||||
export let label
|
||||
|
||||
$: updateRole(roleId, saveId)
|
||||
|
||||
let roleId
|
||||
|
||||
async function updateRole(role, id) {
|
||||
roleId = role
|
||||
const queryId = query?._id || id
|
||||
if (roleId && queryId) {
|
||||
for (let level of ["read", "write"]) {
|
||||
await permissions.save({
|
||||
level,
|
||||
role,
|
||||
resource: queryId,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if label}
|
||||
<Label>{label}</Label>
|
||||
{/if}
|
||||
<Select
|
||||
value={roleId}
|
||||
on:change={e => updateRole(e.detail)}
|
||||
options={$roles}
|
||||
getOptionLabel={x => x.name}
|
||||
getOptionValue={x => x._id}
|
||||
/>
|
|
@ -14,32 +14,60 @@
|
|||
|
||||
export let defaults
|
||||
export let object = defaults || {}
|
||||
export let activity = {}
|
||||
export let readOnly
|
||||
export let noAddButton
|
||||
export let name
|
||||
export let headings = false
|
||||
export let activity = false
|
||||
export let options
|
||||
export let toggle
|
||||
|
||||
let fields = Object.entries(object).map(([name, value]) => ({ name, value }))
|
||||
let fieldActivity = []
|
||||
|
||||
$: object = fields.reduce(
|
||||
(acc, next) => ({ ...acc, [next.name]: next.value }),
|
||||
{}
|
||||
)
|
||||
$: fieldActivity = buildFieldActivity(activity)
|
||||
|
||||
function buildFieldActivity(obj) {
|
||||
if (!obj || typeof obj !== "object") {
|
||||
return []
|
||||
}
|
||||
const array = Array(fields.length)
|
||||
for (let [key, value] of Object.entries(obj)) {
|
||||
const field = fields.find(el => el.name === key)
|
||||
const idx = fields.indexOf(field)
|
||||
if (idx !== -1) {
|
||||
array[idx] = value
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
export function addEntry() {
|
||||
fields = [...fields, { name: "", value: "" }]
|
||||
fieldActivity = [...fieldActivity, true]
|
||||
changed()
|
||||
}
|
||||
|
||||
function deleteEntry(idx) {
|
||||
fields.splice(idx, 1)
|
||||
fieldActivity.splice(idx, 1)
|
||||
changed()
|
||||
}
|
||||
|
||||
function changed() {
|
||||
fields = fields
|
||||
const newActivity = {}
|
||||
for (let idx = 0; idx < fields.length; idx++) {
|
||||
const fieldName = fields[idx].name
|
||||
if (fieldName) {
|
||||
newActivity[fieldName] = fieldActivity[idx]
|
||||
}
|
||||
}
|
||||
activity = newActivity
|
||||
dispatch("change", fields)
|
||||
}
|
||||
</script>
|
||||
|
@ -67,8 +95,12 @@
|
|||
on:change={changed}
|
||||
/>
|
||||
{/if}
|
||||
{#if activity}
|
||||
<Toggle />
|
||||
{#if toggle}
|
||||
<Toggle
|
||||
bind:value={fieldActivity[idx]}
|
||||
on:change={changed}
|
||||
default={true}
|
||||
/>
|
||||
{/if}
|
||||
{#if !readOnly}
|
||||
<Icon hoverable name="Close" on:click={() => deleteEntry(idx)} />
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
datasources,
|
||||
integrations,
|
||||
queries,
|
||||
roles,
|
||||
permissions,
|
||||
} from "stores/backend"
|
||||
import { capitalise } from "../../helpers"
|
||||
|
@ -32,6 +31,7 @@
|
|||
import { onMount } from "svelte"
|
||||
import KeyValueBuilder from "./KeyValueBuilder.svelte"
|
||||
import { fieldsToSchema, schemaToFields } from "helpers/data/utils"
|
||||
import AccessLevelSelect from "./AccessLevelSelect.svelte"
|
||||
|
||||
export let query
|
||||
|
||||
|
@ -39,6 +39,7 @@
|
|||
let parameters
|
||||
let data = []
|
||||
let roleId
|
||||
let saveId
|
||||
const transformerDocs =
|
||||
"https://docs.budibase.com/building-apps/data/transformers"
|
||||
|
||||
|
@ -62,19 +63,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
async function updateRole(role, id = null) {
|
||||
roleId = role
|
||||
if (query?._id || id) {
|
||||
for (let level of ["read", "write"]) {
|
||||
await permissions.save({
|
||||
level,
|
||||
role,
|
||||
resource: query?._id || id,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function populateExtraQuery(extraQueryFields) {
|
||||
query.fields.extra = extraQueryFields
|
||||
}
|
||||
|
@ -99,7 +87,7 @@
|
|||
async function saveQuery() {
|
||||
try {
|
||||
const { _id } = await queries.save(query.datasourceId, query)
|
||||
await updateRole(roleId, _id)
|
||||
saveId = _id
|
||||
notifications.success(`Query saved successfully.`)
|
||||
$goto(`../${_id}`)
|
||||
} catch (err) {
|
||||
|
@ -142,14 +130,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="config-field">
|
||||
<Label>Access level</Label>
|
||||
<Select
|
||||
value={roleId}
|
||||
on:change={e => updateRole(e.detail)}
|
||||
options={$roles}
|
||||
getOptionLabel={x => x.name}
|
||||
getOptionValue={x => x._id}
|
||||
/>
|
||||
<AccessLevelSelect {saveId} {query} label="Access Level" />
|
||||
</div>
|
||||
{#if integrationInfo?.extra && query.queryVerb}
|
||||
<ExtraQueryConfig
|
||||
|
|
|
@ -17,3 +17,35 @@ export function fieldsToSchema(fields) {
|
|||
}
|
||||
return response
|
||||
}
|
||||
|
||||
export function breakQueryString(qs) {
|
||||
if (!qs) {
|
||||
return {}
|
||||
}
|
||||
if (qs.includes("?")) {
|
||||
qs = qs.split("?")[1]
|
||||
}
|
||||
const params = qs.split("&")
|
||||
let paramObj = {}
|
||||
for (let param of params) {
|
||||
const [key, value] = param.split("=")
|
||||
paramObj[key] = value
|
||||
}
|
||||
return paramObj
|
||||
}
|
||||
|
||||
export function buildQueryString(obj) {
|
||||
let str = ""
|
||||
if (obj) {
|
||||
for (let [key, value] of Object.entries(obj)) {
|
||||
if (!key || key === "") {
|
||||
continue
|
||||
}
|
||||
if (str !== "") {
|
||||
str += "&"
|
||||
}
|
||||
str += `${key}=${value || ""}`
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { store, automationStore } from "builderStore"
|
||||
import { roles } from "stores/backend"
|
||||
import { roles, flags } from "stores/backend"
|
||||
import { Icon, ActionGroup, Tabs, Tab, notifications } from "@budibase/bbui"
|
||||
import DeployModal from "components/deploy/DeployModal.svelte"
|
||||
import RevertModal from "components/deploy/RevertModal.svelte"
|
||||
|
@ -49,6 +49,7 @@
|
|||
}
|
||||
await automationStore.actions.fetch()
|
||||
await roles.fetch()
|
||||
await flags.fetch()
|
||||
return pkg
|
||||
} else {
|
||||
throw new Error(pkg)
|
||||
|
|
|
@ -4,17 +4,19 @@
|
|||
import { IntegrationTypes } from "constants/backend"
|
||||
import { goto } from "@roxi/routify"
|
||||
|
||||
let datasourceId
|
||||
if ($params.query) {
|
||||
const query = $queries.list.find(q => q._id === $params.query)
|
||||
if (query) {
|
||||
queries.select(query)
|
||||
datasourceId = query.datasourceId
|
||||
}
|
||||
}
|
||||
const datasource = $datasources.list.find(
|
||||
ds => ds._id === $datasources.selected
|
||||
ds => ds._id === $datasources.selected || ds._id === datasourceId
|
||||
)
|
||||
if (datasource?.source === IntegrationTypes.REST) {
|
||||
$goto("../rest")
|
||||
$goto(`../rest/${$params.query}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<script>
|
||||
import { params } from "@roxi/routify"
|
||||
import { queries } from "stores/backend"
|
||||
|
||||
if ($params.query) {
|
||||
const query = $queries.list.find(q => q._id === $params.query)
|
||||
if (query) {
|
||||
queries.select(query)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<slot />
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { params } from "@roxi/routify"
|
||||
import { datasources, integrations, queries } from "stores/backend"
|
||||
import { datasources, integrations, queries, flags } from "stores/backend"
|
||||
import {
|
||||
Layout,
|
||||
Input,
|
||||
|
@ -22,10 +22,15 @@
|
|||
import CodeMirrorEditor, {
|
||||
EditorModes,
|
||||
} from "components/common/CodeMirrorEditor.svelte"
|
||||
import RestBodyInput from "../_components/RestBodyInput.svelte"
|
||||
import RestBodyInput from "../../_components/RestBodyInput.svelte"
|
||||
import { capitalise } from "helpers"
|
||||
import { onMount } from "svelte"
|
||||
import { fieldsToSchema, schemaToFields } from "helpers/data/utils"
|
||||
import {
|
||||
fieldsToSchema,
|
||||
schemaToFields,
|
||||
breakQueryString,
|
||||
buildQueryString,
|
||||
} from "helpers/data/utils"
|
||||
import {
|
||||
RestBodyTypes as bodyTypes,
|
||||
SchemaTypeOptions,
|
||||
|
@ -35,7 +40,8 @@
|
|||
let query, datasource
|
||||
let breakQs = {}
|
||||
let url = ""
|
||||
let response, schema
|
||||
let saveId
|
||||
let response, schema, isGet
|
||||
let datasourceType, integrationInfo, queryConfig, responseSuccess
|
||||
|
||||
$: datasource = $datasources.list.find(ds => ds._id === query?.datasourceId)
|
||||
|
@ -44,6 +50,7 @@
|
|||
$: queryConfig = integrationInfo?.query
|
||||
$: url = buildUrl(url, breakQs)
|
||||
$: checkQueryName(url)
|
||||
$: isGet = query?.queryVerb === "read"
|
||||
$: responseSuccess =
|
||||
response?.info?.code >= 200 && response?.info?.code <= 206
|
||||
|
||||
|
@ -58,35 +65,6 @@
|
|||
)
|
||||
}
|
||||
|
||||
function breakQueryString(qs) {
|
||||
if (!qs) {
|
||||
return {}
|
||||
}
|
||||
if (qs.includes("?")) {
|
||||
qs = qs.split("?")[1]
|
||||
}
|
||||
const params = qs.split("&")
|
||||
let paramObj = {}
|
||||
for (let param of params) {
|
||||
const [key, value] = param.split("=")
|
||||
paramObj[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
function buildQueryString(obj) {
|
||||
let str = ""
|
||||
for (let [key, value] of Object.entries(obj)) {
|
||||
if (!key || key === "") {
|
||||
continue
|
||||
}
|
||||
if (str !== "") {
|
||||
str += "&"
|
||||
}
|
||||
str += `${key}=${value || ""}`
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
function checkQueryName(inputUrl = null) {
|
||||
if (query && (!query.name || query.flags.urlName)) {
|
||||
query.flags.urlName = true
|
||||
|
@ -113,11 +91,19 @@
|
|||
const queryString = buildQueryString(breakQs)
|
||||
newQuery.fields.path = url.split("?")[0]
|
||||
newQuery.fields.queryString = queryString
|
||||
newQuery.schema = fieldsToSchema(schema)
|
||||
return newQuery
|
||||
}
|
||||
|
||||
function saveQuery() {
|
||||
query.schema = fieldsToSchema(schema)
|
||||
async function saveQuery() {
|
||||
const toSave = buildQuery()
|
||||
try {
|
||||
const { _id } = await queries.save(toSave.datasourceId, toSave)
|
||||
saveId = _id
|
||||
notifications.success(`Request saved successfully.`)
|
||||
} catch (err) {
|
||||
notifications.error(`Error creating query. ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function runQuery() {
|
||||
|
@ -138,7 +124,7 @@
|
|||
query = getSelectedQuery()
|
||||
const qs = query?.fields.queryString
|
||||
breakQs = breakQueryString(qs)
|
||||
url = buildUrl(query.fields.path, qs)
|
||||
url = buildUrl(query.fields.path, breakQs)
|
||||
schema = schemaToFields(query.schema)
|
||||
if (query && !query.transformer) {
|
||||
query.transformer = "return data"
|
||||
|
@ -146,7 +132,6 @@
|
|||
if (query && !query.flags) {
|
||||
query.flags = {
|
||||
urlName: false,
|
||||
bannerCleared: false,
|
||||
}
|
||||
}
|
||||
if (query && !query.fields.bodyType) {
|
||||
|
@ -187,15 +172,16 @@
|
|||
<Tab title="Headers">
|
||||
<KeyValueBuilder
|
||||
bind:object={query.fields.headers}
|
||||
bind:activity={query.fields.enabledHeaders}
|
||||
toggle
|
||||
name="header"
|
||||
headings
|
||||
activity
|
||||
/>
|
||||
</Tab>
|
||||
<Tab title="Body">
|
||||
<RadioGroup
|
||||
bind:value={query.fields.bodyType}
|
||||
options={bodyTypes}
|
||||
options={isGet ? [bodyTypes[0]] : bodyTypes}
|
||||
direction="horizontal"
|
||||
getOptionLabel={option => option.name}
|
||||
getOptionValue={option => option.value}
|
||||
|
@ -204,11 +190,12 @@
|
|||
</Tab>
|
||||
<Tab title="Transformer">
|
||||
<Layout noPadding>
|
||||
{#if !query.flags.bannerCleared}
|
||||
{#if !$flags.queryTransformerBanner}
|
||||
<Banner
|
||||
extraButtonText="Learn more"
|
||||
extraButtonAction={learnMoreBanner}
|
||||
on:change={() => (query.flags.bannerCleared = true)}
|
||||
on:change={() =>
|
||||
flags.updateFlag("queryTransformerBanner", true)}
|
||||
>
|
||||
Add a JavaScript function to transform the query result.
|
||||
</Banner>
|
|
@ -1,4 +0,0 @@
|
|||
<script>
|
||||
</script>
|
||||
|
||||
<slot />
|
|
@ -0,0 +1,37 @@
|
|||
import { writable } from "svelte/store"
|
||||
import api from "builderStore/api"
|
||||
|
||||
export function createFlagsStore() {
|
||||
const { subscribe, set } = writable({})
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
fetch: async () => {
|
||||
const { doc, response } = await getFlags()
|
||||
set(doc)
|
||||
return response
|
||||
},
|
||||
updateFlag: async (flag, value) => {
|
||||
const response = await api.post("/api/users/flags", {
|
||||
flag,
|
||||
value,
|
||||
})
|
||||
if (response.status === 200) {
|
||||
const { doc } = await getFlags()
|
||||
set(doc)
|
||||
}
|
||||
return response
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async function getFlags() {
|
||||
const response = await api.get("/api/users/flags")
|
||||
let doc = {}
|
||||
if (response.status === 200) {
|
||||
doc = await response.json()
|
||||
}
|
||||
return { doc, response }
|
||||
}
|
||||
|
||||
export const flags = createFlagsStore()
|
|
@ -7,3 +7,4 @@ export { roles } from "./roles"
|
|||
export { datasources } from "./datasources"
|
||||
export { integrations } from "./integrations"
|
||||
export { queries } from "./queries"
|
||||
export { flags } from "./flags"
|
||||
|
|
|
@ -2,6 +2,7 @@ const CouchDB = require("../../db")
|
|||
const {
|
||||
generateUserMetadataID,
|
||||
getUserMetadataParams,
|
||||
generateUserFlagID,
|
||||
} = require("../../db/utils")
|
||||
const { InternalTables } = require("../../db/utils")
|
||||
const { getGlobalUsers, getRawGlobalUser } = require("../../utilities/global")
|
||||
|
@ -195,3 +196,35 @@ exports.destroyMetadata = async function (ctx) {
|
|||
exports.findMetadata = async function (ctx) {
|
||||
ctx.body = await getFullUser(ctx, ctx.params.id)
|
||||
}
|
||||
|
||||
exports.setFlag = async function (ctx) {
|
||||
const userId = ctx.user._id
|
||||
const { flag, value } = ctx.request.body
|
||||
if (!flag) {
|
||||
ctx.throw(400, "Must supply a 'flag' field in request body.")
|
||||
}
|
||||
const flagDocId = generateUserFlagID(userId)
|
||||
const db = new CouchDB(ctx.appId)
|
||||
let doc
|
||||
try {
|
||||
doc = await db.get(flagDocId)
|
||||
} catch (err) {
|
||||
doc = { _id: flagDocId }
|
||||
}
|
||||
doc[flag] = value || true
|
||||
await db.put(doc)
|
||||
ctx.body = { message: "Flag set successfully" }
|
||||
}
|
||||
|
||||
exports.getFlags = async function (ctx) {
|
||||
const userId = ctx.user._id
|
||||
const docId = generateUserFlagID(userId)
|
||||
const db = new CouchDB(ctx.appId)
|
||||
let doc
|
||||
try {
|
||||
doc = await db.get(docId)
|
||||
} catch (err) {
|
||||
doc = { _id: docId }
|
||||
}
|
||||
ctx.body = doc
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ function generateQueryValidation() {
|
|||
extra: Joi.object().optional(),
|
||||
schema: Joi.object({}).required().unknown(true),
|
||||
transformer: Joi.string().optional(),
|
||||
flags: Joi.object().optional(),
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
@ -39,5 +39,15 @@ router
|
|||
authorized(PermissionTypes.USER, PermissionLevels.WRITE),
|
||||
controller.syncUser
|
||||
)
|
||||
.post(
|
||||
"/api/users/flags",
|
||||
authorized(PermissionTypes.USER, PermissionLevels.WRITE),
|
||||
controller.setFlag
|
||||
)
|
||||
.get(
|
||||
"/api/users/flags",
|
||||
authorized(PermissionTypes.USER, PermissionLevels.READ),
|
||||
controller.getFlags
|
||||
)
|
||||
|
||||
module.exports = router
|
||||
|
|
|
@ -40,6 +40,7 @@ const DocumentTypes = {
|
|||
DEPLOYMENTS: "deployments",
|
||||
METADATA: "metadata",
|
||||
MEM_VIEW: "view",
|
||||
USER_FLAG: "flag",
|
||||
}
|
||||
|
||||
const ViewNames = {
|
||||
|
@ -339,6 +340,14 @@ exports.getQueryParams = (datasourceId = null, otherProps = {}) => {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new flag document ID.
|
||||
* @returns {string} The ID of the flag document that was generated.
|
||||
*/
|
||||
exports.generateUserFlagID = userId => {
|
||||
return `${DocumentTypes.USER_FLAG}${SEPARATOR}${userId}`
|
||||
}
|
||||
|
||||
exports.generateMetadataID = (type, entityId) => {
|
||||
return `${DocumentTypes.METADATA}${SEPARATOR}${type}${SEPARATOR}${entityId}`
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue