From daa1d40e39f3f7b5da590ff601d12e1e80557e47 Mon Sep 17 00:00:00 2001 From: Fabrizio Antonangeli Date: Mon, 25 Mar 2024 20:35:52 +0100 Subject: [PATCH] KOGITO-10042: Migrate SWF Dev UI Java module from kogito-apps to kie-tools (#2167) Co-authored-by: Yeser Amer Co-authored-by: Thiago Lugli Co-authored-by: Paulo Martins --- .../env/index.js | 31 +++ .../install.js | 25 ++ .../package.json | 41 ++++ .../pom.xml | 150 ++++++++++++ .../pom.xml | 201 ++++++++++++++++ .../tools/deployment/DevConsoleProcessor.java | 133 +++++++++++ .../SonataFlowQuarkusExtensionProcessor.java | 32 +++ .../resources/dev-templates/embedded.html | 53 ++++ .../resources/dev-templates/monitoring.html | 44 ++++ .../dev-templates/workflowInstances.html | 45 ++++ .../src/main/resources/static/index.html | 45 ++++ .../pom.xml | 159 ++++++++++++ .../dashboard/CustomDashboardService.java | 83 +++++++ .../dashboard/CustomDashboardStorage.java | 36 +++ .../CustomDashboardFilterParamConverter.java | 40 ++++ ...DashboardFilterParamConverterProvider.java | 41 ++++ .../impl/CustomDashboardStorageImpl.java | 226 ++++++++++++++++++ .../model/CustomDashboardFilter.java | 50 ++++ .../dashboard/model/CustomDashboardInfo.java | 95 ++++++++ .../src/main/resources/META-INF/beans.xml | 7 + .../resources/META-INF/quarkus-extension.yaml | 28 +++ .../impl/CustomDashboardStorageTest.java | 115 +++++++++ .../CustomDashboardStorageTestProfile.java | 46 ++++ .../custom/dashboards/products.dash.yaml | 61 +++++ .../custom/dashboards/subdir/age.dash.yml | 44 ++++ pnpm-lock.yaml | 15 ++ repo/graph.dot | 2 + repo/graph.json | 7 + 28 files changed, 1855 insertions(+) create mode 100644 packages/sonataflow-quarkus-devui-extension/env/index.js create mode 100644 packages/sonataflow-quarkus-devui-extension/install.js create mode 100644 packages/sonataflow-quarkus-devui-extension/package.json create mode 100644 packages/sonataflow-quarkus-devui-extension/pom.xml create mode 100644 packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/pom.xml create mode 100644 packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/java/org/kie/sonataflow/swf/tools/deployment/DevConsoleProcessor.java create mode 100644 packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/java/org/kie/sonataflow/swf/tools/deployment/SonataFlowQuarkusExtensionProcessor.java create mode 100644 packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/resources/dev-templates/embedded.html create mode 100644 packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/resources/dev-templates/monitoring.html create mode 100644 packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/resources/dev-templates/workflowInstances.html create mode 100644 packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/resources/static/index.html create mode 100644 packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/pom.xml create mode 100644 packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/CustomDashboardService.java create mode 100644 packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/CustomDashboardStorage.java create mode 100644 packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/converter/CustomDashboardFilterParamConverter.java create mode 100644 packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/converter/CustomDashboardFilterParamConverterProvider.java create mode 100644 packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/impl/CustomDashboardStorageImpl.java create mode 100644 packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/model/CustomDashboardFilter.java create mode 100644 packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/model/CustomDashboardInfo.java create mode 100644 packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/resources/META-INF/beans.xml create mode 100644 packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/resources/META-INF/quarkus-extension.yaml create mode 100644 packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/test/java/org/kie/sonataflow/swf/tools/custom/dashboard/impl/CustomDashboardStorageTest.java create mode 100644 packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/test/java/org/kie/sonataflow/swf/tools/custom/dashboard/impl/CustomDashboardStorageTestProfile.java create mode 100644 packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/test/resources/custom/dashboards/products.dash.yaml create mode 100644 packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/test/resources/custom/dashboards/subdir/age.dash.yml diff --git a/packages/sonataflow-quarkus-devui-extension/env/index.js b/packages/sonataflow-quarkus-devui-extension/env/index.js new file mode 100644 index 00000000000..33ffd575a68 --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/env/index.js @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const { varsWithName, composeEnv, getOrDefault } = require("@kie-tools-scripts/build-env"); + +module.exports = composeEnv([require("@kie-tools/root-env/env")], { + vars: varsWithName({}), + get env() { + return { + sonataflowQuarkusDevuiExtension: { + version: require("../package.json").version, + }, + }; + }, +}); diff --git a/packages/sonataflow-quarkus-devui-extension/install.js b/packages/sonataflow-quarkus-devui-extension/install.js new file mode 100644 index 00000000000..99bfadb8cf8 --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/install.js @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const buildEnv = require("./env"); +const { setup } = require("@kie-tools/maven-config-setup-helper"); + +setup(` + -Drevision=${buildEnv.env.sonataflowQuarkusDevuiExtension.version} +`); diff --git a/packages/sonataflow-quarkus-devui-extension/package.json b/packages/sonataflow-quarkus-devui-extension/package.json new file mode 100644 index 00000000000..9407dfdba19 --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/package.json @@ -0,0 +1,41 @@ +{ + "private": true, + "name": "@kie-tools/sonataflow-quarkus-devui-extension", + "version": "0.0.0", + "description": "", + "license": "Apache-2.0", + "homepage": "https://github.com/apache/incubator-kie-tools", + "repository": { + "type": "git", + "url": "https://github.com/apache/incubator-kie-tools.git" + }, + "bugs": { + "url": "https://github.com/apache/incubator-kie-tools/issues" + }, + "scripts": { + "build:dev": "run-script-os", + "build:dev:darwin:linux": "mvn clean install -DskipTests", + "build:dev:win32": "pnpm powershell \"mvn clean install -DskipTests \"", + "build:prod": "pnpm lint && run-script-os", + "build:prod:darwin:linux": "mvn clean install -DskipTests=$(build-env tests.run --not) -Dmaven.test.failure.ignore=$(build-env tests.ignoreFailures)", + "build:prod:win32": "pnpm powershell \"mvn clean install `-DskipTests `-Dmaven.test.failure.ignore=$(build-env tests.ignoreFailures)\"", + "install": "node install.js", + "lint": "echo 'Linting'", + "powershell": "@powershell -NoProfile -ExecutionPolicy Unrestricted -Command", + "quarkus:dev": "run-script-os", + "quarkus:dev:darwin:linux": "mvn clean package quarkus:dev -DskipTests", + "quarkus:dev:win32": "mvn clean package quarkus:dev -DskipTests" + }, + "devDependencies": { + "@kie-tools/maven-config-setup-helper": "workspace:*", + "@kie-tools/root-env": "workspace:*", + "@kie-tools/serverless-workflow-dev-ui-webapp": "workspace:*", + "run-script-os": "^1.1.6" + }, + "kieTools": { + "requiredPreinstalledCliCommands": [ + "java", + "mvn" + ] + } +} \ No newline at end of file diff --git a/packages/sonataflow-quarkus-devui-extension/pom.xml b/packages/sonataflow-quarkus-devui-extension/pom.xml new file mode 100644 index 00000000000..cded104b09b --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/pom.xml @@ -0,0 +1,150 @@ + + + + + 4.0.0 + KIE Tools :: SonataFlow Quarkus Dev UI Extension + org.apache.kie.sonataflow + ${revision} + sonataflow-quarkus-devui-extension-parent + pom + + + + + central + Central Repository + https://repo.maven.apache.org/maven2 + default + + false + + + + apache-public-repository-group + Apache Public Repository Group + https://repository.apache.org/content/groups/public/ + + true + never + + + true + daily + + + + + + 3.12.1 + 17 + 17 + UTF-8 + 3.2.0 + org.kie.kogito.quarkus.swf.dev.ui + 1.3.0 + 3.2.9.Final + 999-20240218-SNAPSHOT + + + + sonataflow-quarkus-devui-extension-deployment + sonataflow-quarkus-devui-extension + + + + + + io.quarkus + quarkus-bom + ${quarkus.platform.version} + pom + import + + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${version.flatten.plugin} + + true + resolveCiFriendliesOnly + + + + flatten-revision + process-resources + + flatten + + + + flatten-revision-clean + clean + + clean + + + + + + + + + io.quarkus + quarkus-maven-plugin + + + maven-surefire-plugin + + + org.jboss.logmanager.LogManager + + + + + maven-failsafe-plugin + + + org.jboss.logmanager.LogManager + + + + + maven-compiler-plugin + + + + + diff --git a/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/pom.xml b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/pom.xml new file mode 100644 index 00000000000..e319b361962 --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/pom.xml @@ -0,0 +1,201 @@ + + + + 4.0.0 + + + org.apache.kie.sonataflow + sonataflow-quarkus-devui-extension-parent + ${revision} + ../pom.xml + + + sonataflow-quarkus-devui-extension-deployment + KIE Tools :: SonataFlow Quarkus Dev UI Extension :: Deployment + + + ../../serverless-workflow-dev-ui-webapp + + + + + + io.quarkus + quarkus-development-mode-spi + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-arc-deployment + + + io.quarkus + quarkus-resteasy-deployment + + + io.quarkus + quarkus-resteasy-jackson-deployment + + + io.quarkus + quarkus-rest-client-deployment + + + io.quarkus + quarkus-resteasy-multipart-deployment + + + + + io.quarkus + quarkus-undertow-deployment + + + + + io.rest-assured + rest-assured + test + + + org.mockito + mockito-core + test + + + org.apache.kie.sonataflow + sonataflow-quarkus-devui-extension + ${project.version} + + + io.quarkus + quarkus-junit5-internal + test + + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + + io.quarkus + quarkus-extension-processor + ${quarkus.platform.version} + + + + + + maven-resources-plugin + ${version.resources.plugin} + + + copy-webapp + process-resources + + copy-resources + + + ${basedir}/target/classes/dev-static/resources/webapp + + + ${path.to.webapp.app}/dist/resources/webapp + false + + + ${path.to.webapp.app}/dist/webapp + false + + + + + + + copy-envelope-resources + process-resources + + copy-resources + + + ${basedir}/target/classes/dev-static + + + ${path.to.webapp.app}/dist + + form-displayer.html + form-displayer.js + serverless-workflow-combined-editor-envelope.html + serverless-workflow-combined-editor-envelope.js + serverless-workflow-text-editor-envelope.html + serverless-workflow-text-editor-envelope.js + serverless-workflow-diagram-editor-envelope.html + serverless-workflow-diagram-editor-envelope.js + vendors-*.bundle.js + *.worker.js + + + + + ${path.to.webapp.app}/dist + + diagram/ + + + + + + + + copy-index + process-resources + + copy-resources + + + ${basedir}/target/classes/dev-static + + + ${basedir}/target/classes/static + + index.html + + + + + + + + + + diff --git a/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/java/org/kie/sonataflow/swf/tools/deployment/DevConsoleProcessor.java b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/java/org/kie/sonataflow/swf/tools/deployment/DevConsoleProcessor.java new file mode 100644 index 00000000000..721a00c4e2e --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/java/org/kie/sonataflow/swf/tools/deployment/DevConsoleProcessor.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.sonataflow.swf.tools.deployment; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.file.Path; + +import io.quarkus.deployment.IsDevelopment; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.ConfigurationBuildItem; +import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.builditem.LiveReloadBuildItem; +import io.quarkus.deployment.builditem.ShutdownContextBuildItem; +import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; +import io.quarkus.deployment.util.WebJarUtil; +import io.quarkus.devui.spi.page.CardPageBuildItem; +import io.quarkus.devui.spi.page.Page; +import io.quarkus.maven.dependency.ResolvedDependency; +import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem; +import io.quarkus.vertx.http.deployment.RouteBuildItem; +import io.quarkus.vertx.http.runtime.devmode.DevConsoleRecorder; +import io.quarkus.vertx.http.runtime.management.ManagementInterfaceBuildTimeConfig; + +public class DevConsoleProcessor { + + private static final String STATIC_RESOURCES_PATH = "dev-static/"; + private static final String BASE_RELATIVE_URL = "/q/dev-v1/org.apache.kie.sonataflow.sonataflow-quarkus-devui-extension"; + + @BuildStep(onlyIf = IsDevelopment.class) + public CardPageBuildItem pages(NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, + ManagementInterfaceBuildTimeConfig managementInterfaceBuildTimeConfig, + LaunchModeBuildItem launchModeBuildItem, + ConfigurationBuildItem configurationBuildItem) throws UnsupportedEncodingException { + + String uiPath = nonApplicationRootPathBuildItem.resolveManagementPath(BASE_RELATIVE_URL, + managementInterfaceBuildTimeConfig, launchModeBuildItem, true); + + String devUIUrl = getProperty(configurationBuildItem, "kogito.dev-ui.url"); + String devUIUrlQueryParam = devUIUrl != null ? "&devUIUrl=" + URLEncoder.encode(devUIUrl, "UTF-8") : ""; + + String dataIndexUrl = getProperty(configurationBuildItem, "kogito.data-index.url"); + String dataIndexUrlQueryParam = dataIndexUrl != null ? "&dataIndexUrl=" + URLEncoder.encode(dataIndexUrl, "UTF-8") : ""; + + CardPageBuildItem cardPageBuildItem = new CardPageBuildItem(); + + cardPageBuildItem.addPage(Page.externalPageBuilder("Workflows") + .url(uiPath + "/index.html?page=Workflows" + devUIUrlQueryParam + dataIndexUrlQueryParam, uiPath) + .isHtmlContent() + .icon("font-awesome-solid:diagram-project")); + + cardPageBuildItem.addPage(Page.externalPageBuilder("Monitoring") + .url(uiPath + "/index.html?page=Monitoring" + devUIUrlQueryParam + dataIndexUrlQueryParam, uiPath) + .isHtmlContent() + .icon("font-awesome-solid:gauge-high")); + + return cardPageBuildItem; + } + + @BuildStep(onlyIf = IsDevelopment.class) + @Record(ExecutionTime.RUNTIME_INIT) + public void deployStaticResources(final DevConsoleRecorder recorder, + final CurateOutcomeBuildItem curateOutcomeBuildItem, + final LiveReloadBuildItem liveReloadBuildItem, + final LaunchModeBuildItem launchMode, + final ShutdownContextBuildItem shutdownContext, + final BuildProducer routeBuildItemBuildProducer) throws IOException { + ResolvedDependency devConsoleResourcesArtifact = WebJarUtil.getAppArtifact(curateOutcomeBuildItem, + "org.apache.kie.sonataflow", + "sonataflow-quarkus-devui-extension-deployment"); + + Path devConsoleStaticResourcesDeploymentPath = WebJarUtil.copyResourcesForDevOrTest( + liveReloadBuildItem, + curateOutcomeBuildItem, + launchMode, + devConsoleResourcesArtifact, + STATIC_RESOURCES_PATH, + true); + + routeBuildItemBuildProducer.produce(new RouteBuildItem.Builder() + .route(BASE_RELATIVE_URL + "/*") + .handler(recorder.devConsoleHandler(devConsoleStaticResourcesDeploymentPath.toString(), + shutdownContext)) + .build()); + } + + private static String getProperty(ConfigurationBuildItem configurationBuildItem, + String propertyKey) { + + String propertyValue = configurationBuildItem + .getReadResult() + .getAllBuildTimeValues() + .get(propertyKey); + + if (propertyValue == null) { + propertyValue = configurationBuildItem + .getReadResult() + .getBuildTimeRunTimeValues() + .get(propertyKey); + } else { + return propertyValue; + } + + if (propertyValue == null) { + propertyValue = configurationBuildItem + .getReadResult() + .getRunTimeDefaultValues() + .get(propertyKey); + } + + return propertyValue; + } +} diff --git a/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/java/org/kie/sonataflow/swf/tools/deployment/SonataFlowQuarkusExtensionProcessor.java b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/java/org/kie/sonataflow/swf/tools/deployment/SonataFlowQuarkusExtensionProcessor.java new file mode 100644 index 00000000000..60d10d1853b --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/java/org/kie/sonataflow/swf/tools/deployment/SonataFlowQuarkusExtensionProcessor.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.sonataflow.swf.tools.deployment; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.FeatureBuildItem; + +class SonataFlowQuarkusExtensionProcessor { + + private static final String FEATURE = "sonataflow-quarkus-devui-extension"; + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } +} diff --git a/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/resources/dev-templates/embedded.html b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/resources/dev-templates/embedded.html new file mode 100644 index 00000000000..da2e7069430 --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/resources/dev-templates/embedded.html @@ -0,0 +1,53 @@ + + + + Workflow Instances + + + + + Monitoring + + diff --git a/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/resources/dev-templates/monitoring.html b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/resources/dev-templates/monitoring.html new file mode 100644 index 00000000000..2132fd4c35c --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/resources/dev-templates/monitoring.html @@ -0,0 +1,44 @@ + +{#include main fluid=true} {#style} .main-container { margin: 0; padding: 0; } #envelope-app { width: 100vw; height: +calc(100vh - 98px); } {/style} {#title}Runtime UI{/title} {#body} +
+ + + +{/body} {/include} diff --git a/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/resources/dev-templates/workflowInstances.html b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/resources/dev-templates/workflowInstances.html new file mode 100644 index 00000000000..90370483336 --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/resources/dev-templates/workflowInstances.html @@ -0,0 +1,45 @@ + +{#include main fluid=true} {#style} .main-container { margin: 0; padding: 0; } #envelope-app { width: 100vw; height: +calc(100vh - 98px); } {/style} {#title}Runtime UI{/title} {#body} +
+ + + +{/body} {/include} diff --git a/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/resources/static/index.html b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/resources/static/index.html new file mode 100644 index 00000000000..8e850ece508 --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension-deployment/src/main/resources/static/index.html @@ -0,0 +1,45 @@ + +
+ + + diff --git a/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/pom.xml b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/pom.xml new file mode 100644 index 00000000000..87e8bf44fe4 --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/pom.xml @@ -0,0 +1,159 @@ + + + + 4.0.0 + + org.apache.kie.sonataflow + sonataflow-quarkus-devui-extension-parent + ${revision} + ../pom.xml + + sonataflow-quarkus-devui-extension + KIE Tools :: SonataFlow Quarkus Dev UI Extension :: Runtime + Runtime development tools for Serverless Workflows + + + + io.quarkus + quarkus-core + + + + io.quarkus + quarkus-resteasy + + + + io.quarkus + quarkus-resteasy-jackson + + + + io.quarkus + quarkus-rest-client + + + + io.quarkus + quarkus-resteasy-multipart + + + + io.quarkus + quarkus-arc + + + + io.quarkus.arc + arc-processor + + + + + io.quarkus + quarkus-undertow + + + + jakarta.annotation + jakarta.annotation-api + + + + org.apache.commons + commons-lang3 + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + org.mockito + mockito-junit-jupiter + test + + + + io.quarkus + quarkus-junit5 + test + + + + io.quarkus + quarkus-junit5-mockito + test + + + + io.rest-assured + rest-assured + test + + + org.awaitility + awaitility + test + + + + + + io.quarkus + quarkus-extension-maven-plugin + ${quarkus.platform.version} + + + compile + + extension-descriptor + + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + + io.quarkus + quarkus-extension-processor + ${quarkus.platform.version} + + + + + + + diff --git a/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/CustomDashboardService.java b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/CustomDashboardService.java new file mode 100644 index 00000000000..530819d4cae --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/CustomDashboardService.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.sonataflow.swf.tools.custom.dashboard; + +import org.kie.sonataflow.swf.tools.custom.dashboard.model.CustomDashboardFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import static jakarta.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR; + +@Path("/customDashboard") +public class CustomDashboardService { + + private static final Logger LOGGER = LoggerFactory.getLogger(CustomDashboardService.class); + + private CustomDashboardStorage storage; + + @Inject + public void setStorage(CustomDashboardStorage storage) { + this.storage = storage; + } + + @GET + @Path("/count") + @Produces(MediaType.TEXT_PLAIN) + public Response getCustomDashboardFilesCount() { + try { + return Response.ok(storage.getCustomDashboardFilesCount()).build(); + } catch (Exception e) { + LOGGER.warn("Error while getting CustomDashboard file count: ", e); + return Response.status(INTERNAL_SERVER_ERROR.getStatusCode(), "Unexpected error while getting CustomDashboard files count: " + e.getMessage()).build(); + } + } + + @GET + @Path("/list") + @Produces(MediaType.APPLICATION_JSON) + public Response getCustomDashboardFiles(@QueryParam("names") CustomDashboardFilter filter) { + try { + return Response.ok(storage.getCustomDashboardFiles(filter)).build(); + } catch (Exception e) { + LOGGER.warn("Error while getting CustomDashboard list: ", e); + return Response.status(INTERNAL_SERVER_ERROR.getStatusCode(), "Unexpected error while getting CustomDashboard files list: " + e.getMessage()).build(); + } + } + + @GET + @Path("/{name:\\S+}") + @Produces(MediaType.APPLICATION_JSON) + public Response getCustomDashboardFileContent(@PathParam("name") String name) { + try { + return Response.ok(storage.getCustomDashboardFileContent(name)).build(); + } catch (Exception e) { + LOGGER.warn("Error while getting CustomDashboard file content: ", e); + return Response.status(INTERNAL_SERVER_ERROR.getStatusCode(), "Unexpected error while getting CustomDashboard file content: " + e.getMessage()).build(); + } + } +} diff --git a/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/CustomDashboardStorage.java b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/CustomDashboardStorage.java new file mode 100644 index 00000000000..29f6096ad69 --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/CustomDashboardStorage.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.sonataflow.swf.tools.custom.dashboard; + +import java.io.IOException; +import java.util.Collection; + +import org.kie.sonataflow.swf.tools.custom.dashboard.model.CustomDashboardFilter; +import org.kie.sonataflow.swf.tools.custom.dashboard.model.CustomDashboardInfo; + +public interface CustomDashboardStorage { + + int getCustomDashboardFilesCount(); + + Collection getCustomDashboardFiles(CustomDashboardFilter filter); + + String getCustomDashboardFileContent(String name) throws IOException; + + void updateCustomDashboard(String content); +} diff --git a/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/converter/CustomDashboardFilterParamConverter.java b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/converter/CustomDashboardFilterParamConverter.java new file mode 100644 index 00000000000..fe648a586ad --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/converter/CustomDashboardFilterParamConverter.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.sonataflow.swf.tools.custom.dashboard.converter; + +import java.util.Collections; +import java.util.StringTokenizer; +import java.util.stream.Collectors; + +import org.kie.sonataflow.swf.tools.custom.dashboard.model.CustomDashboardFilter; + +import jakarta.ws.rs.ext.ParamConverter; +import jakarta.ws.rs.ext.Provider; + +@Provider +public class CustomDashboardFilterParamConverter implements ParamConverter { + public CustomDashboardFilter fromString(String names) { + StringTokenizer stringTokenizer = new StringTokenizer(names, ";"); + return new CustomDashboardFilter(Collections.list(stringTokenizer).stream().map(s -> (String) s).collect(Collectors.toList())); + } + + public String toString(CustomDashboardFilter names) { + return names.toString(); + } +} diff --git a/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/converter/CustomDashboardFilterParamConverterProvider.java b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/converter/CustomDashboardFilterParamConverterProvider.java new file mode 100644 index 00000000000..dcf1487657b --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/converter/CustomDashboardFilterParamConverterProvider.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.sonataflow.swf.tools.custom.dashboard.converter; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import org.kie.sonataflow.swf.tools.custom.dashboard.model.CustomDashboardFilter; + +import jakarta.ws.rs.ext.ParamConverter; +import jakarta.ws.rs.ext.ParamConverterProvider; +import jakarta.ws.rs.ext.Provider; + +@Provider +public class CustomDashboardFilterParamConverterProvider implements ParamConverterProvider { + + @SuppressWarnings("unchecked") + @Override + public ParamConverter getConverter(Class rawType, Type genericType, Annotation[] annotations) { + if (rawType.isAssignableFrom(CustomDashboardFilter.class)) { + return (ParamConverter) new CustomDashboardFilterParamConverter(); + } + return null; + } +} diff --git a/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/impl/CustomDashboardStorageImpl.java b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/impl/CustomDashboardStorageImpl.java new file mode 100644 index 00000000000..5060c20f749 --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/impl/CustomDashboardStorageImpl.java @@ -0,0 +1,226 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.sonataflow.swf.tools.custom.dashboard.impl; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.nio.file.attribute.BasicFileAttributes; +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.TimeZone; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.microprofile.config.ConfigProvider; +import org.kie.sonataflow.swf.tools.custom.dashboard.CustomDashboardStorage; +import org.kie.sonataflow.swf.tools.custom.dashboard.model.CustomDashboardFilter; +import org.kie.sonataflow.swf.tools.custom.dashboard.model.CustomDashboardInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.enterprise.context.ApplicationScoped; + +import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; + +@ApplicationScoped +public class CustomDashboardStorageImpl implements CustomDashboardStorage { + + public static final String PROJECT_CUSTOM_DASHBOARD_STORAGE_PROP = "quarkus.kogito-runtime-tools.custom.dashboard.folder"; + private static final String CUSTOM_DASHBOARD_STORAGE_PATH = "/dashboards/"; + private static final Logger LOGGER = LoggerFactory.getLogger(CustomDashboardStorageImpl.class); + + private final Map customDashboardInfoMap = new HashMap<>(); + + private URL classLoaderCustomDashboardUrl; + private URL customDashStorageUrl; + + public CustomDashboardStorageImpl() { + start(Thread.currentThread().getContextClassLoader().getResource(CUSTOM_DASHBOARD_STORAGE_PATH)); + } + + public CustomDashboardStorageImpl(final URL classLoaderFormsUrl) { + start(classLoaderFormsUrl); + } + + private void start(final URL classLoaderFormsUrl) { + start(classLoaderFormsUrl, getCustomDashboardStorageUrl(classLoaderFormsUrl)); + } + + private void start(final URL classLoaderCustomDashboardUrl, final URL customDashStorageUrl) { + try { + this.classLoaderCustomDashboardUrl = classLoaderCustomDashboardUrl; + this.customDashStorageUrl = customDashStorageUrl; + } catch (Exception ex) { + LOGGER.warn("Couldn't properly initialize CustomDashboardStorageImpl"); + } finally { + if (classLoaderCustomDashboardUrl == null) { + return; + } + + init(readCustomDashboardResources()); + String storageUrl = getStorageUrl(classLoaderCustomDashboardUrl); + Thread t = new Thread(new DashboardFilesWatcher(reload(), storageUrl)); + t.start(); + } + } + + protected String getStorageUrl(URL classLoaderCustomDashboardUrl) { + return ConfigProvider.getConfig() + .getOptionalValue(PROJECT_CUSTOM_DASHBOARD_STORAGE_PROP, String.class) + .orElseGet(() -> classLoaderCustomDashboardUrl.getFile()); + } + + private URL getCustomDashboardStorageUrl(URL classLoaderCustomDashboardUrl) { + if (classLoaderCustomDashboardUrl == null) { + return null; + } + + String storageUrl = getStorageUrl(classLoaderCustomDashboardUrl); + + File customDashStorageeFolder = new File(storageUrl); + + if (!customDashStorageeFolder.exists() || !customDashStorageeFolder.isDirectory()) { + LOGGER.warn("Cannot initialize form storage folder in path '" + customDashStorageeFolder.getPath() + "'"); + } + + try { + return customDashStorageeFolder.toURI().toURL(); + } catch (MalformedURLException ex) { + LOGGER.warn("Cannot initialize form storage folder in path '" + customDashStorageeFolder.getPath() + "'", ex); + } + return null; + } + + @Override + public int getCustomDashboardFilesCount() { + return customDashboardInfoMap.size(); + } + + @Override + public Collection getCustomDashboardFiles(CustomDashboardFilter filter) { + if (filter != null && !filter.getNames().isEmpty()) { + return customDashboardInfoMap.entrySet().stream() + .filter(entry -> StringUtils.containsAnyIgnoreCase(entry.getKey(), filter.getNames().toArray(new String[0]))) + .map(Map.Entry::getValue) + .collect(Collectors.toList()); + } else { + return customDashboardInfoMap.values(); + } + } + + @Override + public String getCustomDashboardFileContent(String name) throws IOException { + try { + return IOUtils.toString(new FileInputStream(customDashboardInfoMap.get(name).getPath()), StandardCharsets.UTF_8); + } catch (IOException e) { + LOGGER.info("custom-dashboard's file {} can not ready, because of {}", customDashboardInfoMap.get(name).getPath(), e.getMessage()); + throw e; + } + } + + @Override + public void updateCustomDashboard(String content) { + + } + + private void init(Collection files) { + customDashboardInfoMap.clear(); + files.stream() + .forEach(file -> { + LocalDateTime lastModified = LocalDateTime.ofInstant(Instant.ofEpochMilli(file.lastModified()), TimeZone.getDefault().toZoneId()); + customDashboardInfoMap.put(file.getName(), + new CustomDashboardInfo(file.getName(), file.getPath(), lastModified)); + }); + } + + private Collection readCustomDashboardResources() { + if (classLoaderCustomDashboardUrl != null) { + LOGGER.info("custom-dashboard's files path is {}", classLoaderCustomDashboardUrl.toString()); + File rootFolder = FileUtils.toFile(classLoaderCustomDashboardUrl); + return FileUtils.listFiles(rootFolder, new String[] { "dash.yaml", "dash.yml" }, true); + } + return Collections.emptyList(); + } + + private Consumer> reload() { + return this::init; + } + + private class DashboardFilesWatcher implements Runnable { + + private final Map keys = new HashMap<>(); + private Consumer> consumer; + private String folder; + + public DashboardFilesWatcher(Consumer> consumer, String folder) { + this.consumer = consumer; + this.folder = folder; + } + + @Override + public void run() { + try (WatchService ws = FileSystems.getDefault().newWatchService()) { + Path path = Path.of(folder); + keys.put(path.register(ws, ENTRY_MODIFY, ENTRY_CREATE), path); + + Files.walkFileTree(path, new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + keys.put(dir.register(ws, ENTRY_MODIFY, ENTRY_CREATE, ENTRY_DELETE), dir); + return FileVisitResult.CONTINUE; + } + }); + WatchKey key; + while ((key = ws.take()) != null) { + for (WatchEvent event : key.pollEvents()) { + LOGGER.warn("Event kind: {}. File affected: {}", event.kind(), event.context()); + consumer.accept(readCustomDashboardResources()); + } + key.reset(); + } + } catch (InterruptedException e) { + LOGGER.warn("Exception in custom dashboard folder watcher for folder: {}, message: {}", folder, e.getMessage(), e); + Thread.currentThread().interrupt(); + } catch (IOException ex) { + LOGGER.warn("Exception in custom dashboard folder watcher for folder: {}, message: {}", folder, ex.getMessage(), ex); + } + } + } +} diff --git a/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/model/CustomDashboardFilter.java b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/model/CustomDashboardFilter.java new file mode 100644 index 00000000000..e4a59b5917e --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/model/CustomDashboardFilter.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.sonataflow.swf.tools.custom.dashboard.model; + +import java.util.ArrayList; +import java.util.List; + +public class CustomDashboardFilter { + + private final List names; + + public CustomDashboardFilter() { + this.names = new ArrayList<>(); + } + + public CustomDashboardFilter(List names) { + this.names = names; + } + + public List getNames() { + return names; + } + + public void setNames(List names) { + this.names.addAll(names); + } + + @Override + public String toString() { + return "CustomDashboardFilter{" + + "names=" + names + + '}'; + } +} diff --git a/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/model/CustomDashboardInfo.java b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/model/CustomDashboardInfo.java new file mode 100644 index 00000000000..7c873cbd136 --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/java/org/kie/sonataflow/swf/tools/custom/dashboard/model/CustomDashboardInfo.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.sonataflow.swf.tools.custom.dashboard.model; + +import java.time.LocalDateTime; + +public class CustomDashboardInfo { + String name; + String path; + LocalDateTime lastModified; + + public CustomDashboardInfo(String name, String path, LocalDateTime lastModified) { + this.name = name; + this.path = path; + this.lastModified = lastModified; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public LocalDateTime getLastModified() { + return lastModified; + } + + public void setLastModified(LocalDateTime lastModified) { + this.lastModified = lastModified; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CustomDashboardInfo that = (CustomDashboardInfo) o; + + if (name != null ? !name.equals(that.name) : that.name != null) { + return false; + } + if (path != null ? !path.equals(that.path) : that.path != null) { + return false; + } + return lastModified != null ? lastModified.equals(that.lastModified) : that.lastModified == null; + } + + @Override + public int hashCode() { + int result = name != null ? name.hashCode() : 0; + result = 31 * result + (path != null ? path.hashCode() : 0); + result = 31 * result + (lastModified != null ? lastModified.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "CustomDashboardInfo{" + + "name='" + name + '\'' + + ", path='" + path + '\'' + + ", lastModified=" + lastModified + + '}'; + } + +} diff --git a/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/resources/META-INF/beans.xml b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/resources/META-INF/beans.xml new file mode 100644 index 00000000000..030bdcbec5b --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/resources/META-INF/beans.xml @@ -0,0 +1,7 @@ + + + diff --git a/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/resources/META-INF/quarkus-extension.yaml b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 00000000000..06ec65f1e4c --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +name: Serverless Workflow Tools +metadata: + keywords: + - "sonataflow" + - "workflows" + guide: "https://quarkus.io/guides/kogito" + categories: + - "business-automation" + status: "preview" diff --git a/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/test/java/org/kie/sonataflow/swf/tools/custom/dashboard/impl/CustomDashboardStorageTest.java b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/test/java/org/kie/sonataflow/swf/tools/custom/dashboard/impl/CustomDashboardStorageTest.java new file mode 100644 index 00000000000..912c021d9a6 --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/test/java/org/kie/sonataflow/swf/tools/custom/dashboard/impl/CustomDashboardStorageTest.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.sonataflow.swf.tools.custom.dashboard.impl; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.kie.sonataflow.swf.tools.custom.dashboard.CustomDashboardStorage; +import org.kie.sonataflow.swf.tools.custom.dashboard.model.CustomDashboardFilter; +import org.kie.sonataflow.swf.tools.custom.dashboard.model.CustomDashboardInfo; + +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class CustomDashboardStorageTest { + + private static String[] DASHBOARD_NAMES = { "age.dash.yml", "products.dash.yaml" }; + private static String DASHBOARD_NAME = "age.dash.yml"; + + private CustomDashboardStorage customDashboardStorage; + private URL tempFolder; + + @BeforeAll + public void init() { + tempFolder = Thread.currentThread().getContextClassLoader().getResource("custom/dashboards/"); + + customDashboardStorage = new CustomDashboardStorageImpl(tempFolder); + } + + @Test + public void testGetFormInfoList() { + Collection customDashboardInfoFilterAll = customDashboardStorage.getCustomDashboardFiles(null); + assertEquals(2, customDashboardInfoFilterAll.size()); + + CustomDashboardFilter filterEmpty = new CustomDashboardFilter(); + filterEmpty.setNames(Collections.emptyList()); + Collection customDashboardInfoAllEmptyFilter = customDashboardStorage.getCustomDashboardFiles(filterEmpty); + assertEquals(2, customDashboardInfoAllEmptyFilter.size()); + + CustomDashboardFilter filter = new CustomDashboardFilter(); + filter.setNames(Arrays.asList(DASHBOARD_NAMES)); + + Collection formInfos = customDashboardStorage.getCustomDashboardFiles(filter); + assertEquals(2, formInfos.size()); + } + + @Test + public void testHotReloading() throws IOException { + String storageUrl = Thread.currentThread().getContextClassLoader().getResource("custom/dashboards/").getFile(); + File srcFile = new File(storageUrl + "products.dash.yaml"); + File targetFile = new File(storageUrl + "copy.dash.yml"); + + assertEquals(false, targetFile.exists()); + FileUtils.copyFile(srcFile, targetFile); + assertEquals(true, targetFile.exists()); + await().atMost(20, TimeUnit.SECONDS).until(() -> testBeforeDelete()); + Collection customDashboardInfoFilterAllBeforeDelete = customDashboardStorage.getCustomDashboardFiles(null); + assertEquals(3, customDashboardInfoFilterAllBeforeDelete.size()); + + assertEquals(true, targetFile.exists()); + FileUtils.delete(targetFile); + assertEquals(false, targetFile.exists()); + await().atMost(20, TimeUnit.SECONDS).until(() -> testAfterDelete()); + + Collection customDashboardInfoFilterAllAfterDelete = customDashboardStorage.getCustomDashboardFiles(null); + assertEquals(2, customDashboardInfoFilterAllAfterDelete.size()); + } + + private boolean testBeforeDelete() { + if (customDashboardStorage.getCustomDashboardFiles(null).size() == 3) { + return true; + } + return false; + } + + private boolean testAfterDelete() { + if (customDashboardStorage.getCustomDashboardFiles(null).size() == 2) { + return true; + } + return false; + } + + @Test + public void testGetFormContent() throws IOException { + String content = customDashboardStorage.getCustomDashboardFileContent(DASHBOARD_NAME); + assertNotNull(content); + } +} diff --git a/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/test/java/org/kie/sonataflow/swf/tools/custom/dashboard/impl/CustomDashboardStorageTestProfile.java b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/test/java/org/kie/sonataflow/swf/tools/custom/dashboard/impl/CustomDashboardStorageTestProfile.java new file mode 100644 index 00000000000..9b9e3350be9 --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/test/java/org/kie/sonataflow/swf/tools/custom/dashboard/impl/CustomDashboardStorageTestProfile.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.sonataflow.swf.tools.custom.dashboard.impl; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Collections; +import java.util.Map; + +import io.quarkus.test.junit.QuarkusTestProfile; + +import static org.kie.sonataflow.swf.tools.custom.dashboard.impl.CustomDashboardStorageImpl.PROJECT_CUSTOM_DASHBOARD_STORAGE_PROP; + +public class CustomDashboardStorageTestProfile implements QuarkusTestProfile { + + private String storagePath; + + public CustomDashboardStorageTestProfile() throws IOException { + File storage = Files.createTempDirectory("CustomDashboardStorageTestProfile").toFile(); + storage.deleteOnExit(); + storage.mkdir(); + storagePath = storage.getAbsolutePath(); + } + + @Override + public Map getConfigOverrides() { + return Collections.singletonMap(PROJECT_CUSTOM_DASHBOARD_STORAGE_PROP, storagePath); + } +} diff --git a/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/test/resources/custom/dashboards/products.dash.yaml b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/test/resources/custom/dashboards/products.dash.yaml new file mode 100644 index 00000000000..ea685a0b5c3 --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/test/resources/custom/dashboards/products.dash.yaml @@ -0,0 +1,61 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +datasets: + - uuid: products + content: >- + [ + ["Computers", "Scanner", 5, 3], + ["Computers", "Printer", 7, 4], + ["Computers", "Laptop", 3, 2], + ["Electronics", "Camera", 10, 7], + ["Electronics", "Headphones", 5, 9] + ] + columns: + - id: Section + type: LABEL + - id: Product + type: LABEL + - id: Quantity + type: NUMBER + - id: Quantity2 + type: NUMBER +pages: + - components: + - html: Welcome to Dashbuilder! + properties: + font-size: xx-large + margin-bottom: 30px + - settings: + type: BARCHART + dataSetLookup: + uuid: products + group: + - columnGroup: + source: Product + groupFunctions: + - source: Product + - source: Quantity + function: SUM + - source: Quantity2 + function: SUM + - settings: + type: TABLE + dataSetLookup: + uuid: products diff --git a/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/test/resources/custom/dashboards/subdir/age.dash.yml b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/test/resources/custom/dashboards/subdir/age.dash.yml new file mode 100644 index 00000000000..011aff8684e --- /dev/null +++ b/packages/sonataflow-quarkus-devui-extension/sonataflow-quarkus-devui-extension/src/test/resources/custom/dashboards/subdir/age.dash.yml @@ -0,0 +1,44 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +datasets: + - uuid: age + content: >- + [ + ["John", 5], + ["Mary", 7], + ["Mark", 3] + ] + columns: + - id: Name + type: LABEL + - id: Age + type: Number +pages: + - components: + - settings: + type: BARCHART + dataSetLookup: + uuid: age + group: + - columnGroup: + source: Name + groupFunctions: + - source: Name + - source: Age diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dac30f69a52..ddf6f313063 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9221,6 +9221,21 @@ importers: specifier: ^5.9.0 version: 5.9.0 + packages/sonataflow-quarkus-devui-extension: + devDependencies: + "@kie-tools/maven-config-setup-helper": + specifier: workspace:* + version: link:../maven-config-setup-helper + "@kie-tools/root-env": + specifier: workspace:* + version: link:../root-env + "@kie-tools/serverless-workflow-dev-ui-webapp": + specifier: workspace:* + version: link:../serverless-workflow-dev-ui-webapp + run-script-os: + specifier: ^1.1.6 + version: 1.1.6 + packages/storybook-base: devDependencies: "@kie-tools-core/webpack-base": diff --git a/repo/graph.dot b/repo/graph.dot index 30d6dff5996..ec49e8db3f4 100644 --- a/repo/graph.dot +++ b/repo/graph.dot @@ -150,6 +150,7 @@ digraph G { "@kie-tools/serverless-workflow-service-catalog" [ color = "blue", fontcolor = "blue", style = "rounded" ]; "@kie-tools/serverless-workflow-language-service" [ color = "blue", fontcolor = "blue", style = "rounded" ]; "swf-vscode-extension" [ color = "blue", fontcolor = "blue", style = "rounded" ]; + "@kie-tools/sonataflow-quarkus-devui-extension" [ color = "black", fontcolor = "black", style = "dashed, rounded" ]; "@kie-tools/stunner-editors-dmn-loader" [ color = "blue", fontcolor = "blue", style = "rounded" ]; "@kie-tools/unitables" [ color = "blue", fontcolor = "blue", style = "rounded" ]; "vscode-extension-dashbuilder-editor" [ color = "blue", fontcolor = "blue", style = "rounded" ]; @@ -462,6 +463,7 @@ digraph G { "swf-vscode-extension" -> "@kie-tools/vscode-extension-common-test-helpers" [ style = "dashed", color = "blue" ]; "sonataflow-deployment-webapp" -> "@kie-tools-core/react-hooks" [ style = "dashed", color = "blue" ]; "sonataflow-deployment-webapp" -> "@kie-tools/runtime-tools-webapp-components" [ style = "dashed", color = "blue" ]; + "@kie-tools/sonataflow-quarkus-devui-extension" -> "@kie-tools/serverless-workflow-dev-ui-webapp" [ style = "dashed", color = "black" ]; "@kie-tools/storybook-base" -> "@kie-tools-core/webpack-base" [ style = "dashed", color = "blue" ]; "@kie-tools/storybook-base" -> "@kie-tools/tsconfig" [ style = "dashed", color = "blue" ]; "@kie-tools/stunner-editors" -> "@kie-tools/stunner-editors-dmn-loader" [ style = "solid", color = "black" ]; diff --git a/repo/graph.json b/repo/graph.json index 3440aac19f1..7014bcd673e 100644 --- a/repo/graph.json +++ b/repo/graph.json @@ -155,6 +155,7 @@ { "id": "@kie-tools/serverless-workflow-jq-expressions" }, { "id": "@kie-tools/serverless-workflow-service-catalog" }, { "id": "swf-vscode-extension" }, + { "id": "@kie-tools/sonataflow-quarkus-devui-extension" }, { "id": "vscode-extension-dashbuilder-editor" }, { "id": "vscode-extension-kie-ba-bundle" }, { "id": "vscode-extension-kogito-bundle" }, @@ -963,6 +964,11 @@ "weight": 1 }, { "source": "swf-vscode-extension", "target": "@kie-tools/vscode-extension-common-test-helpers", "weight": 1 }, + { + "source": "@kie-tools/sonataflow-quarkus-devui-extension", + "target": "@kie-tools/serverless-workflow-dev-ui-webapp", + "weight": 1 + }, { "source": "vscode-extension-dashbuilder-editor", "target": "@kie-tools-core/vscode-extension", "weight": 1 }, { "source": "vscode-extension-dashbuilder-editor", @@ -1141,6 +1147,7 @@ ["@kie-tools/serverless-workflow-text-editor", "packages/serverless-workflow-text-editor"], ["swf-vscode-extension", "packages/serverless-workflow-vscode-extension"], ["sonataflow-deployment-webapp", "packages/sonataflow-deployment-webapp"], + ["@kie-tools/sonataflow-quarkus-devui-extension", "packages/sonataflow-quarkus-devui-extension"], ["@kie-tools/storybook-base", "packages/storybook-base"], ["@kie-tools/stunner-editors", "packages/stunner-editors"], ["@kie-tools/stunner-editors-dmn-loader", "packages/stunner-editors-dmn-loader"],