diff --git a/designer/client/cypress/support/process.ts b/designer/client/cypress/support/process.ts index b493106f56a..aca566a4a44 100644 --- a/designer/client/cypress/support/process.ts +++ b/designer/client/cypress/support/process.ts @@ -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("POST", "/api/processValidation/*").as("fetch"); 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("POST", "/api/processValidation/*").as("fetch"); return cy.createTestFragment(name, fixture, category).then((processName) => { return cy.visitProcess(processName); }); diff --git a/designer/client/src/actions/nk/fetchVisualizationData.ts b/designer/client/src/actions/nk/fetchVisualizationData.ts index c73139b8d16..24994e36300 100644 --- a/designer/client/src/actions/nk/fetchVisualizationData.ts +++ b/designer/client/src/actions/nk/fetchVisualizationData.ts @@ -1,17 +1,27 @@ import { ThunkAction } from "../reduxTypes"; -import { displayCurrentProcessVersion } from "./process"; +import { displayTestCapabilities, fetchStickyNotesForScenario } 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) { diff --git a/designer/client/src/common/ProcessUtils.ts b/designer/client/src/common/ProcessUtils.ts index 2cbbe187297..c1e5770650f 100644 --- a/designer/client/src/common/ProcessUtils.ts +++ b/designer/client/src/common/ProcessUtils.ts @@ -48,9 +48,13 @@ class ProcessUtils { return isEmpty(scenario) ? false : !isEmpty(scenario.scenarioGraph.nodes); }; + isValidationResultPresent = (scenario: Scenario) => { + return Boolean(scenario.validationResult); + }; + //fixme maybe return hasErrors flag from backend? hasNeitherErrorsNorWarnings = (scenario: Scenario) => { - return this.hasNoErrors(scenario) && this.hasNoWarnings(scenario); + return this.isValidationResultPresent(scenario) && this.hasNoErrors(scenario) && this.hasNoWarnings(scenario); }; extractInvalidNodes = (invalidNodes: Pick) => { diff --git a/designer/client/src/components/tips/Tips.tsx b/designer/client/src/components/tips/Tips.tsx index 761003a66ab..331ec980a22 100644 --- a/designer/client/src/components/tips/Tips.tsx +++ b/designer/client/src/components/tips/Tips.tsx @@ -39,7 +39,11 @@ export default function Tips(props: ToolbarPanelProps): JSX.Element { renderThumbVertical={(props) =>
} hideTracksWhenNotNeeded={true} > - + {!ProcessUtils.hasNoErrors(scenario) && } {!ProcessUtils.hasNoWarnings(scenario) && ( + {loading && ( + + + + )} {hasNeitherErrorsNorWarnings && ( )} diff --git a/designer/client/src/components/toolbars/creator/ToolBox.tsx b/designer/client/src/components/toolbars/creator/ToolBox.tsx index f7217a53d02..a13981daecc 100644 --- a/designer/client/src/components/toolbars/creator/ToolBox.tsx +++ b/designer/client/src/components/toolbars/creator/ToolBox.tsx @@ -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); diff --git a/designer/client/src/components/toolbars/scenarioActions/buttons/DeployButton.tsx b/designer/client/src/components/toolbars/scenarioActions/buttons/DeployButton.tsx index a2868e533c4..df43ddc856d 100644 --- a/designer/client/src/components/toolbars/scenarioActions/buttons/DeployButton.tsx +++ b/designer/client/src/components/toolbars/scenarioActions/buttons/DeployButton.tsx @@ -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"; @@ -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.") diff --git a/designer/client/src/components/toolbars/scenarioActions/buttons/RunOffScheduleButton.tsx b/designer/client/src/components/toolbars/scenarioActions/buttons/RunOffScheduleButton.tsx index 4911d20d32a..e316456fc90 100644 --- a/designer/client/src/components/toolbars/scenarioActions/buttons/RunOffScheduleButton.tsx +++ b/designer/client/src/components/toolbars/scenarioActions/buttons/RunOffScheduleButton.tsx @@ -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"; @@ -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) => diff --git a/designer/client/src/http/HttpService.ts b/designer/client/src/http/HttpService.ts index 5aed923b1a7..5f4d887e26f 100644 --- a/designer/client/src/http/HttpService.ts +++ b/designer/client/src/http/HttpService.ts @@ -284,6 +284,14 @@ class HttpService { return api.get(url); } + fetchLatestProcessDetailsWithoutValidation(processName: ProcessName, versionId?: ProcessVersionId): Promise> { + const id = encodeURIComponent(processName); + const url = versionId + ? `/processes/${id}/${versionId}?skipValidateAndResolve=true` + : `/processes/${id}?skipValidateAndResolve=true`; + return api.get(url); + } + fetchProcessesStates() { return api .get("/processes/status") diff --git a/designer/client/src/reducers/selectors/graph.ts b/designer/client/src/reducers/selectors/graph.ts index 473f3251439..484fa6541ee 100644 --- a/designer/client/src/reducers/selectors/graph.ts +++ b/designer/client/src/reducers/selectors/graph.ts @@ -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)); @@ -77,8 +78,9 @@ export const getTestParameters = createSelector(getGraph, (g) => g.testFormParam 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[]), +export const getStickyNotes = createSelector( + [getGraph, getStickyNotesSettings], + (g, settings) => (settings?.enabled ? g.stickyNotes || ([] as StickyNote[]) : []) as StickyNote[], ); export const getShowRunProcessDetails = createSelector( [getTestResults, getProcessCounts], diff --git a/designer/client/src/reducers/selectors/settings.ts b/designer/client/src/reducers/selectors/settings.ts index 8177dc4d240..88e6e060c51 100644 --- a/designer/client/src/reducers/selectors/settings.ts +++ b/designer/client/src/reducers/selectors/settings.ts @@ -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)), diff --git a/docs/Changelog.md b/docs/Changelog.md index c3904f1ea4f..3d786265bcf 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -76,6 +76,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 * [#7522](https://github.com/TouK/nussknacker/pull/7522) Improved fetching UI Components: faster resolving of fragments, optimized db query for fetching fragments * [#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.