Merge branch 'plugins-dev-experience' of github.com:Budibase/budibase into plugins-dev-experience

This commit is contained in:
mike12345567 2022-08-11 16:36:07 +01:00
commit c9c468a424
13 changed files with 158 additions and 45 deletions

View File

@ -178,6 +178,14 @@ export const streamUpload = async (
const objectStore = ObjectStore(bucketName) const objectStore = ObjectStore(bucketName)
await makeSureBucketExists(objectStore, bucketName) await makeSureBucketExists(objectStore, bucketName)
// Set content type for certain known extensions
if (filename?.endsWith(".js")) {
extra = {
...extra,
ContentType: "application/javascript",
}
}
const params = { const params = {
Bucket: sanitizeBucket(bucketName), Bucket: sanitizeBucket(bucketName),
Key: sanitizeKey(filename), Key: sanitizeKey(filename),

View File

@ -90,36 +90,14 @@ export const getFrontendStore = () => {
// Fetch component definitions. // Fetch component definitions.
// Allow errors to propagate. // Allow errors to propagate.
let components = await API.fetchComponentLibDefinitions(application.appId) const components = await API.fetchComponentLibDefinitions(
application.appId
// Extend definitions with custom components )
components["test"] = {
component: "test",
name: "Super cool component",
icon: "Text",
description: "A custom component",
showSettingsBar: false,
hasChildren: true,
settings: [
{
type: "text",
key: "text",
label: "Text",
},
],
context: {
type: "static",
values: [
{
label: "Text prop",
key: "text",
},
],
},
}
// Filter out custom component keys so we can flag them // Filter out custom component keys so we can flag them
let customComponents = ["test"] const customComponents = Object.keys(components).filter(name =>
name.startsWith("plugin/")
)
// Reset store state // Reset store state
store.update(state => ({ store.update(state => ({
@ -146,6 +124,7 @@ export const getFrontendStore = () => {
version: application.version, version: application.version,
revertableVersion: application.revertableVersion, revertableVersion: application.revertableVersion,
navigation: application.navigation || {}, navigation: application.navigation || {},
usedPlugins: application.usedPlugins || [],
})) }))
// Initialise backend stores // Initialise backend stores

View File

@ -41,10 +41,23 @@
} }
// Construct iframe template // Construct iframe template
$: template = iframeTemplate.replace( $: pluginLinks = generatePluginLinks($store.usedPlugins)
/\{\{ CLIENT_LIB_PATH }}/, $: template = iframeTemplate
$store.clientLibPath .replace(/\{\{ CLIENT_LIB_PATH }}/, $store.clientLibPath)
) .replace(/\{\{ PLUGINS }}/, pluginLinks)
const generatePluginLinks = plugins => {
if (!plugins?.length) {
return ""
}
return plugins
.map(plugin => {
// Split up like this as otherwise parsing fails because the script
// tags confuse svelte
return `<` + `script src="/plugins/${plugin.jsUrl}"></` + `script>`
})
.join("")
}
const placeholderScreen = new Screen() const placeholderScreen = new Screen()
.name("Screen Placeholder") .name("Screen Placeholder")
@ -92,6 +105,7 @@
? [$store.componentToPaste?._id] ? [$store.componentToPaste?._id]
: [], : [],
isBudibaseEvent: true, isBudibaseEvent: true,
usedPlugins: $store.usedPlugins,
} }
// Refresh the preview when required // Refresh the preview when required

View File

@ -37,9 +37,7 @@ export default `
} }
</style> </style>
<script src='{{ CLIENT_LIB_PATH }}'></script> <script src='{{ CLIENT_LIB_PATH }}'></script>
<script {{ PLUGINS }}
type="application/javascript"
src="https://cdn.kingston.dev/plugin.min.js"></script>
<script> <script>
function receiveMessage(event) { function receiveMessage(event) {
if (!event.data) { if (!event.data) {

View File

@ -121,7 +121,8 @@ const createComponentStore = () => {
if (!state.customComponentManifest) { if (!state.customComponentManifest) {
state.customComponentManifest = {} state.customComponentManifest = {}
} }
state.customComponentManifest[schema.schema.name] = { const componentName = `plugin/${schema.schema.name}/1.0.0`
state.customComponentManifest[componentName] = {
schema, schema,
Component, Component,
} }

View File

@ -10,4 +10,13 @@ export const buildPluginEndpoints = API => ({
json: false, json: false,
}) })
}, },
/**
* Gets a list of all plugins
*/
getPlugins: async () => {
return await API.get({
url: "/api/plugin",
})
},
}) })

View File

@ -547,7 +547,7 @@ export const sync = async (ctx: any, next: any) => {
} }
} }
const updateAppPackage = async (appPackage: any, appId: any) => { export const updateAppPackage = async (appPackage: any, appId: any) => {
return context.doInAppContext(appId, async () => { return context.doInAppContext(appId, async () => {
const db = context.getAppDB() const db = context.getAppDB()
const application = await db.get(DocumentType.APP_METADATA) const application = await db.get(DocumentType.APP_METADATA)

View File

@ -1,6 +1,7 @@
const { DocumentType } = require("../../db/utils") const { DocumentType, getPluginParams } = require("../../db/utils")
const { getComponentLibraryManifest } = require("../../utilities/fileSystem") const { getComponentLibraryManifest } = require("../../utilities/fileSystem")
const { getAppDB } = require("@budibase/backend-core/context") const { getAppDB } = require("@budibase/backend-core/context")
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
exports.fetchAppComponentDefinitions = async function (ctx) { exports.fetchAppComponentDefinitions = async function (ctx) {
const db = getAppDB() const db = getAppDB()
@ -9,7 +10,6 @@ exports.fetchAppComponentDefinitions = async function (ctx) {
let componentManifests = await Promise.all( let componentManifests = await Promise.all(
app.componentLibraries.map(async library => { app.componentLibraries.map(async library => {
let manifest = await getComponentLibraryManifest(library) let manifest = await getComponentLibraryManifest(library)
return { return {
manifest, manifest,
library, library,
@ -30,5 +30,24 @@ exports.fetchAppComponentDefinitions = async function (ctx) {
} }
} }
} }
// Add custom components
const globalDB = getGlobalDB()
const response = await globalDB.allDocs(
getPluginParams(null, {
include_docs: true,
})
)
response.rows
.map(row => row.doc)
.filter(plugin => plugin.schema.type === "component")
.forEach(plugin => {
const fullComponentName = `plugin/${plugin.name}/${plugin.version}`
definitions[fullComponentName] = {
component: fullComponentName,
...plugin.schema.schema,
}
})
ctx.body = definitions ctx.body = definitions
} }

View File

@ -1,7 +1,7 @@
import { ObjectStoreBuckets } from "../../constants" import { ObjectStoreBuckets } from "../../constants"
import { extractPluginTarball } from "../../utilities/fileSystem" import { extractPluginTarball } from "../../utilities/fileSystem"
import { getGlobalDB } from "@budibase/backend-core/tenancy" import { getGlobalDB } from "@budibase/backend-core/tenancy"
import { generatePluginID } from "../../db/utils" import { generatePluginID, getPluginParams } from "../../db/utils"
import { uploadDirectory } from "@budibase/backend-core/objectStore" import { uploadDirectory } from "@budibase/backend-core/objectStore"
export async function upload(ctx: any) { export async function upload(ctx: any) {
@ -67,6 +67,14 @@ export async function upload(ctx: any) {
} }
} }
export async function fetch(ctx: any) {} export async function fetch(ctx: any) {
const db = getGlobalDB()
const response = await db.allDocs(
getPluginParams(null, {
include_docs: true,
})
)
ctx.body = response.rows.map((row: any) => row.doc)
}
export async function destroy(ctx: any) {} export async function destroy(ctx: any) {}

View File

@ -1,7 +1,14 @@
const { getScreenParams, generateScreenID } = require("../../db/utils") const {
getScreenParams,
generateScreenID,
getPluginParams,
DocumentTypes,
} = require("../../db/utils")
const { AccessController } = require("@budibase/backend-core/roles") const { AccessController } = require("@budibase/backend-core/roles")
const { getAppDB } = require("@budibase/backend-core/context") const { getAppDB } = require("@budibase/backend-core/context")
const { events } = require("@budibase/backend-core") const { events } = require("@budibase/backend-core")
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
import { updateAppPackage } from "./application"
exports.fetch = async ctx => { exports.fetch = async ctx => {
const db = getAppDB() const db = getAppDB()
@ -32,6 +39,48 @@ exports.save = async ctx => {
const response = await db.put(screen) const response = await db.put(screen)
// Find any custom components being used
let pluginNames = []
findPlugins(screen.props, pluginNames)
if (pluginNames.length) {
const globalDB = getGlobalDB()
const pluginsResponse = await globalDB.allDocs(
getPluginParams(null, {
include_docs: true,
})
)
const requiredPlugins = pluginsResponse.rows
.map(row => row.doc)
.filter(plugin => {
return (
plugin.schema.type === "component" &&
pluginNames.includes(`plugin/${plugin.name}/${plugin.version}`)
)
})
// Update the app metadata
const application = await db.get(DocumentTypes.APP_METADATA)
let usedPlugins = application.usedPlugins || []
let pluginAdded = false
requiredPlugins.forEach(plugin => {
if (!usedPlugins.find(x => x._id === plugin._id)) {
pluginAdded = true
usedPlugins.push({
_id: plugin._id,
name: plugin.name,
version: plugin.version,
jsUrl: plugin.jsUrl,
})
}
})
if (pluginAdded) {
console.log("plugin added! new plugins", usedPlugins)
await updateAppPackage({ usedPlugins }, ctx.appId)
}
}
if (eventFn) { if (eventFn) {
await eventFn(screen) await eventFn(screen)
} }
@ -56,3 +105,18 @@ exports.destroy = async ctx => {
} }
ctx.status = 200 ctx.status = 200
} }
const findPlugins = (component, foundPlugins) => {
if (!component) {
return
}
if (component._component.startsWith("plugin")) {
if (!foundPlugins.includes(component._component)) {
foundPlugins.push(component._component)
}
}
if (!component._children || !component._children.length) {
return
}
component._children.forEach(child => findPlugins(child, foundPlugins))
}

View File

@ -109,6 +109,7 @@ export const serveApp = async function (ctx: any) {
production: env.isProd(), production: env.isProd(),
appId, appId,
clientLibPath: clientLibraryPath(appId, appInfo.version, ctx), clientLibPath: clientLibraryPath(appId, appInfo.version, ctx),
usedPlugins: appInfo.usedPlugins,
}) })
const appHbs = loadHandlebarsFile(`${__dirname}/templates/app.hbs`) const appHbs = loadHandlebarsFile(`${__dirname}/templates/app.hbs`)

View File

@ -3,6 +3,7 @@
export let favicon = "" export let favicon = ""
export let clientLibPath export let clientLibPath
export let usedPlugins
</script> </script>
<svelte:head> <svelte:head>
@ -85,9 +86,13 @@
</script> </script>
<!-- Custom components need inserted after the core client library --> <!-- Custom components need inserted after the core client library -->
<!-- But before loadBudibase is called --> <!-- But before loadBudibase is called -->
<script {#if usedPlugins?.length}
type="application/javascript" {#each usedPlugins as plugin}
src="https://cdn.kingston.dev/plugin.min.js"></script> <script
type="application/javascript"
src={`/plugins/${plugin.jsUrl}`}></script>
{/each}
{/if}
<script type="application/javascript"> <script type="application/javascript">
if (window.loadBudibase) { if (window.loadBudibase) {
window.loadBudibase() window.loadBudibase()

View File

@ -384,3 +384,10 @@ exports.getMultiIDParams = ids => {
include_docs: true, include_docs: true,
} }
} }
/**
* Gets parameters for retrieving automations, this is a utility function for the getDocParams function.
*/
exports.getPluginParams = (pluginId = null, otherProps = {}) => {
return getDocParams(DocumentType.PLUGIN, pluginId, otherProps)
}