Spark Workbench Dashboard Gadget Plugin Guide
This guide is for developers extending Spark Workbench Dashboard Gadget. A Gadget plugin should be self-contained: backend extension point, frontend Vue components, and generated frontend assets all live in the same plugin module. The platform host only loads and runs the plugin.
1. Gadget Plugin Model
A Spark Workbench Gadget has two parts:
- Backend extension point: registers Gadget descriptors through Lattice and renders runtime data.
- Frontend component: displays the data with Vue, packaged as a UMD resource and loaded dynamically by Workbench.
The default plugin is located at:
spark-plugins/spark-workbench-default-plugin/
It contains both Java and frontend code. Third-party plugins should follow the same structure so concrete Gadget code does not leak into the platform host.
2. Recommended Structure
my-workbench-gadget-plugin/
pom.xml
src/main/java/
.../MyGadgetRegistryExt.java
src/main/frontend/
package.json
vite.config.js
src/
index.js
GadgetMyChart.vue
GadgetMyList.vue
MyGadgetRegistryExt.javadeclares Gadget type, title, category, icon, frontend resource URL, and runtime data.src/main/frontend/src/index.jsregisters Vue components with the Workbench host.vite.config.jsbuilds a UMD bundle into the plugin jar static resource directory.
3. Backend Extension
Implement SparkEraTaskGadgetRegistryExt, usually by extending the blank implementation:
@Realization(codes = "my.workbench.gadget")
public class MyGadgetRegistryExt extends BlankSparkEraTaskGadgetRegistryExt {
private static final String FRONTEND_RESOURCE =
"/spark-gadgets/my-workbench-gadget/index.umd.js";
@Override
public List<SparkEraTaskGadgetDescriptor> listGadgetDescriptors(SparkEraTaskGadgetCatalogContext context) {
SparkEraTaskGadgetDescriptor descriptor = new SparkEraTaskGadgetDescriptor();
descriptor.setCode("myChart");
descriptor.setTitle("My Chart");
descriptor.setCategory("charts");
descriptor.setIcon("bar");
descriptor.setComponent("spark$gadget-my-chart");
descriptor.setFrontendResource(FRONTEND_RESOURCE);
descriptor.setConfigurable(true);
return List.of(descriptor);
}
@Override
public SparkEraTaskGadgetRenderResult renderGadget(SparkEraTaskGadgetRenderContext context) {
if (!StringUtils.equals(context.getGadgetType(), "myChart")) {
return null;
}
SparkEraTaskGadgetRenderResult result =
SparkEraTaskGadgetRenderResult.of("myChart", "spark$gadget-my-chart");
result.getData().put("bars", List.of(Map.of("label", "Open", "value", 12)));
return result;
}
}
Important fields:
code: globally unique Gadget type code.component: frontend component key, recommended formatspark$gadget-xxx.frontendResource: runtime URL of the plugin frontend entry.configurable: whether this Gadget can open a configuration panel.
4. Frontend Self-Registration
The frontend entry registers components when the script is loaded:
import GadgetMyChart from "./GadgetMyChart.vue";
const COMPONENTS = {
"spark$gadget-my-chart": GadgetMyChart
};
const api = typeof window === "undefined" ? null : window.__SPARK_DASHBOARD__;
if (api && typeof api.registerGadgetComponent === "function") {
Object.entries(COMPONENTS).forEach(([key, component]) => {
api.registerGadgetComponent(key, component);
});
}
Workbench collects frontendResource values from the Dashboard catalog, de-duplicates them, and injects the scripts dynamically. After a bundle is loaded, its components are available in the host registry.
5. Vite Build
Use Vite library mode and keep Vue external. The plugin should use the host Vue runtime through window.__SPARK_VUE__.
import { resolve } from "node:path";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({
plugins: [vue()],
build: {
outDir: resolve(__dirname, "../../../target/classes/META-INF/resources/spark-gadgets/my-workbench-gadget"),
lib: {
entry: resolve(__dirname, "src/index.js"),
name: "MyWorkbenchGadget",
formats: ["umd"],
fileName: () => "index.umd.js",
cssFileName: "index"
},
rollupOptions: {
external: ["vue"],
output: {
globals: {
vue: "__SPARK_VUE__"
}
}
}
}
});
Build output:
target/classes/META-INF/resources/spark-gadgets/my-workbench-gadget/index.umd.js
target/classes/META-INF/resources/spark-gadgets/my-workbench-gadget/index.css
Spring Boot serves META-INF/resources, so the runtime entry is:
/spark-gadgets/my-workbench-gadget/index.umd.js
6. Maven Packaging
Use frontend-maven-plugin in the plugin pom.xml:
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.15.0</version>
<configuration>
<workingDirectory>src/main/frontend</workingDirectory>
<nodeVersion>v18.19.1</nodeVersion>
<npmVersion>9.2.0</npmVersion>
</configuration>
<executions>
<execution>
<id>install-node-and-npm</id>
<phase>prepare-package</phase>
<goals>
<goal>install-node-and-npm</goal>
</goals>
</execution>
<execution>
<id>npm-ci</id>
<phase>prepare-package</phase>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>ci</arguments>
</configuration>
</execution>
<execution>
<id>npm-build</id>
<phase>prepare-package</phase>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>run build</arguments>
</configuration>
</execution>
</executions>
</plugin>
The final plugin jar contains both Java extensions and frontend assets. The platform only needs to depend on the jar.
7. Component Rules
- Components receive host props such as
dataandt; they should not call platform APIs directly. - Use
scopedstyles and Spark theme variables such as--text-main,--card-bg, and--primary. - Do not create a separate Vue app inside the plugin.
- Do not bundle a second Vue runtime.
- Runtime data is returned by
renderGadget; components only render it. - Configuration is stored in the Gadget instance
configand read during backend rendering.
8. Verification Checklist
cd src/main/frontend
npm ci
npm run build
Then package the plugin:
mvn -DskipTests package
Check the jar contains:
META-INF/resources/spark-gadgets/<plugin-code>/index.umd.js
META-INF/resources/spark-gadgets/<plugin-code>/index.css
After starting Spark Console, open:
/spark-gadgets/<plugin-code>/index.umd.js
If it returns JavaScript, the plugin frontend has been served correctly.
9. Troubleshooting
The Gadget shows Unsupported
The component key likely does not match the key registered in index.js, or frontendResource was not loaded.
Styles are missing
Check whether Vite generated index.css and whether the entry script injects that CSS file.
Vue runtime errors
Make sure Vue is external in Vite and mapped to __SPARK_VUE__. A plugin must not bundle its own Vue runtime.
The Gadget does not appear in the catalog
Ensure the plugin jar is on the Spark Console classpath and the @Realization(codes = "...") business code is scanned by Lattice.
10. Delivery Principle
A Dashboard Gadget plugin should be one self-contained artifact:
- Java extension point in the plugin module.
- Vue components in the plugin module.
- Frontend build assets packaged into the plugin jar.
- The platform host keeps only the registry, dynamic loader, card shell, and fallback component.
With this model, adding a business Gadget only requires adding a plugin dependency, not changing platform core code.