Skip to main content

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.

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.java declares Gadget type, title, category, icon, frontend resource URL, and runtime data.
  • src/main/frontend/src/index.js registers Vue components with the Workbench host.
  • vite.config.js builds 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 format spark$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 data and t; they should not call platform APIs directly.
  • Use scoped styles 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 config and 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.