Skip to content

Commit

Permalink
Replace "run now" CustomAction with standard action PerformSingleExec…
Browse files Browse the repository at this point in the history
…ution (#7165)
  • Loading branch information
mgoworko authored and lciolecki committed Dec 7, 2024
1 parent 395f208 commit 5e982e3
Show file tree
Hide file tree
Showing 61 changed files with 687 additions and 399 deletions.
12 changes: 11 additions & 1 deletion designer/client/src/components/Process/ProcessStateUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PredefinedActionName, ProcessStateType, Scenario } from "./types";
import { ActionName, PredefinedActionName, ProcessStateType, Scenario } from "./types";
import {
descriptionProcessArchived,
descriptionFragment,
Expand All @@ -18,6 +18,12 @@ class ProcessStateUtils {

public canArchive = (state: ProcessStateType): boolean => state?.allowedActions.includes(PredefinedActionName.Archive);

public canSeePerformSingleExecution = (state: ProcessStateType): boolean =>
state?.visibleActions.includes(PredefinedActionName.PerformSingleExecution);

public canPerformSingleExecution = (state: ProcessStateType): boolean =>
state?.allowedActions.includes(PredefinedActionName.PerformSingleExecution);

getStateDescription({ isArchived, isFragment }: Scenario, processState: ProcessStateType): string {
if (isArchived) {
return isFragment ? descriptionFragmentArchived() : descriptionProcessArchived();
Expand Down Expand Up @@ -60,6 +66,10 @@ class ProcessStateUtils {
}
return `${name}-${processState?.icon || state?.icon || unknownIcon}`;
}

getActionCustomTooltip(processState: ProcessStateType, actionName: ActionName): string | undefined {
return processState?.actionTooltips[actionName] || undefined;
}
}

export default new ProcessStateUtils();
3 changes: 3 additions & 0 deletions designer/client/src/components/Process/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export enum PredefinedActionName {
Archive = "ARCHIVE",
UnArchive = "UNARCHIVE",
Pause = "PAUSE",
PerformSingleExecution = "PERFORM_SINGLE_EXECUTION",
}

export type ActionName = string;
Expand Down Expand Up @@ -69,7 +70,9 @@ export type ProcessName = Scenario["name"];
export type ProcessStateType = {
status: StatusType;
externalDeploymentId?: string;
visibleActions: Array<ActionName>;
allowedActions: Array<ActionName>;
actionTooltips: Record<ActionName, string>;
icon: string;
tooltip: string;
description: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,7 @@ export function ActionButton({ name, type }: ActionButtonProps): JSX.Element {
const customActions = useSelector(getCustomActions);
const action = useMemo(() => customActions.find((a) => a.name === name), [customActions, name]);

// FIXME: This part requires further changes within periodic scenario engine.
// Currently we use experimental api of custom actions for periodic scenarios (an experimental engine).
// Part of this experimental engine allows to run immediately scheduled scenario. This activity will be moved inside core deployment operations and aligned with other deployment engines.
// Here we want to disable that one action button in confusing situation when user looks at scenario version that is not currently deployed.
const isDeployed = useSelector(isDeployedVersion);
const disabledValue = useMemo(() => !isDeployed, [isDeployed, name]);

return action ? (
<CustomActionButton
action={action}
processName={processName}
processStatus={status}
disabled={name === "run now" ? disabledValue : false}
type={type}
/>
<CustomActionButton action={action} processName={processName} disabled={false} processStatus={status} type={type} />
) : null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export enum BuiltinButtonTypes {
processSave = "process-save",
processDeploy = "process-deploy",
processCancel = "process-cancel",
processPerformSingleExecution = "process-perform-single-execution",
editUndo = "edit-undo",
editRedo = "edit-redo",
editCopy = "edit-copy",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { ZoomOutButton } from "../../toolbars/view/buttons/ZoomOutButton";
import { BuiltinButtonTypes } from "./BuiltinButtonTypes";
import { CustomButtonTypes } from "./CustomButtonTypes";
import { ToolbarButton, ToolbarButtonTypes } from "./types";
import PerformSingleExecutionButton from "../../toolbars/scenarioActions/buttons/PerformSingleExecutionButton";

export type PropsOfButton<T> = ToolbarButton & {
type: T;
Expand All @@ -44,6 +45,7 @@ export const TOOLBAR_BUTTONS_MAP: ToolbarButtonsMap = {
[BuiltinButtonTypes.processSave]: SaveButton,
[BuiltinButtonTypes.processDeploy]: DeployButton,
[BuiltinButtonTypes.processCancel]: CancelDeployButton,
[BuiltinButtonTypes.processPerformSingleExecution]: PerformSingleExecutionButton,
[BuiltinButtonTypes.viewZoomIn]: ZoomInButton,
[BuiltinButtonTypes.viewZoomOut]: ZoomOutButton,
[BuiltinButtonTypes.viewReset]: ResetViewButton,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export function defaultToolbarsConfig(isFragment: boolean, isArchived: boolean):
{ type: BuiltinButtonTypes.processSave },
{ type: BuiltinButtonTypes.processDeploy },
{ type: BuiltinButtonTypes.processCancel },
{ type: BuiltinButtonTypes.processPerformSingleExecution },
],
},
{
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { useDispatch, useSelector } from "react-redux";
import { loadProcessState } from "../../../../actions/nk";
import Icon from "../../../../assets/img/toolbarButtons/perform-single-execution.svg";
import HttpService from "../../../../http/HttpService";
import { getProcessName, isPerformSingleExecutionPossible, isPerformSingleExecutionVisible } from "../../../../reducers/selectors/graph";
import { getCapabilities } from "../../../../reducers/selectors/other";
import { useWindows, WindowKind } from "../../../../windowManager";
import { ToggleProcessActionModalData } from "../../../modals/DeployProcessDialog";
import { ToolbarButton } from "../../../toolbarComponents/toolbarButtons";
import { ToolbarButtonProps } from "../../types";
import { ACTION_DIALOG_WIDTH } from "../../../../stylesheets/variables";
import ProcessStateUtils from "../../../Process/ProcessStateUtils";
import { RootState } from "../../../../reducers";
import { getProcessState } from "../../../../reducers/selectors/scenarioState";
import { PredefinedActionName } from "../../../Process/types";

export default function PerformSingleExecutionButton(props: ToolbarButtonProps) {
const { t } = useTranslation();
const dispatch = useDispatch();
const { disabled, type } = props;
const scenarioState = useSelector((state: RootState) => getProcessState(state));
const isVisible = useSelector(isPerformSingleExecutionVisible);
const isPossible = useSelector(isPerformSingleExecutionPossible);
const processName = useSelector(getProcessName);
const capabilities = useSelector(getCapabilities);
const available = !disabled && isPossible && capabilities.deploy;

const { open } = useWindows();
const action = (p, c) => HttpService.performSingleExecution(p, c).finally(() => dispatch(loadProcessState(processName)));
const message = t("panels.actions.perform-single-execution.dialog", "Perform single execution", { name: processName });

const defaultTooltip = t("panels.actions.perform-single-execution.tooltip", "run now");
const tooltip = ProcessStateUtils.getActionCustomTooltip(scenarioState, PredefinedActionName.PerformSingleExecution) ?? defaultTooltip;

if (isVisible) {
return (
<ToolbarButton
name={t("panels.actions.perform-single-execution.button", "run now")}
title={tooltip}
disabled={!available}
icon={<Icon />}
onClick={() =>
open<ToggleProcessActionModalData>({
title: message,
kind: WindowKind.deployProcess,
width: ACTION_DIALOG_WIDTH,
meta: { action },
})
}
type={type}
/>
);
} else return <></>;
}
3 changes: 3 additions & 0 deletions designer/client/src/containers/event-tracking/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ export const mapToolbarButtonToStatisticsEvent = (
case BuiltinButtonTypes.processCancel: {
return EventTrackingSelector.ScenarioCancel;
}
case BuiltinButtonTypes.processPerformSingleExecution: {
return EventTrackingSelector.ScenarioPerformSingleExecution;
}
case BuiltinButtonTypes.processArchiveToggle: {
return EventTrackingSelector.ScenarioArchiveToggle;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ enum ClickEventsSelector {
ScenarioSave = "SCENARIO_SAVE",
TestCounts = "TEST_COUNTS",
ScenarioCancel = "SCENARIO_CANCEL",
ScenarioPerformSingleExecution = "SCENARIO_PERFORM_SINGLE_EXECUTION",
ScenarioArchiveToggle = "SCENARIO_ARCHIVE_TOGGLE",
ScenarioUnarchive = "SCENARIO_UNARCHIVE",
ScenarioCustomAction = "SCENARIO_CUSTOM_ACTION",
Expand Down
27 changes: 26 additions & 1 deletion designer/client/src/http/HttpService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,31 @@ class HttpService {
});
}

performSingleExecution(processName: string, comment?: string) {
const data = {
comment: comment,
};
return api
.post(`/processManagement/performSingleExecution/${encodeURIComponent(processName)}`, data)
.then((res) => {
const msg = res.data.msg;
this.#addInfo(msg);
return {
isSuccess: res.data.isSuccess,
msg: msg,
};
})
.catch((error) => {
const msg = error.response.data.msg || error.response.data;
const result = {
isSuccess: false,
msg: msg,
};
if (error?.response?.status != 400) return this.#addError(msg, error, false).then(() => result);
return result;
});
}

customAction(processName: string, actionName: string, params: Record<string, unknown>, comment?: string) {
const data = {
actionName: actionName,
Expand Down Expand Up @@ -859,7 +884,7 @@ class HttpService {
fetchAllProcessDefinitionDataDicts(processingType: ProcessingType, refClazzName: string, type = "TypedClass") {
return api
.post<DictOption[]>(`/processDefinitionData/${processingType}/dicts`, {
expectedType: { type: type, refClazzName, params: [] },
expectedType: { type: type, refClazzName, params: [] },
})
.catch((error) =>
Promise.reject(
Expand Down
2 changes: 2 additions & 0 deletions designer/client/src/reducers/graph/utils.fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,9 @@ export const state: GraphState = {
name: "NOT_DEPLOYED",
},
version: null,
visibleActions: ["DEPLOY", "ARCHIVE", "RENAME"],
allowedActions: ["DEPLOY", "ARCHIVE", "RENAME"],
actionTooltips: {},
icon: "/assets/states/not-deployed.svg",
tooltip: "The scenario is not deployed.",
description: "The scenario is not deployed.",
Expand Down
9 changes: 8 additions & 1 deletion designer/client/src/reducers/selectors/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,18 @@ export const isDeployedVersion = createSelector(
[getProcessVersionId, createSelector(getScenario, (scenario) => scenario?.lastDeployedAction?.processVersionId)],
(visibleVersion, deployedVersion) => visibleVersion === deployedVersion,
);
export const isCancelPossible = createSelector(getProcessState, (state) => ProcessStateUtils.canCancel(state));
export const isPerformSingleExecutionVisible = createSelector([getProcessState], (state) =>
ProcessStateUtils.canSeePerformSingleExecution(state),
);
export const isPerformSingleExecutionPossible = createSelector(
[isSaveDisabled, hasError, getProcessState, isFragment],
(saveDisabled, error, state, fragment) => !fragment && saveDisabled && !error && ProcessStateUtils.canPerformSingleExecution(state),
);
export const isMigrationPossible = createSelector(
[isSaveDisabled, hasError, getProcessState, isFragment],
(saveDisabled, error, state, fragment) => saveDisabled && !error && (fragment || ProcessStateUtils.canDeploy(state)),
);
export const isCancelPossible = createSelector(getProcessState, (state) => ProcessStateUtils.canCancel(state));
export const isArchivePossible = createSelector(
[getProcessState, isFragment],
(state, isFragment) => isFragment || ProcessStateUtils.canArchive(state),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,7 @@ import pl.touk.nussknacker.engine.api.ProcessVersion
import pl.touk.nussknacker.engine.api.process.ProcessName
import pl.touk.nussknacker.engine.api.test.ScenarioTestData
import pl.touk.nussknacker.engine.canonicalgraph.CanonicalProcess
import pl.touk.nussknacker.engine.deployment.{
CustomActionResult,
DeploymentData,
DeploymentId,
ExternalDeploymentId,
User
}
import pl.touk.nussknacker.engine.deployment._
import pl.touk.nussknacker.engine.testmode.TestProcess.TestResults

// DM Prefix is from Deployment Manager, to distinguish from commands passed into the domain service layer (DeploymentService)
Expand Down Expand Up @@ -86,3 +80,9 @@ case class DMCancelScenarioCommand(scenarioName: ProcessName, user: User) extend

case class DMStopScenarioCommand(scenarioName: ProcessName, savepointDir: Option[String], user: User)
extends DMScenarioCommand[SavepointResult]

case class DMPerformSingleExecutionCommand(
processVersion: ProcessVersion,
canonicalProcess: CanonicalProcess,
user: User,
) extends DMScenarioCommand[SingleExecutionResult]
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package pl.touk.nussknacker.engine.api.deployment

import pl.touk.nussknacker.engine.api.deployment.inconsistency.InconsistentStateDetector
import pl.touk.nussknacker.engine.api.process.{ProcessIdWithName, ProcessName}
import pl.touk.nussknacker.engine.api.process.{ProcessIdWithName, ProcessName, VersionId}
import pl.touk.nussknacker.engine.deployment.CustomActionDefinition
import pl.touk.nussknacker.engine.newdeployment

Expand All @@ -14,10 +14,14 @@ trait DeploymentManagerInconsistentStateHandlerMixIn {
final override def resolve(
idWithName: ProcessIdWithName,
statusDetails: List[StatusDetails],
lastStateAction: Option[ProcessAction]
lastStateAction: Option[ProcessAction],
latestVersionId: VersionId,
deployedVersionId: Option[VersionId],
): Future[ProcessState] = {
val engineStateResolvedWithLastAction = flattenStatus(lastStateAction, statusDetails)
Future.successful(processStateDefinitionManager.processState(engineStateResolvedWithLastAction))
Future.successful(
processStateDefinitionManager.processState(engineStateResolvedWithLastAction, latestVersionId, deployedVersionId)
)
}

// This method is protected to make possible to override it with own logic handling different edge cases like
Expand All @@ -37,14 +41,23 @@ trait DeploymentManager extends AutoCloseable {

def processCommand[Result](command: DMScenarioCommand[Result]): Future[Result]

final def getProcessState(idWithName: ProcessIdWithName, lastStateAction: Option[ProcessAction])(
final def getProcessState(
idWithName: ProcessIdWithName,
lastStateAction: Option[ProcessAction],
latestVersionId: VersionId,
deployedVersionId: Option[VersionId],
)(
implicit freshnessPolicy: DataFreshnessPolicy
): Future[WithDataFreshnessStatus[ProcessState]] = {
for {
statusDetailsWithFreshness <- getProcessStates(idWithName.name)
stateWithFreshness <- resolve(idWithName, statusDetailsWithFreshness.value, lastStateAction).map(state =>
statusDetailsWithFreshness.map(_ => state)
)
stateWithFreshness <- resolve(
idWithName,
statusDetailsWithFreshness.value,
lastStateAction,
latestVersionId,
deployedVersionId
).map(state => statusDetailsWithFreshness.map(_ => state))
} yield stateWithFreshness
}

Expand All @@ -63,7 +76,9 @@ trait DeploymentManager extends AutoCloseable {
def resolve(
idWithName: ProcessIdWithName,
statusDetails: List[StatusDetails],
lastStateAction: Option[ProcessAction]
lastStateAction: Option[ProcessAction],
latestVersionId: VersionId,
deployedVersionId: Option[VersionId],
): Future[ProcessState]

def processStateDefinitionManager: ProcessStateDefinitionManager
Expand Down
Loading

0 comments on commit 5e982e3

Please sign in to comment.