Merge branch 'master' of github.com:budibase/budibase into budi-8882-ms-sql-export-schema-feature-creates-and-downloads

This commit is contained in:
Sam Rose 2024-12-05 10:47:17 +00:00
commit 0e6b3c258a
No known key found for this signature in database
49 changed files with 514 additions and 231 deletions

View File

@ -2,6 +2,8 @@ import {
PermissionLevel,
PermissionType,
BuiltinPermissionID,
Permission,
BuiltinPermissions,
} from "@budibase/types"
import flatten from "lodash/flatten"
import cloneDeep from "lodash/fp/cloneDeep"
@ -12,7 +14,7 @@ export type RoleHierarchy = {
permissionId: string
}[]
export class Permission {
export class PermissionImpl implements Permission {
type: PermissionType
level: PermissionLevel
@ -61,68 +63,62 @@ export function getAllowedLevels(userPermLevel: PermissionLevel): string[] {
}
}
export const BUILTIN_PERMISSIONS: {
[key in keyof typeof BuiltinPermissionID]: {
_id: (typeof BuiltinPermissionID)[key]
name: string
permissions: Permission[]
}
} = {
export const BUILTIN_PERMISSIONS: BuiltinPermissions = {
PUBLIC: {
_id: BuiltinPermissionID.PUBLIC,
name: "Public",
permissions: [
new Permission(PermissionType.WEBHOOK, PermissionLevel.EXECUTE),
new PermissionImpl(PermissionType.WEBHOOK, PermissionLevel.EXECUTE),
],
},
READ_ONLY: {
_id: BuiltinPermissionID.READ_ONLY,
name: "Read only",
permissions: [
new Permission(PermissionType.QUERY, PermissionLevel.READ),
new Permission(PermissionType.TABLE, PermissionLevel.READ),
new Permission(PermissionType.APP, PermissionLevel.READ),
new PermissionImpl(PermissionType.QUERY, PermissionLevel.READ),
new PermissionImpl(PermissionType.TABLE, PermissionLevel.READ),
new PermissionImpl(PermissionType.APP, PermissionLevel.READ),
],
},
WRITE: {
_id: BuiltinPermissionID.WRITE,
name: "Read/Write",
permissions: [
new Permission(PermissionType.QUERY, PermissionLevel.WRITE),
new Permission(PermissionType.TABLE, PermissionLevel.WRITE),
new Permission(PermissionType.AUTOMATION, PermissionLevel.EXECUTE),
new Permission(PermissionType.LEGACY_VIEW, PermissionLevel.READ),
new Permission(PermissionType.APP, PermissionLevel.READ),
new PermissionImpl(PermissionType.QUERY, PermissionLevel.WRITE),
new PermissionImpl(PermissionType.TABLE, PermissionLevel.WRITE),
new PermissionImpl(PermissionType.AUTOMATION, PermissionLevel.EXECUTE),
new PermissionImpl(PermissionType.LEGACY_VIEW, PermissionLevel.READ),
new PermissionImpl(PermissionType.APP, PermissionLevel.READ),
],
},
POWER: {
_id: BuiltinPermissionID.POWER,
name: "Power",
permissions: [
new Permission(PermissionType.TABLE, PermissionLevel.WRITE),
new Permission(PermissionType.USER, PermissionLevel.READ),
new Permission(PermissionType.AUTOMATION, PermissionLevel.EXECUTE),
new Permission(PermissionType.WEBHOOK, PermissionLevel.READ),
new Permission(PermissionType.LEGACY_VIEW, PermissionLevel.READ),
new Permission(PermissionType.APP, PermissionLevel.READ),
new PermissionImpl(PermissionType.TABLE, PermissionLevel.WRITE),
new PermissionImpl(PermissionType.USER, PermissionLevel.READ),
new PermissionImpl(PermissionType.AUTOMATION, PermissionLevel.EXECUTE),
new PermissionImpl(PermissionType.WEBHOOK, PermissionLevel.READ),
new PermissionImpl(PermissionType.LEGACY_VIEW, PermissionLevel.READ),
new PermissionImpl(PermissionType.APP, PermissionLevel.READ),
],
},
ADMIN: {
_id: BuiltinPermissionID.ADMIN,
name: "Admin",
permissions: [
new Permission(PermissionType.TABLE, PermissionLevel.ADMIN),
new Permission(PermissionType.USER, PermissionLevel.ADMIN),
new Permission(PermissionType.AUTOMATION, PermissionLevel.ADMIN),
new Permission(PermissionType.WEBHOOK, PermissionLevel.READ),
new Permission(PermissionType.QUERY, PermissionLevel.ADMIN),
new Permission(PermissionType.LEGACY_VIEW, PermissionLevel.READ),
new Permission(PermissionType.APP, PermissionLevel.READ),
new PermissionImpl(PermissionType.TABLE, PermissionLevel.ADMIN),
new PermissionImpl(PermissionType.USER, PermissionLevel.ADMIN),
new PermissionImpl(PermissionType.AUTOMATION, PermissionLevel.ADMIN),
new PermissionImpl(PermissionType.WEBHOOK, PermissionLevel.READ),
new PermissionImpl(PermissionType.QUERY, PermissionLevel.ADMIN),
new PermissionImpl(PermissionType.LEGACY_VIEW, PermissionLevel.READ),
new PermissionImpl(PermissionType.APP, PermissionLevel.READ),
],
},
}
export function getBuiltinPermissions() {
export function getBuiltinPermissions(): BuiltinPermissions {
return cloneDeep(BUILTIN_PERMISSIONS)
}

View File

@ -133,7 +133,7 @@ describe("getBuiltinPermissionByID", () => {
_id: BuiltinPermissionID.PUBLIC,
name: "Public",
permissions: [
new permissions.Permission(
new permissions.PermissionImpl(
permissions.PermissionType.WEBHOOK,
permissions.PermissionLevel.EXECUTE
),

View File

@ -52,9 +52,16 @@
let modal
$: text = value?.label ?? "Choose an option"
$: tables = $tablesStore.list.map(table =>
format.table(table, $datasources.list)
)
$: tables = $tablesStore.list
.map(table => format.table(table, $datasources.list))
.sort((a, b) => {
// sort tables alphabetically, grouped by datasource
const dsComparison = a.datasourceName.localeCompare(b.datasourceName)
if (dsComparison !== 0) {
return dsComparison
}
return a.label.localeCompare(b.label)
})
$: viewsV1 = $viewsStore.list.map(view => ({
...view,
label: view.name,

View File

@ -1,5 +1,5 @@
<script>
import { Heading, Body, Layout, Button, Modal } from "@budibase/bbui"
import { Heading, Body, Layout, Button, Modal, Icon } from "@budibase/bbui"
import AutomationPanel from "components/automation/AutomationPanel/AutomationPanel.svelte"
import CreateAutomationModal from "components/automation/AutomationPanel/CreateAutomationModal.svelte"
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
@ -12,11 +12,13 @@
automationStore,
selectedAutomation,
} from "stores/builder"
import { createLocalStorageStore } from "@budibase/frontend-core"
import { fly } from "svelte/transition"
$: automationId = $selectedAutomation?.data?._id
$: builderStore.selectResource(automationId)
// Keep URL and state in sync for selected screen ID
const surveyDismissed = createLocalStorageStore("automation-survey", false)
const stopSyncing = syncURLToState({
urlParam: "automationId",
stateKey: "selectedAutomationId",
@ -29,9 +31,11 @@
let modal
let webhookModal
let mounted = false
onMount(() => {
$automationStore.showTestPanel = false
mounted = true
})
onDestroy(stopSyncing)
@ -79,6 +83,43 @@
</Modal>
</div>
{#if !$surveyDismissed && mounted}
<div
class="survey"
in:fly={{ x: 600, duration: 260, delay: 1000 }}
out:fly={{ x: 600, duration: 260 }}
>
<div class="survey__body">
<div class="survey__title">We value your feedback!</div>
<div class="survey__text">
<a
href="https://t.maze.co/310149185"
target="_blank"
rel="noopener noreferrer"
on:click={() => surveyDismissed.set(true)}
>
Complete our survey on Automations</a
>
and receive a $20 thank-you gift.
<a
href="https://drive.google.com/file/d/12-qk_2F9g5PdbM6wuKoz2KkIyLI-feMX/view?usp=sharing"
target="_blank"
rel="noopener noreferrer"
>
Terms apply.
</a>
</div>
</div>
<Icon
name="Close"
hoverable
color="var(--spectrum-global-color-static-gray-300)"
hoverColor="var(--spectrum-global-color-static-gray-100)"
on:click={() => surveyDismissed.set(true)}
/>
</div>
{/if}
<style>
.root {
flex: 1 1 auto;
@ -108,11 +149,9 @@
justify-content: center;
align-items: center;
}
.main {
width: 300px;
}
.setup {
padding-top: 9px;
border-left: var(--border-light);
@ -125,4 +164,39 @@
grid-column: 3;
overflow: auto;
}
/* Survey */
.survey {
position: absolute;
bottom: 32px;
right: 32px;
background: var(--spectrum-semantic-positive-color-background);
display: flex;
flex-direction: row;
padding: var(--spacing-l) var(--spacing-xl);
border-radius: 4px;
gap: var(--spacing-xl);
}
.survey * {
color: var(--spectrum-global-color-static-gray-300);
white-space: nowrap;
}
.survey a {
text-decoration: underline;
transition: color 130ms ease-out;
}
.survey a:hover {
color: var(--spectrum-global-color-static-gray-100);
cursor: pointer;
}
.survey__body {
flex: 1 1 auto;
display: flex;
flex-direction: column;
gap: 2px;
}
.survey__title {
font-weight: 600;
font-size: 15px;
}
</style>

View File

@ -1,12 +1,17 @@
import { getDefinition, getDefinitions } from "../../integrations"
import { SourceName, UserCtx } from "@budibase/types"
import {
SourceName,
UserCtx,
FetchIntegrationsResponse,
FindIntegrationResponse,
} from "@budibase/types"
const DISABLED_EXTERNAL_INTEGRATIONS = [
SourceName.AIRTABLE,
SourceName.BUDIBASE,
]
export async function fetch(ctx: UserCtx) {
export async function fetch(ctx: UserCtx<void, FetchIntegrationsResponse>) {
const definitions = await getDefinitions()
for (let disabledIntegration of DISABLED_EXTERNAL_INTEGRATIONS) {
delete definitions[disabledIntegration]
@ -14,10 +19,14 @@ export async function fetch(ctx: UserCtx) {
ctx.body = definitions
}
export async function find(ctx: UserCtx) {
export async function find(ctx: UserCtx<void, FindIntegrationResponse>) {
const sourceType = ctx.params?.type
if (DISABLED_EXTERNAL_INTEGRATIONS.indexOf(sourceType) !== -1) {
ctx.throw(400, `Invalid source type - ${sourceType} is not supported.`)
}
ctx.body = await getDefinition(ctx.params.type)
const integration = await getDefinition(ctx.params.type)
if (!integration) {
ctx.throw(404, "Integration not found")
}
ctx.body = integration
}

View File

@ -2,7 +2,7 @@ import { EMPTY_LAYOUT } from "../../constants/layouts"
import { generateLayoutID, getScreenParams } from "../../db/utils"
import { events, context } from "@budibase/backend-core"
import {
BBContext,
DeleteLayoutResponse,
Layout,
SaveLayoutRequest,
SaveLayoutResponse,
@ -32,7 +32,7 @@ export async function save(
ctx.status = 200
}
export async function destroy(ctx: BBContext) {
export async function destroy(ctx: UserCtx<void, DeleteLayoutResponse>) {
const db = context.getAppDB()
const layoutId = ctx.params.layoutId,
layoutRev = ctx.params.layoutRev

View File

@ -1,24 +1,35 @@
import { MetadataTypes } from "../../constants"
import { generateMetadataID } from "../../db/utils"
import { saveEntityMetadata, deleteEntityMetadata } from "../../utilities"
import { context } from "@budibase/backend-core"
import { BBContext } from "@budibase/types"
import {
UserCtx,
MetadataType,
GetMetadataTypesResponse,
SaveMetadataRequest,
SaveMetadataResponse,
DeleteMetadataResponse,
FindMetadataResponse,
} from "@budibase/types"
export async function getTypes(ctx: BBContext) {
export async function getTypes(ctx: UserCtx<void, GetMetadataTypesResponse>) {
ctx.body = {
types: MetadataTypes,
types: MetadataType,
}
}
export async function saveMetadata(ctx: BBContext) {
export async function saveMetadata(
ctx: UserCtx<SaveMetadataRequest, SaveMetadataResponse>
) {
const { type, entityId } = ctx.params
if (type === MetadataTypes.AUTOMATION_TEST_HISTORY) {
if (type === MetadataType.AUTOMATION_TEST_HISTORY) {
ctx.throw(400, "Cannot save automation history type")
}
ctx.body = await saveEntityMetadata(type, entityId, ctx.request.body)
}
export async function deleteMetadata(ctx: BBContext) {
export async function deleteMetadata(
ctx: UserCtx<void, DeleteMetadataResponse>
) {
const { type, entityId } = ctx.params
await deleteEntityMetadata(type, entityId)
ctx.body = {
@ -26,17 +37,9 @@ export async function deleteMetadata(ctx: BBContext) {
}
}
export async function getMetadata(ctx: BBContext) {
export async function getMetadata(ctx: UserCtx<void, FindMetadataResponse>) {
const { type, entityId } = ctx.params
const db = context.getAppDB()
const id = generateMetadataID(type, entityId)
try {
ctx.body = await db.get(id)
} catch (err: any) {
if (err.status === 404) {
ctx.body = {}
} else {
ctx.throw(err.status, err)
}
}
ctx.body = (await db.tryGet(id)) || {}
}

View File

@ -1,24 +1,33 @@
import { context } from "@budibase/backend-core"
import { migrate as migrationImpl, MIGRATIONS } from "../../migrations"
import { Ctx } from "@budibase/types"
import {
Ctx,
FetchOldMigrationResponse,
GetOldMigrationStatus,
RunOldMigrationRequest,
} from "@budibase/types"
import {
getAppMigrationVersion,
getLatestEnabledMigrationId,
} from "../../appMigrations"
export async function migrate(ctx: Ctx) {
export async function migrate(ctx: Ctx<RunOldMigrationRequest, void>) {
const options = ctx.request.body
// don't await as can take a while, just return
migrationImpl(options)
ctx.status = 200
}
export async function fetchDefinitions(ctx: Ctx) {
export async function fetchDefinitions(
ctx: Ctx<void, FetchOldMigrationResponse>
) {
ctx.body = MIGRATIONS
ctx.status = 200
}
export async function getMigrationStatus(ctx: Ctx) {
export async function getMigrationStatus(
ctx: Ctx<void, GetOldMigrationStatus>
) {
const appId = context.getAppId()
if (!appId) {

View File

@ -1,16 +1,7 @@
import { Ctx } from "@budibase/types"
import { Ctx, LogOpsRequest, ErrorOpsRequest } from "@budibase/types"
import { logging } from "@budibase/backend-core"
interface LogRequest {
message: string
data?: any
}
interface ErrorRequest {
message: string
}
export async function log(ctx: Ctx<LogRequest>) {
export async function log(ctx: Ctx<LogOpsRequest, void>) {
const body = ctx.request.body
console.trace(body.message, body.data)
console.debug(body.message, body.data)
@ -20,13 +11,13 @@ export async function log(ctx: Ctx<LogRequest>) {
ctx.status = 204
}
export async function alert(ctx: Ctx<ErrorRequest>) {
export async function alert(ctx: Ctx<ErrorOpsRequest, void>) {
const body = ctx.request.body
logging.logAlert(body.message, new Error(body.message))
ctx.status = 204
}
export async function error(ctx: Ctx<ErrorRequest>) {
export async function error(ctx: Ctx<ErrorOpsRequest, void>) {
const body = ctx.request.body
throw new Error(body.message)
}

View File

@ -9,6 +9,8 @@ import {
RemovePermissionRequest,
RemovePermissionResponse,
FetchResourcePermissionInfoResponse,
FetchBuiltinPermissionsRequest,
FetchPermissionLevelsRequest,
} from "@budibase/types"
import {
CURRENTLY_SUPPORTED_LEVELS,
@ -19,11 +21,13 @@ import { PermissionUpdateType } from "../../sdk/app/permissions"
const SUPPORTED_LEVELS = CURRENTLY_SUPPORTED_LEVELS
export function fetchBuiltin(ctx: UserCtx) {
export function fetchBuiltin(
ctx: UserCtx<void, FetchBuiltinPermissionsRequest>
) {
ctx.body = Object.values(permissions.getBuiltinPermissions())
}
export function fetchLevels(ctx: UserCtx) {
export function fetchLevels(ctx: UserCtx<void, FetchPermissionLevelsRequest>) {
// for now only provide the read/write perms externally
ctx.body = SUPPORTED_LEVELS
}

View File

@ -3,8 +3,12 @@ import {
getPluginMetadata,
extractTarball,
} from "../../../utilities/fileSystem"
import { KoaFile } from "@budibase/types"
export async function fileUpload(file: { name: string; path: string }) {
export async function fileUpload(file: KoaFile) {
if (!file.name || !file.path) {
throw new Error("File is not valid - cannot upload.")
}
if (!file.name.endsWith(".tar.gz")) {
throw new Error("Plugin must be compressed into a gzipped tarball.")
}

View File

@ -2,26 +2,37 @@ import { npmUpload, urlUpload, githubUpload } from "./uploaders"
import { plugins as pluginCore } from "@budibase/backend-core"
import {
PluginType,
FileType,
PluginSource,
Ctx,
CreatePluginRequest,
CreatePluginResponse,
UserCtx,
UploadPluginRequest,
Plugin,
UploadPluginResponse,
FetchPluginResponse,
DeletePluginResponse,
} from "@budibase/types"
import env from "../../../environment"
import { clientAppSocket } from "../../../websockets"
import sdk from "../../../sdk"
import { sdk as pro } from "@budibase/pro"
export async function upload(ctx: any) {
const plugins: FileType[] =
ctx.request.files.file.length > 1
? Array.from(ctx.request.files.file)
: [ctx.request.files.file]
export async function upload(
ctx: UserCtx<UploadPluginRequest, UploadPluginResponse>
) {
const files = ctx.request.files
const plugins =
files && Array.isArray(files.file) && files.file.length > 1
? Array.from(files.file)
: [files?.file]
try {
let docs = []
let docs: Plugin[] = []
// can do single or multiple plugins
for (let plugin of plugins) {
if (!plugin || Array.isArray(plugin)) {
continue
}
const doc = await sdk.plugins.processUploaded(plugin, PluginSource.FILE)
docs.push(doc)
}
@ -37,7 +48,7 @@ export async function upload(ctx: any) {
}
export async function create(
ctx: Ctx<CreatePluginRequest, CreatePluginResponse>
ctx: UserCtx<CreatePluginRequest, CreatePluginResponse>
) {
const { source, url, headers, githubToken } = ctx.request.body
@ -91,11 +102,11 @@ export async function create(
}
}
export async function fetch(ctx: any) {
export async function fetch(ctx: UserCtx<void, FetchPluginResponse>) {
ctx.body = await sdk.plugins.fetch()
}
export async function destroy(ctx: any) {
export async function destroy(ctx: UserCtx<void, DeletePluginResponse>) {
const { pluginId } = ctx.params
try {

View File

@ -4,26 +4,38 @@ import { save as saveDatasource } from "../datasource"
import { RestImporter } from "./import"
import { invalidateCachedVariable } from "../../../threads/utils"
import env from "../../../environment"
import { events, context, utils, constants } from "@budibase/backend-core"
import { constants, context, events, utils } from "@budibase/backend-core"
import sdk from "../../../sdk"
import { QueryEvent, QueryEventParameters } from "../../../threads/definitions"
import {
ConfigType,
Query,
UserCtx,
SessionCookie,
JsonFieldSubType,
QueryResponse,
QuerySchema,
FieldType,
CreateDatasourceRequest,
Datasource,
ExecuteQueryRequest,
ExecuteQueryResponse,
ExecuteV2QueryResponse,
ExecuteV1QueryResponse,
FetchQueriesResponse,
FieldType,
FindQueryResponse,
ImportRestQueryRequest,
ImportRestQueryResponse,
JsonFieldSubType,
PreviewQueryRequest,
PreviewQueryResponse,
Query,
QueryResponse,
QuerySchema,
SaveQueryRequest,
SaveQueryResponse,
SessionCookie,
SourceName,
UserCtx,
DeleteQueryResponse,
} from "@budibase/types"
import { ValidQueryNameRegex, utils as JsonUtils } from "@budibase/shared-core"
import { utils as JsonUtils, ValidQueryNameRegex } from "@budibase/shared-core"
import { findHBSBlocks } from "@budibase/string-templates"
import { ObjectId } from "mongodb"
import { merge } from "lodash"
const Runner = new Thread(ThreadType.QUERY, {
timeoutMs: env.QUERY_THREAD_TIMEOUT,
@ -43,11 +55,13 @@ function validateQueryInputs(parameters: QueryEventParameters) {
}
}
export async function fetch(ctx: UserCtx) {
export async function fetch(ctx: UserCtx<void, FetchQueriesResponse>) {
ctx.body = await sdk.queries.fetch()
}
const _import = async (ctx: UserCtx) => {
const _import = async (
ctx: UserCtx<ImportRestQueryRequest, ImportRestQueryResponse>
) => {
const body = ctx.request.body
const data = body.data
@ -58,9 +72,9 @@ const _import = async (ctx: UserCtx) => {
if (!body.datasourceId) {
// construct new datasource
const info: any = await importer.getInfo()
let datasource = {
let datasource: Datasource = {
type: "datasource",
source: "REST",
source: SourceName.REST,
config: {
url: info.url,
defaultHeaders: [],
@ -69,8 +83,14 @@ const _import = async (ctx: UserCtx) => {
name: info.name,
}
// save the datasource
const datasourceCtx = { ...ctx }
datasourceCtx.request.body.datasource = datasource
const datasourceCtx: UserCtx<CreateDatasourceRequest> = merge(ctx, {
request: {
body: {
datasource,
tablesFilter: [],
},
},
})
await saveDatasource(datasourceCtx)
datasourceId = datasourceCtx.body.datasource._id
} else {
@ -88,7 +108,7 @@ const _import = async (ctx: UserCtx) => {
}
export { _import as import }
export async function save(ctx: UserCtx<Query, Query>) {
export async function save(ctx: UserCtx<SaveQueryRequest, SaveQueryResponse>) {
const db = context.getAppDB()
const query: Query = ctx.request.body
@ -119,10 +139,9 @@ export async function save(ctx: UserCtx<Query, Query>) {
query._rev = response.rev
ctx.body = query
ctx.message = `Query ${query.name} saved successfully.`
}
export async function find(ctx: UserCtx) {
export async function find(ctx: UserCtx<void, FindQueryResponse>) {
const queryId = ctx.params.queryId
ctx.body = await sdk.queries.find(queryId)
}
@ -335,7 +354,7 @@ export async function preview(
async function execute(
ctx: UserCtx<
ExecuteQueryRequest,
ExecuteQueryResponse | Record<string, any>[]
ExecuteV2QueryResponse | ExecuteV1QueryResponse
>,
opts: any = { rowsOnly: false, isAutomation: false }
) {
@ -390,19 +409,21 @@ async function execute(
}
export async function executeV1(
ctx: UserCtx<ExecuteQueryRequest, Record<string, any>[]>
ctx: UserCtx<ExecuteQueryRequest, ExecuteV1QueryResponse>
) {
return execute(ctx, { rowsOnly: true, isAutomation: false })
}
export async function executeV2(
ctx: UserCtx<
ExecuteQueryRequest,
ExecuteQueryResponse | Record<string, any>[]
>,
{ isAutomation }: { isAutomation?: boolean } = {}
ctx: UserCtx<ExecuteQueryRequest, ExecuteV2QueryResponse>
) {
return execute(ctx, { rowsOnly: false, isAutomation })
return execute(ctx, { rowsOnly: false })
}
export async function executeV2AsAutomation(
ctx: UserCtx<ExecuteQueryRequest, ExecuteV2QueryResponse>
) {
return execute(ctx, { rowsOnly: false, isAutomation: true })
}
const removeDynamicVariables = async (queryId: string) => {
@ -426,14 +447,14 @@ const removeDynamicVariables = async (queryId: string) => {
}
}
export async function destroy(ctx: UserCtx) {
export async function destroy(ctx: UserCtx<void, DeleteQueryResponse>) {
const db = context.getAppDB()
const queryId = ctx.params.queryId as string
await removeDynamicVariables(queryId)
const query = await db.get<Query>(queryId)
const datasource = await sdk.datasources.get(query.datasourceId)
await db.remove(ctx.params.queryId, ctx.params.revId)
ctx.message = `Query deleted.`
ctx.body = { message: `Query deleted.` }
ctx.status = 200
await events.query.deleted(datasource, query)
}

View File

@ -9,7 +9,7 @@ import { getUserMetadataParams, InternalTables } from "../../db/utils"
import {
AccessibleRolesResponse,
Database,
DestroyRoleResponse,
DeleteRoleResponse,
FetchRolesResponse,
FindRoleResponse,
Role,
@ -199,7 +199,7 @@ export async function save(ctx: UserCtx<SaveRoleRequest, SaveRoleResponse>) {
builderSocket?.emitRoleUpdate(ctx, role)
}
export async function destroy(ctx: UserCtx<void, DestroyRoleResponse>) {
export async function destroy(ctx: UserCtx<void, DeleteRoleResponse>) {
const db = context.getAppDB()
let roleId = ctx.params.roleId as string
if (roles.isBuiltin(roleId)) {

View File

@ -1,11 +1,17 @@
import { getRoutingInfo } from "../../utilities/routing"
import { roles } from "@budibase/backend-core"
import { UserCtx } from "@budibase/types"
import {
FetchClientScreenRoutingResponse,
FetchScreenRoutingResponse,
ScreenRoutingJson,
UserCtx,
} from "@budibase/types"
const URL_SEPARATOR = "/"
class Routing {
json: any
json: ScreenRoutingJson
constructor() {
this.json = {}
}
@ -43,7 +49,7 @@ class Routing {
* @returns The routing structure, this is the full structure designed for use in the builder,
* if the client routing is required then the updateRoutingStructureForUserRole should be used.
*/
async function getRoutingStructure() {
async function getRoutingStructure(): Promise<{ routes: ScreenRoutingJson }> {
const screenRoutes = await getRoutingInfo()
const routing = new Routing()
@ -56,11 +62,13 @@ async function getRoutingStructure() {
return { routes: routing.json }
}
export async function fetch(ctx: UserCtx) {
export async function fetch(ctx: UserCtx<void, FetchScreenRoutingResponse>) {
ctx.body = await getRoutingStructure()
}
export async function clientFetch(ctx: UserCtx) {
export async function clientFetch(
ctx: UserCtx<void, FetchClientScreenRoutingResponse>
) {
const routing = await getRoutingStructure()
let roleId = ctx.user?.role?._id
const roleIds = roleId ? await roles.getUserRoleIdHierarchy(roleId) : []

View File

@ -11,11 +11,14 @@ import {
DeleteRow,
DeleteRowRequest,
DeleteRows,
DownloadAttachmentResponse,
EventType,
ExportRowsRequest,
ExportRowsResponse,
FetchEnrichedRowResponse,
FetchRowsResponse,
FieldType,
GetRowResponse,
FindRowResponse,
isRelationshipField,
PatchRowRequest,
PatchRowResponse,
@ -23,12 +26,15 @@ import {
Row,
RowAttachment,
RowSearchParams,
SaveRowRequest,
SaveRowResponse,
SearchFilters,
SearchRowRequest,
SearchRowResponse,
Table,
UserCtx,
ValidateResponse,
ValidateRowRequest,
ValidateRowResponse,
} from "@budibase/types"
import * as utils from "./utils"
import { gridSocket } from "../../../websockets"
@ -83,7 +89,7 @@ export async function patch(
}
}
export const save = async (ctx: UserCtx<Row, Row>) => {
export const save = async (ctx: UserCtx<SaveRowRequest, SaveRowResponse>) => {
const { tableId, viewId } = utils.getSourceId(ctx)
const sourceId = viewId || tableId
@ -131,12 +137,12 @@ export async function fetchLegacyView(ctx: any) {
})
}
export async function fetch(ctx: any) {
export async function fetch(ctx: UserCtx<void, FetchRowsResponse>) {
const { tableId } = utils.getSourceId(ctx)
ctx.body = await sdk.rows.fetch(tableId)
}
export async function find(ctx: UserCtx<void, GetRowResponse>) {
export async function find(ctx: UserCtx<void, FindRowResponse>) {
const { tableId, viewId } = utils.getSourceId(ctx)
const sourceId = viewId || tableId
const rowId = ctx.params.rowId
@ -314,7 +320,9 @@ function replaceTableNamesInFilters(
})
}
export async function validate(ctx: Ctx<Row, ValidateResponse>) {
export async function validate(
ctx: Ctx<ValidateRowRequest, ValidateRowResponse>
) {
const source = await utils.getSource(ctx)
const table = await utils.getTableFromSource(source)
// external tables are hard to validate currently
@ -328,7 +336,9 @@ export async function validate(ctx: Ctx<Row, ValidateResponse>) {
}
}
export async function fetchEnrichedRow(ctx: UserCtx<void, Row>) {
export async function fetchEnrichedRow(
ctx: UserCtx<void, FetchEnrichedRowResponse>
) {
const { tableId } = utils.getSourceId(ctx)
ctx.body = await pickApi(tableId).fetchEnrichedRow(ctx)
}
@ -366,7 +376,9 @@ export const exportRows = async (
ctx.body = apiFileReturn(content)
}
export async function downloadAttachment(ctx: UserCtx) {
export async function downloadAttachment(
ctx: UserCtx<void, DownloadAttachmentResponse>
) {
const { columnName } = ctx.params
const { tableId } = utils.getSourceId(ctx)

View File

@ -56,7 +56,7 @@ router
"/api/v2/queries/:queryId",
paramResource("queryId"),
authorized(PermissionType.QUERY, PermissionLevel.WRITE),
queryController.executeV2 as any
queryController.executeV2
)
export default router

View File

@ -1,11 +1,11 @@
const { testAutomation } = require("./utilities/TestFunctions")
const setup = require("./utilities")
const { MetadataTypes } = require("../../../constants")
import { testAutomation } from "./utilities/TestFunctions"
import * as setup from "./utilities"
import { MetadataType, Automation } from "@budibase/types"
describe("/metadata", () => {
let request = setup.getRequest()
let config = setup.getConfig()
let automation
let automation: Automation
afterAll(setup.afterAll)
@ -15,8 +15,8 @@ describe("/metadata", () => {
})
async function createMetadata(
data,
type = MetadataTypes.AUTOMATION_TEST_INPUT
data: Record<string, string>,
type = MetadataType.AUTOMATION_TEST_INPUT
) {
const res = await request
.post(`/api/metadata/${type}/${automation._id}`)
@ -27,7 +27,7 @@ describe("/metadata", () => {
expect(res.body._rev).toBeDefined()
}
async function getMetadata(type) {
async function getMetadata(type: MetadataType) {
const res = await request
.get(`/api/metadata/${type}/${automation._id}`)
.set(config.defaultHeaders())
@ -39,14 +39,14 @@ describe("/metadata", () => {
describe("save", () => {
it("should be able to save some metadata", async () => {
await createMetadata({ test: "a" })
const testInput = await getMetadata(MetadataTypes.AUTOMATION_TEST_INPUT)
const testInput = await getMetadata(MetadataType.AUTOMATION_TEST_INPUT)
expect(testInput.test).toBe("a")
})
it("should save history metadata on automation run", async () => {
// this should have created some history
await testAutomation(config, automation)
const metadata = await getMetadata(MetadataTypes.AUTOMATION_TEST_HISTORY)
await testAutomation(config, automation, {})
const metadata = await getMetadata(MetadataType.AUTOMATION_TEST_HISTORY)
expect(metadata).toBeDefined()
expect(metadata.history.length).toBe(1)
expect(typeof metadata.history[0].occurredAt).toBe("number")
@ -57,13 +57,13 @@ describe("/metadata", () => {
it("should be able to delete some test inputs", async () => {
const res = await request
.delete(
`/api/metadata/${MetadataTypes.AUTOMATION_TEST_INPUT}/${automation._id}`
`/api/metadata/${MetadataType.AUTOMATION_TEST_INPUT}/${automation._id}`
)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
expect(res.body.message).toBeDefined()
const metadata = await getMetadata(MetadataTypes.AUTOMATION_TEST_INPUT)
const metadata = await getMetadata(MetadataType.AUTOMATION_TEST_INPUT)
expect(metadata.test).toBeUndefined()
})
})

View File

@ -94,7 +94,7 @@ export async function run({
})
try {
await queryController.executeV2(ctx, { isAutomation: true })
await queryController.executeV2AsAutomation(ctx)
const { data, ...rest } = ctx.body
return {

View File

@ -2,7 +2,6 @@ import { Thread, ThreadType } from "../threads"
import { definitions } from "./triggerInfo"
import { automationQueue } from "./bullboard"
import { updateEntityMetadata } from "../utilities"
import { MetadataTypes } from "../constants"
import { context, db as dbCore, utils } from "@budibase/backend-core"
import { getAutomationMetadataParams } from "../db/utils"
import { cloneDeep } from "lodash/fp"
@ -14,6 +13,7 @@ import {
AutomationStepDefinition,
AutomationTriggerDefinition,
AutomationTriggerStepId,
MetadataType,
} from "@budibase/types"
import { automationsEnabled } from "../features"
import { helpers, REBOOT_CRON } from "@budibase/shared-core"
@ -107,7 +107,7 @@ export async function updateTestHistory(
history: any
) {
return updateEntityMetadata(
MetadataTypes.AUTOMATION_TEST_HISTORY,
MetadataType.AUTOMATION_TEST_HISTORY,
automation._id,
(metadata: any) => {
if (metadata && Array.isArray(metadata.history)) {

View File

@ -124,11 +124,6 @@ export enum BaseQueryVerbs {
DELETE = "delete",
}
export enum MetadataTypes {
AUTOMATION_TEST_INPUT = "automationTestInput",
AUTOMATION_TEST_HISTORY = "automationTestHistory",
}
export enum InvalidColumns {
ID = "_id",
REV = "_rev",

View File

@ -3,10 +3,10 @@ import {
RequiredKeys,
Webhook,
WebhookActionType,
MetadataType,
} from "@budibase/types"
import { generateAutomationID, getAutomationParams } from "../../../db/utils"
import { deleteEntityMetadata } from "../../../utilities"
import { MetadataTypes } from "../../../constants"
import {
context,
events,
@ -161,7 +161,7 @@ export async function update(automation: Automation) {
if (oldAutoTrigger && oldAutoTrigger.id !== newAutoTrigger?.id) {
await events.automation.triggerUpdated(automation)
await deleteEntityMetadata(
MetadataTypes.AUTOMATION_TEST_INPUT,
MetadataType.AUTOMATION_TEST_INPUT,
automation._id!
)
}
@ -183,11 +183,8 @@ export async function remove(automationId: string, rev: string) {
})
// delete metadata first
await deleteEntityMetadata(MetadataTypes.AUTOMATION_TEST_INPUT, automationId)
await deleteEntityMetadata(
MetadataTypes.AUTOMATION_TEST_HISTORY,
automationId
)
await deleteEntityMetadata(MetadataType.AUTOMATION_TEST_INPUT, automationId)
await deleteEntityMetadata(MetadataType.AUTOMATION_TEST_HISTORY, automationId)
const result = await db.remove(automationId, rev)

View File

@ -1,4 +1,4 @@
import { FileType, Plugin, PluginSource, PluginType } from "@budibase/types"
import { KoaFile, Plugin, PluginSource, PluginType } from "@budibase/types"
import {
db as dbCore,
objectStore,
@ -10,7 +10,7 @@ import env from "../../environment"
import { clientAppSocket } from "../../websockets"
import { sdk as pro } from "@budibase/pro"
export async function fetch(type?: PluginType) {
export async function fetch(type?: PluginType): Promise<Plugin[]> {
const db = tenancy.getGlobalDB()
const response = await db.allDocs(
dbCore.getPluginParams(null, {
@ -26,7 +26,7 @@ export async function fetch(type?: PluginType) {
}
}
export async function processUploaded(plugin: FileType, source?: PluginSource) {
export async function processUploaded(plugin: KoaFile, source?: PluginSource) {
const { metadata, directory } = await fileUpload(plugin)
pluginCore.validate(metadata?.schema)

View File

@ -1,7 +1,7 @@
import {
Query,
ExecuteQueryRequest,
ExecuteQueryResponse,
ExecuteV2QueryResponse,
PreviewQueryRequest,
PreviewQueryResponse,
} from "@budibase/types"
@ -17,8 +17,8 @@ export class QueryAPI extends TestAPI {
queryId: string,
body?: ExecuteQueryRequest,
expectations?: Expectations
): Promise<ExecuteQueryResponse> => {
return await this._post<ExecuteQueryResponse>(
): Promise<ExecuteV2QueryResponse> => {
return await this._post<ExecuteV2QueryResponse>(
`/api/v2/queries/${queryId}`,
{
body,

View File

@ -2,7 +2,7 @@ import {
PatchRowRequest,
SaveRowRequest,
Row,
ValidateResponse,
ValidateRowResponse,
ExportRowsRequest,
BulkImportRequest,
BulkImportResponse,
@ -51,8 +51,8 @@ export class RowAPI extends TestAPI {
sourceId: string,
row: SaveRowRequest,
expectations?: Expectations
): Promise<ValidateResponse> => {
return await this._post<ValidateResponse>(
): Promise<ValidateRowResponse> => {
return await this._post<ValidateRowResponse>(
`/api/${sourceId}/rows/validate`,
{
body: row,

View File

@ -88,7 +88,7 @@ export async function saveEntityMetadata(
type: string,
entityId: string,
metadata: Document
) {
): Promise<Document> {
return updateEntityMetadata(type, entityId, () => {
return metadata
})

View File

@ -1,24 +1,19 @@
import { createRoutingView } from "../../db/views/staticViews"
import { ViewName, getQueryIndex, UNICODE_MAX } from "../../db/utils"
import { context } from "@budibase/backend-core"
import { ScreenRouting, Document } from "@budibase/types"
import { ScreenRoutesViewOutput } from "@budibase/types"
interface ScreenRoutesView extends Document {
id: string
routing: ScreenRouting
}
export async function getRoutingInfo(): Promise<ScreenRoutesView[]> {
export async function getRoutingInfo(): Promise<ScreenRoutesViewOutput[]> {
const db = context.getAppDB()
try {
const allRouting = await db.query<ScreenRoutesView>(
const allRouting = await db.query<ScreenRoutesViewOutput>(
getQueryIndex(ViewName.ROUTING),
{
startkey: "",
endkey: UNICODE_MAX,
}
)
return allRouting.rows.map(row => row.value as ScreenRoutesView)
return allRouting.rows.map(row => row.value)
} catch (err: any) {
// check if the view doesn't exist, it should for all new instances
/* istanbul ignore next */

View File

@ -1,6 +1,5 @@
export * from "./backup"
export * from "./datasource"
export * from "./row"
export * from "./view"
export * from "./rows"
export * from "./table"
@ -10,3 +9,7 @@ export * from "./user"
export * from "./rowAction"
export * from "./automation"
export * from "./component"
export * from "./integration"
export * from "./metadata"
export * from "./query"
export * from "./screen"

View File

@ -0,0 +1,8 @@
import { Integration, SourceName } from "../../../sdk"
export type FetchIntegrationsResponse = Record<
SourceName,
Integration | undefined
>
export type FindIntegrationResponse = Integration

View File

@ -0,0 +1,14 @@
import { MetadataType, Document } from "../../../documents"
export interface GetMetadataTypesResponse {
types: typeof MetadataType
}
export interface SaveMetadataRequest extends Document {}
export interface SaveMetadataResponse extends Document {}
export interface DeleteMetadataResponse {
message: string
}
export interface FindMetadataResponse extends Document {}

View File

@ -1,4 +1,8 @@
import { PermissionLevel } from "../../../sdk"
import { BuiltinPermission, PermissionLevel } from "../../../sdk"
export type FetchBuiltinPermissionsRequest = BuiltinPermission[]
export type FetchPermissionLevelsRequest = string[]
export interface FetchResourcePermissionInfoResponse {
[key: string]: Record<string, string>

View File

@ -0,0 +1,47 @@
import {
Datasource,
Query,
QueryPreview,
QuerySchema,
} from "../../../documents"
export type FetchQueriesResponse = Query[]
export interface SaveQueryRequest extends Query {}
export interface SaveQueryResponse extends Query {}
export interface ImportRestQueryRequest {
datasourceId: string
data: string
datasource: Datasource
}
export interface ImportRestQueryResponse {
errorQueries: Query[]
queries: Query[]
datasourceId: string
}
export interface FindQueryResponse extends Query {}
export interface PreviewQueryRequest extends QueryPreview {}
export interface PreviewQueryResponse {
rows: any[]
nestedSchemaFields: { [key: string]: { [key: string]: string | QuerySchema } }
schema: { [key: string]: string | QuerySchema }
info: any
extra: any
}
export interface ExecuteQueryRequest {
parameters?: Record<string, string>
pagination?: any
}
export type ExecuteV1QueryResponse = Record<string, any>[]
export interface ExecuteV2QueryResponse {
data: Record<string, any>[]
}
export interface DeleteQueryResponse {
message: string
}

View File

@ -1,18 +0,0 @@
import { Row } from "../../../documents/app/row"
export interface GetRowResponse extends Row {}
export interface DeleteRows {
rows: (Row | string)[]
}
export interface DeleteRow {
_id: string
}
export type DeleteRowRequest = DeleteRows | DeleteRow
export interface ValidateResponse {
valid: boolean
errors: Record<string, any>
}

View File

@ -1,11 +1,17 @@
import { SearchFilters } from "../../../../sdk"
import { Row } from "../../../../documents"
import { SortOrder } from "../../../../api/web/pagination"
import { SortOrder } from "../../pagination"
import { ReadStream } from "fs"
import stream from "node:stream"
export * from "./search"
export interface FetchEnrichedRowResponse extends Row {}
export type FetchRowsResponse = Row[]
export interface SaveRowRequest extends Row {}
export interface SaveRowResponse extends Row {}
export interface PatchRowRequest extends Row {
_id: string
@ -26,3 +32,23 @@ export interface ExportRowsRequest {
}
export type ExportRowsResponse = ReadStream
export type DownloadAttachmentResponse = stream.PassThrough | stream.Readable
export interface FindRowResponse extends Row {}
export interface DeleteRows {
rows: (Row | string)[]
}
export interface DeleteRow {
_id: string
}
export type DeleteRowRequest = DeleteRows | DeleteRow
export interface ValidateRowRequest extends Row {}
export interface ValidateRowResponse {
valid: boolean
errors: Record<string, any>
}

View File

@ -0,0 +1,8 @@
import { ScreenRoutingJson } from "../../../documents"
export interface FetchScreenRoutingResponse {
routes: ScreenRoutingJson
}
export interface FetchClientScreenRoutingResponse
extends FetchScreenRoutingResponse {}

View File

@ -4,3 +4,4 @@ export * from "./events"
export * from "./configs"
export * from "./scim"
export * from "./license"
export * from "./oldMigration"

View File

@ -0,0 +1,9 @@
import { Migration, MigrationOptions } from "../../../sdk"
export interface RunOldMigrationRequest extends MigrationOptions {}
export type FetchOldMigrationResponse = Migration[]
export interface GetOldMigrationStatus {
migrated: boolean
}

View File

@ -13,7 +13,6 @@ export * from "./searchFilter"
export * from "./cookies"
export * from "./automation"
export * from "./layout"
export * from "./query"
export * from "./role"
export * from "./plugins"
export * from "./apikeys"

View File

@ -3,3 +3,7 @@ import { Layout } from "../../documents"
export interface SaveLayoutRequest extends Layout {}
export interface SaveLayoutResponse extends Layout {}
export interface DeleteLayoutResponse {
message: string
}

View File

@ -1,4 +1,10 @@
import { PluginSource } from "../../documents"
import { PluginSource, Plugin } from "../../documents"
export interface UploadPluginRequest {}
export interface UploadPluginResponse {
message: string
plugins: Plugin[]
}
export interface CreatePluginRequest {
source: PluginSource
@ -10,3 +16,9 @@ export interface CreatePluginRequest {
export interface CreatePluginResponse {
plugin: any
}
export type FetchPluginResponse = Plugin[]
export interface DeletePluginResponse {
message: string
}

View File

@ -1,20 +0,0 @@
import { QueryPreview, QuerySchema } from "../../documents"
export interface PreviewQueryRequest extends QueryPreview {}
export interface PreviewQueryResponse {
rows: any[]
nestedSchemaFields: { [key: string]: { [key: string]: string | QuerySchema } }
schema: { [key: string]: string | QuerySchema }
info: any
extra: any
}
export interface ExecuteQueryRequest {
parameters?: Record<string, string>
pagination?: any
}
export interface ExecuteQueryResponse {
data: Record<string, any>[]
}

View File

@ -18,7 +18,7 @@ export interface FindRoleResponse extends Role {}
export type FetchRolesResponse = Role[]
export interface DestroyRoleResponse {
export interface DeleteRoleResponse {
message: string
}

View File

@ -1,2 +1,3 @@
export * from "./environment"
export * from "./status"
export * from "./ops"

View File

@ -0,0 +1,8 @@
export interface LogOpsRequest {
message: string
data?: any
}
export interface ErrorOpsRequest {
message: string
}

View File

@ -19,3 +19,4 @@ export * from "./snippet"
export * from "./rowAction"
export * from "./theme"
export * from "./deployment"
export * from "./metadata"

View File

@ -0,0 +1,4 @@
export enum MetadataType {
AUTOMATION_TEST_INPUT = "automationTestInput",
AUTOMATION_TEST_HISTORY = "automationTestHistory",
}

View File

@ -24,3 +24,20 @@ export interface Screen extends Document {
name?: string
pluginAdded?: boolean
}
export interface ScreenRoutesViewOutput extends Document {
id: string
routing: ScreenRouting
}
export type ScreenRoutingJson = Record<
string,
{
subpaths: Record<
string,
{
screens: Record<string, string>
}
>
}
>

View File

@ -12,9 +12,9 @@ export enum PluginSource {
URL = "URL",
FILE = "File Upload",
}
export interface FileType {
path: string
name: string
export interface KoaFile {
path: string | null
name: string | null
}
export interface Plugin extends Document {

View File

@ -36,3 +36,22 @@ export enum PermissionSource {
INHERITED = "INHERITED",
BASE = "BASE",
}
export interface Permission {
type: PermissionType
level: PermissionLevel
}
export interface BuiltinPermission {
_id: BuiltinPermissionID
name: string
permissions: Permission[]
}
export type BuiltinPermissions = {
[key in keyof typeof BuiltinPermissionID]: {
_id: (typeof BuiltinPermissionID)[key]
name: string
permissions: Permission[]
}
}