Merge pull request #11058 from Budibase/feature/budi-7076

Datasource settings tab for SQL databases and SQL schema export functionality
This commit is contained in:
Michael Drury 2023-06-28 17:56:03 +01:00 committed by GitHub
commit 72e7373073
10 changed files with 105 additions and 29 deletions

View File

@ -84,7 +84,7 @@
await roles.save(selectedRole) await roles.save(selectedRole)
notifications.success("Role saved successfully") notifications.success("Role saved successfully")
} catch (error) { } catch (error) {
notifications.error("Error saving role") notifications.error(`Error saving role - ${error.message}`)
return false return false
} }
} }
@ -96,7 +96,8 @@
changeRole() changeRole()
notifications.success("Role deleted successfully") notifications.success("Role deleted successfully")
} catch (error) { } catch (error) {
notifications.error("Error deleting role") notifications.error(`Error deleting role - ${error.message}`)
return false
} }
} }
@ -139,7 +140,7 @@
<Input <Input
label="Name" label="Name"
bind:value={selectedRole.name} bind:value={selectedRole.name}
disabled={shouldDisableRoleInput} disabled={!!selectedRoleId}
error={roleNameError} error={roleNameError}
/> />
<Select <Select

View File

@ -0,0 +1,38 @@
<script>
import { Body, Button, Layout, notifications } from "@budibase/bbui"
import Panel from "./Panel.svelte"
import { API } from "api"
import { downloadText } from "@budibase/frontend-core"
export let datasource
async function download() {
if (!datasource?._id) {
notifications.error("Datasource invalid")
}
const response = await API.fetchExternalSchema(datasource._id)
downloadText(`${datasource.name}-dump.sql`, response.schema)
}
</script>
<Panel>
<div class="main">
<Layout gap="S" noPadding>
<Body size="L" weight="700">Troubleshooting</Body>
<Body>Download your schema to share with the Budibase team</Body>
<div class="download-button">
<Button cta on:click={download}>Download</Button>
</div>
</Layout>
</div>
</Panel>
<style>
.main {
margin-top: calc(var(--spacing-l) * -1);
}
.download-button {
padding-top: var(--spacing-s);
}
</style>

View File

@ -10,6 +10,8 @@
import RestAuthenticationPanel from "./_components/panels/Authentication/index.svelte" import RestAuthenticationPanel from "./_components/panels/Authentication/index.svelte"
import RestVariablesPanel from "./_components/panels/Variables/index.svelte" import RestVariablesPanel from "./_components/panels/Variables/index.svelte"
import PromptQueryModal from "./_components/PromptQueryModal.svelte" import PromptQueryModal from "./_components/PromptQueryModal.svelte"
import SettingsPanel from "./_components/panels/Settings.svelte"
import { helpers } from "@budibase/shared-core"
let selectedPanel = null let selectedPanel = null
let panelOptions = [] let panelOptions = []
@ -38,6 +40,10 @@
panelOptions = ["Queries"] panelOptions = ["Queries"]
selectedPanel = "Queries" selectedPanel = "Queries"
} }
// always the last option for SQL
if (helpers.isSQL(datasource)) {
panelOptions.push("Settings")
}
} }
</script> </script>
@ -81,6 +87,8 @@
<RestAuthenticationPanel {datasource} /> <RestAuthenticationPanel {datasource} />
{:else if selectedPanel === "Variables"} {:else if selectedPanel === "Variables"}
<RestVariablesPanel {datasource} /> <RestVariablesPanel {datasource} />
{:else if selectedPanel === "Settings"}
<SettingsPanel {datasource} />
{:else} {:else}
<Body>Something went wrong</Body> <Body>Something went wrong</Body>
{/if} {/if}

View File

@ -83,4 +83,10 @@ export const buildDatasourceEndpoints = API => ({
body: { datasource }, body: { datasource },
}) })
}, },
fetchExternalSchema: async datasourceId => {
return await API.get({
url: `/api/datasources/${datasourceId}/schema/external`,
})
},
}) })

View File

@ -0,0 +1,13 @@
export function downloadText(filename, text) {
if (typeof text === "object") {
text = JSON.stringify(text)
}
const blob = new Blob([text], { type: "plain/text" })
const url = URL.createObjectURL(blob)
const link = document.createElement("a")
link.href = url
link.download = filename
link.click()
URL.revokeObjectURL(url)
}

View File

@ -5,3 +5,4 @@ export * as RoleUtils from "./roles"
export * as Utils from "./utils" export * as Utils from "./utils"
export { memo, derivedMemo } from "./memo" export { memo, derivedMemo } from "./memo"
export { createWebsocket } from "./websocket" export { createWebsocket } from "./websocket"
export { downloadText } from "./download"

View File

