From 9ea15824173a82118166a03bef1da643e6be52be Mon Sep 17 00:00:00 2001 From: Julian Haupt Date: Tue, 16 Jun 2026 22:43:31 +0200 Subject: [PATCH] deployments: add redeploy action --- public/locales/de-DE/translation.json | 3 +++ public/locales/en-US/translation.json | 3 +++ src/api/resources/deployment.js | 7 +++++-- src/api/resources/deployment.test.js | 13 ++++++++++++- src/pages/Deployments.jsx | 27 ++++++++++++++++++++++++++- src/pages/Deployments.test.jsx | 20 +++++++++++++++++++- src/state.js | 1 + 7 files changed, 69 insertions(+), 5 deletions(-) diff --git a/public/locales/de-DE/translation.json b/public/locales/de-DE/translation.json index 5f7d6a9..e52e2b0 100644 --- a/public/locales/de-DE/translation.json +++ b/public/locales/de-DE/translation.json @@ -440,6 +440,9 @@ "empty-response": "Leere Antwort", "form-preview": "Formularvorschau", "raw-data-json": "Rohdaten (JSON)", + "actions": "Deployment-Aktionen", + "redeploy": "Erneut deployen", + "redeploy-success": "Deployment erneut bereitgestellt.", "sort": { "deploymentTime": "Deployment-Zeit", "name": "Name", diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index ac20fbe..131873b 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -440,6 +440,9 @@ "empty-response": "Empty response", "form-preview": "Form preview", "raw-data-json": "Raw data (JSON)", + "actions": "Deployment Actions", + "redeploy": "Redeploy", + "redeploy-success": "Deployment redeployed.", "sort": { "deploymentTime": "Deployment time", "name": "Name", diff --git a/src/api/resources/deployment.js b/src/api/resources/deployment.js index 206d8ed..fb53e7e 100644 --- a/src/api/resources/deployment.js +++ b/src/api/resources/deployment.js @@ -1,4 +1,4 @@ -import { GET, DELETE, GET_TEXT } from '../helper.jsx' +import { GET, DELETE, GET_TEXT, POST } from '../helper.jsx' /** * Fetches deployments sorted by deployment time, sets the first as selected @@ -33,12 +33,15 @@ const get_deployment_resource = (state, deployment_id, resource_id) => const delete_deployment = (state, deployment_id, params = {}) => DELETE(`/deployment/${deployment_id}?${new URLSearchParams(params).toString()}`, null, state, state.api.deployment.delete) +const redeploy_deployment = (state, deployment_id) => + POST(`/deployment/${deployment_id}/redeploy`, {}, state, state.api.deployment.redeploy) const deployment = { all: get_deployments, resources: get_deployment_resources, resource: get_deployment_resource, - delete: delete_deployment + delete: delete_deployment, + redeploy: redeploy_deployment, } export default deployment diff --git a/src/api/resources/deployment.test.js b/src/api/resources/deployment.test.js index 66313d3..6ae46ea 100644 --- a/src/api/resources/deployment.test.js +++ b/src/api/resources/deployment.test.js @@ -4,9 +4,10 @@ vi.mock("../helper.jsx", () => ({ GET: vi.fn(), DELETE: vi.fn(), GET_TEXT: vi.fn(), + POST: vi.fn(), })); -import { GET, DELETE, GET_TEXT } from "../helper.jsx"; +import { GET, DELETE, GET_TEXT, POST } from "../helper.jsx"; import { create_mock_state, expect_api_call } from "../../test/helpers.js"; import deployment from "./deployment.js"; @@ -65,4 +66,14 @@ describe("api/resources/deployment", () => { signal: state.api.deployment.delete, }); }); + + it("redeploy() POSTs the selected deployment", () => { + deployment.redeploy(state, "dep-1"); + expect_api_call(POST, { + url: "/deployment/dep-1/redeploy", + body: {}, + state, + signal: state.api.deployment.redeploy, + }); + }); }); diff --git a/src/pages/Deployments.jsx b/src/pages/Deployments.jsx index 6ed1b25..6f5764a 100644 --- a/src/pages/Deployments.jsx +++ b/src/pages/Deployments.jsx @@ -216,7 +216,8 @@ const DeploymentsList = () => { const ResourcesList = () => { const state = useContext(AppState), - { params } = useRoute(), + { params, query } = useRoute(), + { route } = useLocation(), [t] = useTranslation(); if (!params.deployment_id) { @@ -227,6 +228,30 @@ const ResourcesList = () => { return (
+
+

{t("deployments.actions")}

+ null} + on_success={() =>

{t("deployments.redeploy-success")}

} + /> +
+ +
+
diff --git a/src/pages/Deployments.test.jsx b/src/pages/Deployments.test.jsx index f8d536e..9a8a047 100644 --- a/src/pages/Deployments.test.jsx +++ b/src/pages/Deployments.test.jsx @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { h } from "preact"; -import { render, cleanup } from "@testing-library/preact"; +import { render, cleanup, fireEvent } from "@testing-library/preact"; // Spy all engine_rest API functions but keep RequestState/RESPONSE_STATE real. vi.mock("../api/engine_rest.jsx", async (importOriginal) => { @@ -101,6 +101,24 @@ describe("DeploymentsPage", () => { expect(link.getAttribute("href")).toBe("/deployments/dep1/process.bpmn"); }); + it("redeploys the active deployment", async () => { + mockParams = { deployment_id: "dep1" }; + engine_rest.deployment.redeploy.mockImplementationOnce(() => { + signal_response(state.api.deployment.redeploy, { id: "dep2" }); + return Promise.resolve(state.api.deployment.redeploy.value); + }); + const { getByText } = renderPage(state); + + fireEvent.click(getByText("deployments.redeploy")); + await Promise.resolve(); + + expect(engine_rest.deployment.redeploy).toHaveBeenCalled(); + expect(engine_rest.deployment.redeploy.mock.lastCall[0]).toBe(state); + expect(engine_rest.deployment.redeploy.mock.lastCall[1]).toBe("dep1"); + expect(engine_rest.deployment.all).toHaveBeenCalled(); + expect(routeFn).toHaveBeenCalledWith("/deployments/dep2", true); + }); + it("fetches resource content + definition + instance count when a resource is selected", () => { mockParams = { deployment_id: "dep1", resource_name: "process.bpmn" }; signal_response(state.api.deployment.resources, [ diff --git a/src/state.js b/src/state.js index f63ad2e..5d8ca18 100644 --- a/src/state.js +++ b/src/state.js @@ -158,6 +158,7 @@ const createAppState = () => { resources: signal(null), resource: signal(null), delete: signal(null), + redeploy: signal(null), saved_filters: signal(null), }, decision: {