Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scenario visualization loading improvements - part 1 - FE changes #7515

Merged
merged 10 commits into from
Feb 10, 2025
4 changes: 2 additions & 2 deletions designer/client/cypress/support/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,14 @@ function visitProcess(processName: string) {
}

function visitNewProcess(name?: string, fixture?: string, category?: string) {
cy.intercept("GET", "/api/processes/*").as("fetch");
cy.intercept("GET", /^\/api\/processes\/[^/]+$/).as("fetch");
Dzuming marked this conversation as resolved.
Show resolved Hide resolved
return cy.createTestProcess(name, fixture, category).then((processName) => {
return cy.visitProcess(processName);
});
}

function visitNewFragment(name?: string, fixture?: string, category?: string) {
cy.intercept("GET", "/api/processes/*").as("fetch");
cy.intercept("GET", /^\/api\/processes\/[^/]+$/).as("fetch");
return cy.createTestFragment(name, fixture, category).then((processName) => {
return cy.visitProcess(processName);
});
Expand Down
20 changes: 15 additions & 5 deletions designer/client/src/actions/nk/fetchVisualizationData.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import { ThunkAction } from "../reduxTypes";
import { displayCurrentProcessVersion } from "./process";
import { displayTestCapabilities, fetchStickyNotesForScenario, fetchValidatedProcess } from "./process";
import { fetchProcessDefinition } from "./processDefinitionData";
import { loadProcessToolbarsConfiguration } from "./loadProcessToolbarsConfiguration";
import { ProcessName } from "../../components/Process/types";
import HttpService from "../../http/HttpService";

export function fetchVisualizationData(processName: ProcessName, onSuccess: () => void, onError: (error) => void): ThunkAction {
return async (dispatch) => {
try {
const scenario = await dispatch(displayCurrentProcessVersion(processName));
dispatch({ type: "PROCESS_FETCH" });
const response = await HttpService.fetchLatestProcessDetailsWithoutValidation(processName);
const scenario = response.data;
const { name, isFragment, processingType } = scenario;
await dispatch(loadProcessToolbarsConfiguration(name));
const processDefinitionData = await dispatch(fetchProcessDefinition(processingType, isFragment));
dispatch({ type: "CORRECT_INVALID_SCENARIO", processDefinitionData });
await dispatch(fetchProcessDefinition(processingType, isFragment)).then((processDefinitionData) => {
dispatch({ type: "DISPLAY_PROCESS", scenario: scenario });
dispatch({ type: "CORRECT_INVALID_SCENARIO", processDefinitionData });
});
dispatch(loadProcessToolbarsConfiguration(name));
dispatch(displayTestCapabilities(name, scenario.scenarioGraph));
dispatch(fetchStickyNotesForScenario(name, scenario.processVersionId));
HttpService.validateProcess(name, name, scenario.scenarioGraph).then(({ data }) =>
dispatch({ type: "VALIDATION_RESULT", validationResult: data }),
);
onSuccess();
return scenario;
} catch (error) {
Expand Down
12 changes: 12 additions & 0 deletions designer/client/src/actions/nk/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ export function fetchProcessToDisplay(processName: ProcessName, versionId?: Proc
};
}

export function fetchValidatedProcess(processName: ProcessName, versionId?: ProcessVersionId): ThunkAction<Promise<Scenario>> {
Dzuming marked this conversation as resolved.
Show resolved Hide resolved
return (dispatch) => {
return HttpService.fetchProcessDetails(processName, versionId).then((response) => {
dispatch({
type: "DISPLAY_PROCESS",
scenario: response.data,
});
return response.data;
});
};
}

export function loadProcessState(processName: ProcessName, processVersionId: number): ThunkAction {
return (dispatch) =>
HttpService.fetchProcessState(processName, processVersionId).then(({ data }) =>
Expand Down
6 changes: 5 additions & 1 deletion designer/client/src/common/ProcessUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,13 @@ class ProcessUtils {
return isEmpty(scenario) ? false : !isEmpty(scenario.scenarioGraph.nodes);
};

isValidationResultPresent = (scenario: Scenario) => {
Dzuming marked this conversation as resolved.
Show resolved Hide resolved
return !!scenario.validationResult;
};

//fixme maybe return hasErrors flag from backend?
hasNeitherErrorsNorWarnings = (scenario: Scenario) => {
return this.hasNoErrors(scenario) && this.hasNoWarnings(scenario);
return !!scenario.validationResult && this.hasNoErrors(scenario) && this.hasNoWarnings(scenario);
mgoworko marked this conversation as resolved.
Show resolved Hide resolved
};

extractInvalidNodes = (invalidNodes: Pick<ValidationResult, "warnings">) => {
Expand Down
6 changes: 5 additions & 1 deletion designer/client/src/components/tips/Tips.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ export default function Tips(props: ToolbarPanelProps): JSX.Element {
renderThumbVertical={(props) => <div key={uuid4()} {...props} />}
hideTracksWhenNotNeeded={true}
>
<ValidTips testing={!!testResults} hasNeitherErrorsNorWarnings={ProcessUtils.hasNeitherErrorsNorWarnings(scenario)} />
<ValidTips
loading={!ProcessUtils.isValidationResultPresent(scenario)}
testing={!!testResults}
hasNeitherErrorsNorWarnings={ProcessUtils.hasNeitherErrorsNorWarnings(scenario)}
/>
{!ProcessUtils.hasNoErrors(scenario) && <Errors errors={errors} showDetails={showDetails} scenario={scenario} />}
{!ProcessUtils.hasNoWarnings(scenario) && (
<Warnings
Expand Down
8 changes: 6 additions & 2 deletions designer/client/src/components/tips/ValidTips.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import React from "react";
import TestingMode from "../../assets/img/icons/testingMode.svg";
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
import HourglassEmptyIcon from "@mui/icons-material/HourglassEmpty";
import ValidTip from "./ValidTip";
import { useTheme } from "@mui/material";

export default function ValidTips(props: { hasNeitherErrorsNorWarnings?: boolean; testing?: boolean }): JSX.Element {
const { hasNeitherErrorsNorWarnings, testing } = props;
export default function ValidTips(props: { loading: boolean; hasNeitherErrorsNorWarnings?: boolean; testing?: boolean }): JSX.Element {
const { loading, hasNeitherErrorsNorWarnings, testing } = props;
const theme = useTheme();

return (
<React.Fragment>
{loading && (
<ValidTip icon={HourglassEmptyIcon} message={"Verifying scenario and loading tips"} color={theme.palette.warning.main} />
Dzuming marked this conversation as resolved.
Show resolved Hide resolved
)}
{hasNeitherErrorsNorWarnings && (
<ValidTip icon={CheckCircleIcon} message={"Everything seems to be OK"} color={theme.palette.success.main} />
)}
Expand Down
4 changes: 2 additions & 2 deletions designer/client/src/components/toolbars/creator/ToolBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,9 @@ export default function ToolBox(props: ToolBoxProps): JSX.Element {
const pristine = useSelector(isPristine);
const { t } = useTranslation();

const componentGroups: ComponentGroup[] = useMemo(() => processDefinitionData.componentGroups, [processDefinitionData]);
const componentGroups: ComponentGroup[] = useMemo(() => processDefinitionData.componentGroups ?? [], [processDefinitionData]);
const filters = useMemo(() => props.filter?.toLowerCase().split(/\s/).filter(Boolean), [props.filter]);
const stickyNoteToolGroup = useMemo(() => stickyNoteComponentGroup(pristine), [pristine, props, t]);
const stickyNoteToolGroup = useMemo(() => stickyNoteComponentGroup(pristine), [pristine]);
const groups = useMemo(() => {
const allComponentGroups = stickyNotesSettings.enabled ? concat(componentGroups, stickyNoteToolGroup) : componentGroups;
return allComponentGroups.map(filterComponentsByLabel(filters)).filter((g) => g.components.length > 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ import { useDispatch, useSelector } from "react-redux";
import { disableToolTipsHighlight, enableToolTipsHighlight, loadProcessState } from "../../../../actions/nk";
import Icon from "../../../../assets/img/toolbarButtons/deploy.svg";
import HttpService from "../../../../http/HttpService";
import { getProcessName, hasError, isDeployPossible, isSaveDisabled } from "../../../../reducers/selectors/graph";
import {
getProcessName,
hasError,
isDeployPossible,
isSaveDisabled,
isValidationResultPresent,
} from "../../../../reducers/selectors/graph";
import { getCapabilities } from "../../../../reducers/selectors/other";
import { useWindows } from "../../../../windowManager";
import { WindowKind } from "../../../../windowManager";
Expand All @@ -19,11 +25,12 @@ export default function DeployButton(props: ToolbarButtonProps) {
const deployPossible = useSelector(isDeployPossible);
const saveDisabled = useSelector(isSaveDisabled);
const hasErrors = useSelector(hasError);
const validationResultPresent = useSelector(isValidationResultPresent);
const processName = useSelector(getProcessName);
const capabilities = useSelector(getCapabilities);
const { disabled, type } = props;

const available = !disabled && deployPossible && capabilities.deploy;
const available = validationResultPresent && !disabled && deployPossible && capabilities.deploy;
const { t } = useTranslation();
const deployToolTip = !capabilities.deploy
? t("panels.actions.deploy.tooltips.forbidden", "Deploy forbidden for current scenario.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import { useDispatch, useSelector } from "react-redux";
import { loadProcessState } from "../../../../actions/nk";
import Icon from "../../../../assets/img/toolbarButtons/run-off-schedule.svg";
import HttpService from "../../../../http/HttpService";
import { getProcessName, isRunOffSchedulePossible, isRunOffScheduleVisible } from "../../../../reducers/selectors/graph";
import {
getProcessName,
isRunOffSchedulePossible,
isRunOffScheduleVisible,
isValidationResultPresent,
} from "../../../../reducers/selectors/graph";
import { getCapabilities } from "../../../../reducers/selectors/other";
import { useWindows, WindowKind } from "../../../../windowManager";
import { ToggleProcessActionModalData } from "../../../modals/DeployProcessDialog";
Expand All @@ -21,11 +26,12 @@ export default function RunOffScheduleButton(props: ToolbarButtonProps) {
const dispatch = useDispatch();
const { disabled, type } = props;
const scenarioState = useSelector((state: RootState) => getProcessState(state));
const validationResultPresent = useSelector(isValidationResultPresent);
const isVisible = useSelector(isRunOffScheduleVisible);
const isPossible = useSelector(isRunOffSchedulePossible);
const processName = useSelector(getProcessName);
const capabilities = useSelector(getCapabilities);
const available = !disabled && isPossible && capabilities.deploy;
const available = validationResultPresent && !disabled && isPossible && capabilities.deploy;

const { open } = useWindows();
const action = (name: ProcessName, versionId: ProcessVersionId, comment: string) =>
Expand Down
8 changes: 8 additions & 0 deletions designer/client/src/http/HttpService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,14 @@ class HttpService {
return api.get<Scenario>(url);
}

fetchLatestProcessDetailsWithoutValidation(processName: ProcessName, versionId?: ProcessVersionId): Promise<AxiosResponse<Scenario>> {
const id = encodeURIComponent(processName);
const url = versionId
? `/processes/${id}/${versionId}?skipValidateAndResolve=true`
: `/processes/${id}?skipValidateAndResolve=true`;
return api.get<Scenario>(url);
}

fetchProcessesStates() {
return api
.get<StatusesType>("/processes/status")
Expand Down
3 changes: 2 additions & 1 deletion designer/client/src/reducers/selectors/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const isLatestProcessVersion = createSelector(getScenario, (d) => d?.isLa
export const isFragment = createSelector(getScenario, (p) => p?.isFragment);
export const isArchived = createSelector(getScenario, (p) => p?.isArchived);
export const isPristine = (state: RootState): boolean => ProcessUtils.nothingToSave(state) && !isProcessRenamed(state);
export const isValidationResultPresent = createSelector(getScenario, (p) => ProcessUtils.isValidationResultPresent(p));
export const hasError = createSelector(getScenario, (p) => !ProcessUtils.hasNoErrors(p));
export const hasWarnings = createSelector(getScenario, (p) => !ProcessUtils.hasNoWarnings(p));
export const hasPropertiesErrors = createSelector(getScenario, (p) => !ProcessUtils.hasNoPropertiesErrors(p));
Expand Down Expand Up @@ -78,7 +79,7 @@ export const getTestResults = createSelector(getGraph, (g) => g.testResults);
export const getProcessCountsRefresh = createSelector(getGraph, (g) => g.processCountsRefresh || null);
export const getProcessCounts = createSelector(getGraph, (g): ProcessCounts => g.processCounts || ({} as ProcessCounts));
export const getStickyNotes = createSelector([getGraph, getStickyNotesSettings], (g, settings) =>
settings.enabled ? g.stickyNotes || ([] as StickyNote[]) : ([] as StickyNote[]),
(settings ? settings.enabled : false) ? g.stickyNotes || ([] as StickyNote[]) : ([] as StickyNote[]),
mgoworko marked this conversation as resolved.
Show resolved Hide resolved
);
export const getShowRunProcessDetails = createSelector(
[getTestResults, getProcessCounts],
Expand Down
2 changes: 1 addition & 1 deletion designer/client/src/reducers/selectors/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const getProcessDefinitionData = createDeepEqualSelector(
getSettings,
(s) => s.processDefinitionData || ({} as ProcessDefinitionData),
);
export const getComponentGroups = createSelector(getProcessDefinitionData, (p) => p.componentGroups || ({} as ComponentGroup[]));
export const getComponentGroups = createSelector(getProcessDefinitionData, (p) => p.componentGroups || []);
export const getCategories = createSelector(getLoggedUser, (u) => u.categories || []);
export const getWritableCategories = createSelector(getLoggedUser, getCategories, (user, categories) =>
categories.filter((c) => user.canWrite(c)),
Expand Down
1 change: 1 addition & 0 deletions docs/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
* For all - default `windowLength` is 1 hour
* For `aggregate-session` - default `endSessionCondition` is now false
* Improved scenario visualization loading time
* [#7453](https://github.com/TouK/nussknacker/pull/7453) optimized and rearranged API calls and GUI loading order
* [#7516](https://github.com/TouK/nussknacker/pull/7516) Scenario testing endpoints no longer perform full scenario compilation and validation
* [#7524](https://github.com/TouK/nussknacker/pull/7524) Add a possibility to choose a new valid value in node details when inconsistencies in parameter's definition were detected.
* [#7511](https://github.com/TouK/nussknacker/pull/7511) `flink-components-testkit` rework: easier `ScenarioTestRunner` creation - see [Migration guide](MigrationGuide.md) for details
Expand Down