@ -427,8 +427,8 @@ export async function destroy(ctx: UserCtx) {
} }
export async function find(ctx: UserCtx) { export async function find(ctx: UserCtx) {
const database = context.getAppDB() const db = context.getAppDB()
const datasource = await database.get(ctx.params.datasourceId) const datasource = await db.get(ctx.params.datasourceId)
ctx.body = await sdk.datasources.removeSecretSingle(datasource) ctx.body = await sdk.datasources.removeSecretSingle(datasource)
} }
@ -443,7 +443,8 @@ export async function query(ctx: UserCtx) {
} }
export async function getExternalSchema(ctx: UserCtx) { export async function getExternalSchema(ctx: UserCtx) {
const { datasource } = ctx.request.body const db = context.getAppDB()
const datasource = await db.get(ctx.params.datasourceId)
const enrichedDatasource = await getAndMergeDatasource(datasource) const enrichedDatasource = await getAndMergeDatasource(datasource)
const connector = await getConnector(enrichedDatasource) const connector = await getConnector(enrichedDatasource)

View File

@ -1,9 +1,5 @@
import { roles, context, events } from "@budibase/backend-core" import { roles, context, events, db as dbCore } from "@budibase/backend-core"
import { import { getUserMetadataParams, InternalTables } from "../../db/utils"
generateRoleID,
getUserMetadataParams,
InternalTables,
} from "../../db/utils"
import { UserCtx, Database } from "@budibase/types" import { UserCtx, Database } from "@budibase/types"
const UpdateRolesOptions = { const UpdateRolesOptions = {
@ -55,6 +51,7 @@ export async function save(ctx: UserCtx) {
const db = context.getAppDB() const db = context.getAppDB()
let { _id, name, inherits, permissionId, version } = ctx.request.body let { _id, name, inherits, permissionId, version } = ctx.request.body
let isCreate = false let isCreate = false
const isNewVersion = version === roles.RoleIDVersion.NAME
if (_id && roles.isBuiltin(_id)) { if (_id && roles.isBuiltin(_id)) {
ctx.throw(400, "Cannot update builtin roles.") ctx.throw(400, "Cannot update builtin roles.")
@ -62,12 +59,20 @@ export async function save(ctx: UserCtx) {
// if not id found, then its creation // if not id found, then its creation
if (!_id) { if (!_id) {
_id = generateRoleID(name) _id = dbCore.generateRoleID(name)
isCreate = true isCreate = true
} }
// version 2 roles need updated to add back role_ // version 2 roles need updated to add back role_
else if (version === roles.RoleIDVersion.NAME) { else if (isNewVersion) {
_id = generateRoleID(name) _id = dbCore.prefixRoleID(_id)
}
let dbRole
if (!isCreate) {
dbRole = await db.get(_id)
}
if (dbRole && dbRole.name !== name && isNewVersion) {
ctx.throw(400, "Cannot change custom role name")
} }
const role = new roles.Role(_id, name, permissionId).addInheritance(inherits) const role = new roles.Role(_id, name, permissionId).addInheritance(inherits)
@ -98,7 +103,7 @@ export async function destroy(ctx: UserCtx) {
ctx.throw(400, "Cannot delete builtin role.") ctx.throw(400, "Cannot delete builtin role.")
} else { } else {
// make sure has the prefix (if it has it then it won't be added) // make sure has the prefix (if it has it then it won't be added)
roleId = generateRoleID(roleId) roleId = dbCore.generateRoleID(roleId)
} }
const role = await db.get(roleId) const role = await db.get(roleId)
// first check no users actively attached to role // first check no users actively attached to role

View File

@ -1,6 +1,7 @@
import { SourceName, SqlQuery, Datasource, Table } from "@budibase/types" import { SourceName, SqlQuery, Datasource, Table } from "@budibase/types"
import { DocumentType, SEPARATOR } from "../db/utils" import { DocumentType, SEPARATOR } from "../db/utils"
import { FieldTypes, BuildSchemaErrors, InvalidColumns } from "../constants" import { FieldTypes, BuildSchemaErrors, InvalidColumns } from "../constants"
import { helpers } from "@budibase/shared-core"
const DOUBLE_SEPARATOR = `${SEPARATOR}${SEPARATOR}` const DOUBLE_SEPARATOR = `${SEPARATOR}${SEPARATOR}`
const ROW_ID_REGEX = /^\[.*]$/g const ROW_ID_REGEX = /^\[.*]$/g
@ -178,18 +179,7 @@ export function getSqlQuery(query: SqlQuery | string): SqlQuery {
} }
} }
export function isSQL(datasource: Datasource): boolean { export const isSQL = helpers.isSQL
if (!datasource || !datasource.source) {
return false
}
const SQL = [
SourceName.POSTGRES,
SourceName.SQL_SERVER,
SourceName.MYSQL,
SourceName.ORACLE,
]
return SQL.indexOf(datasource.source) !== -1
}
export function isIsoDateString(str: string) { export function isIsoDateString(str: string) {
if (!/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/.test(str)) { if (!/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/.test(str)) {

View File

@ -1,5 +1,18 @@
import { SourceName } from "@budibase/types" import { Datasource, SourceName } from "@budibase/types"
export function isGoogleSheets(type: SourceName) { export function isGoogleSheets(type: SourceName) {
return type === SourceName.GOOGLE_SHEETS return type === SourceName.GOOGLE_SHEETS
} }
export function isSQL(datasource: Datasource): boolean {
if (!datasource || !datasource.source) {
return false
}
const SQL = [
SourceName.POSTGRES,
SourceName.SQL_SERVER,
SourceName.MYSQL,
SourceName.ORACLE,
]
return SQL.indexOf(datasource.source) !== -1
}