diff --git a/designer/client/src/components/AddProcessDialog.tsx b/designer/client/src/components/AddProcessDialog.tsx index 060ad6a7a51..f947318e4eb 100644 --- a/designer/client/src/components/AddProcessDialog.tsx +++ b/designer/client/src/components/AddProcessDialog.tsx @@ -1,9 +1,9 @@ import { WindowButtonProps, WindowContentProps } from "@touk/window-manager"; -import React, { useCallback, useEffect, useMemo, useState } from "react"; +import React, { useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { visualizationUrl } from "../common/VisualizationUrl"; import { useProcessNameValidators } from "../containers/hooks/useProcessNameValidators"; -import HttpService, { ProcessingMode, ScenarioParametersCombination } from "../http/HttpService"; +import HttpService, { ProcessingMode } from "../http/HttpService"; import { WindowContent } from "../windowManager"; import { AddProcessForm, FormValue, TouchedValue } from "./AddProcessForm"; import { extendErrors, mandatoryValueValidator } from "./graph/node-modal/editors/Validators"; @@ -12,6 +12,7 @@ import { NodeValidationError } from "../types"; import { flow, isEmpty, transform } from "lodash"; import { useProcessFormDataOptions } from "./useProcessFormDataOptions"; import { LoadingButtonTypes } from "../windowManager/LoadingButton"; +import { useGetAllCombinations } from "./useGetAllCombinations"; interface AddProcessDialogProps extends WindowContentProps { isFragment?: boolean; @@ -22,7 +23,12 @@ export function AddProcessDialog(props: AddProcessDialogProps): JSX.Element { const { t } = useTranslation(); const { isFragment = false, errors = [], ...passProps } = props; const nameValidators = useProcessNameValidators(); - const [value, setState] = useState({ processName: "", processCategory: "", processingMode: "", processEngine: "" }); + const [value, setState] = useState({ + processName: "", + processCategory: "", + processingMode: "" as ProcessingMode, + processEngine: "", + }); const [touched, setTouched] = useState({ processName: false, processCategory: false, @@ -30,8 +36,13 @@ export function AddProcessDialog(props: AddProcessDialogProps): JSX.Element { processEngine: false, }); const [processNameFromBackend, setProcessNameFromBackendError] = useState([]); - const [engineSetupErrors, setEngineSetupErrors] = useState>({}); - const [allCombinations, setAllCombinations] = useState([]); + + const { engineSetupErrors, allCombinations } = useGetAllCombinations({ + processCategory: value.processCategory, + processingMode: value.processingMode, + processEngine: value.processEngine, + }); + const engineErrors: NodeValidationError[] = (engineSetupErrors[value.processEngine] ?? []).map((error) => ({ fieldName: "processEngine", errorType: "SaveNotAllowed", @@ -122,12 +133,6 @@ export function AddProcessDialog(props: AddProcessDialogProps): JSX.Element { setTouched(touched); }; - useEffect(() => { - HttpService.fetchScenarioParametersCombinations().then((response) => { - setAllCombinations(response.data.combinations); - setEngineSetupErrors(response.data.engineSetupErrors); - }); - }, []); return ( ; diff --git a/designer/client/src/components/modals/MoreScenarioDetailsDialog.tsx b/designer/client/src/components/modals/MoreScenarioDetailsDialog.tsx index 7a357bfa6ac..8180e85a81a 100644 --- a/designer/client/src/components/modals/MoreScenarioDetailsDialog.tsx +++ b/designer/client/src/components/modals/MoreScenarioDetailsDialog.tsx @@ -1,4 +1,4 @@ -import { Box, styled, Typography } from "@mui/material"; +import { Box, Skeleton, styled, Typography } from "@mui/material"; import { WindowButtonProps, WindowContentProps } from "@touk/window-manager"; import React, { useMemo } from "react"; import { useTranslation } from "react-i18next"; @@ -10,6 +10,8 @@ import i18next from "i18next"; import { capitalize, startCase } from "lodash"; import { getProcessingModeVariantName } from "../toolbars/scenarioDetails/getProcessingModeVariantName"; import NuLogoIcon from "../../assets/img/nussknacker-logo-icon.svg"; +import { useGetAllCombinations } from "../useGetAllCombinations"; +import LoaderSpinner from "../spinner/Spinner"; const ItemWrapperStyled = styled("div")({ display: "grid", gridAutoColumns: "minmax(0, 1fr)", gridAutoFlow: "column" }); @@ -38,10 +40,19 @@ function MoreScenarioDetailsDialog(props: WindowContentProps) ], [props, t], ); + const { isCategoryFieldVisible, isAllCombinationsLoading } = useGetAllCombinations({ + processCategory: scenario.processCategory, + processingMode: scenario.processingMode, + processEngine: scenario.engineSetupName, + }); const displayStatus = !scenario.isArchived && !scenario.isFragment; const displayLabels = scenario.labels.length !== 0; + if (isAllCombinationsLoading) { + return ; + } + return ( ) {i18next.t("scenarioDetails.label.processingMode", "Processing mode")} {getProcessingModeVariantName(scenario.processingMode)} - - {i18next.t("scenarioDetails.label.category", "Category")} - {scenario.processCategory} - + {isCategoryFieldVisible && ( + + {i18next.t("scenarioDetails.label.category", "Category")} + {scenario.processCategory} + + )} {i18next.t("scenarioDetails.label.engine", "Engine")} {scenario.engineSetupName} diff --git a/designer/client/src/components/toolbars/scenarioDetails/CategoryDetails.tsx b/designer/client/src/components/toolbars/scenarioDetails/CategoryDetails.tsx index 07be4f6473d..340644d1985 100644 --- a/designer/client/src/components/toolbars/scenarioDetails/CategoryDetails.tsx +++ b/designer/client/src/components/toolbars/scenarioDetails/CategoryDetails.tsx @@ -1,35 +1,17 @@ -import React, { useEffect, useState } from "react"; -import { useProcessFormDataOptions } from "../../useProcessFormDataOptions"; -import HttpService, { ScenarioParametersCombination } from "../../../http/HttpService"; +import React from "react"; import { Skeleton, Typography } from "@mui/material"; import { Scenario } from "../../Process/types"; +import { useGetAllCombinations } from "../../useGetAllCombinations"; import { useTranslation } from "react-i18next"; export const CategoryDetails = ({ scenario }: { scenario: Scenario }) => { const { t } = useTranslation(); - const [allCombinations, setAllCombinations] = useState([]); - const [isAllCombinationsLoading, setIsAllCombinationsLoading] = useState(false); - - const { isCategoryFieldVisible } = useProcessFormDataOptions({ - allCombinations, - value: { - processCategory: scenario.processCategory, - processingMode: scenario.processingMode, - processEngine: scenario.engineSetupName, - }, + const { isAllCombinationsLoading, isCategoryFieldVisible } = useGetAllCombinations({ + processCategory: scenario.processCategory, + processingMode: scenario.processingMode, + processEngine: scenario.engineSetupName, }); - useEffect(() => { - setIsAllCombinationsLoading(true); - HttpService.fetchScenarioParametersCombinations() - .then((response) => { - setAllCombinations(response.data.combinations); - }) - .finally(() => { - setIsAllCombinationsLoading(false); - }); - }, []); - return ( <> {isAllCombinationsLoading ? ( diff --git a/designer/client/src/components/useGetAllCombinations.ts b/designer/client/src/components/useGetAllCombinations.ts new file mode 100644 index 00000000000..7663c0a18fd --- /dev/null +++ b/designer/client/src/components/useGetAllCombinations.ts @@ -0,0 +1,37 @@ +import { useEffect, useState } from "react"; +import HttpService, { ProcessingMode, ScenarioParametersCombination } from "../http/HttpService"; +import { useProcessFormDataOptions } from "./useProcessFormDataOptions"; + +interface Props { + processCategory: string; + processingMode: ProcessingMode; + processEngine: string; +} +export const useGetAllCombinations = ({ processCategory, processingMode, processEngine }: Props) => { + const [allCombinations, setAllCombinations] = useState([]); + const [engineSetupErrors, setEngineSetupErrors] = useState>({}); + const [isAllCombinationsLoading, setIsAllCombinationsLoading] = useState(false); + + const { isCategoryFieldVisible } = useProcessFormDataOptions({ + allCombinations, + value: { + processCategory, + processingMode, + processEngine, + }, + }); + + useEffect(() => { + setIsAllCombinationsLoading(true); + HttpService.fetchScenarioParametersCombinations() + .then((response) => { + setAllCombinations(response.data.combinations); + setEngineSetupErrors(response.data.engineSetupErrors); + }) + .finally(() => { + setIsAllCombinationsLoading(false); + }); + }, []); + + return { allCombinations, isAllCombinationsLoading, isCategoryFieldVisible, engineSetupErrors }; +}; diff --git a/designer/submodules/packages/components/src/scenarios/filters/filtersPart.tsx b/designer/submodules/packages/components/src/scenarios/filters/filtersPart.tsx index c8aa2c2f6fd..65465546296 100644 --- a/designer/submodules/packages/components/src/scenarios/filters/filtersPart.tsx +++ b/designer/submodules/packages/components/src/scenarios/filters/filtersPart.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useMemo } from "react"; import { flatten, sortBy, uniq } from "lodash"; import { useFilterContext } from "../../common"; import { ScenariosFiltersModel, ScenariosFiltersModelType } from "./scenariosFiltersModel"; -import { useScenarioLabelsQuery, useStatusDefinitions, useUserQuery } from "../useScenariosQuery"; +import { useScenarioLabelsQuery, useScenariosWithCategoryVisible, useStatusDefinitions, useUserQuery } from "../useScenariosQuery"; import { QuickFilter } from "./quickFilter"; import { FilterMenu } from "./filterMenu"; import { SimpleOptionsStack } from "./simpleOptionsStack"; @@ -21,6 +21,7 @@ export function FiltersPart({ withSort, isLoading, data = [] }: { data: RowType[ const { data: userData } = useUserQuery(); const { data: statusDefinitions = [] } = useStatusDefinitions(); const { data: availableLabels } = useScenarioLabelsQuery(); + const { withCategoriesVisible } = useScenariosWithCategoryVisible(); const filterableKeys = useMemo(() => ["createdBy", "modifiedBy"], []); const filterableValues = useMemo(() => { @@ -35,7 +36,7 @@ export function FiltersPart({ withSort, isLoading, data = [] }: { data: RowType[ label: (availableLabels?.labels || []).map((name) => ({ name })), processingMode: processingModeItems, }; - }, [data, filterableKeys, statusDefinitions, userData?.categories]); + }, [availableLabels?.labels, data, filterableKeys, statusDefinitions, userData?.categories]); const statusFilterLabels = statusDefinitions.reduce((map, obj) => { map[obj.name] = obj.displayableName; @@ -102,17 +103,19 @@ export function FiltersPart({ withSort, isLoading, data = [] }: { data: RowType[ })} /> - - - + {withCategoriesVisible && ( + + + + )} (); + const { withCategoriesVisible } = useScenariosWithCategoryVisible(); return (
- - / + {withCategoriesVisible && ( + <> + + / + + )} diff --git a/designer/submodules/packages/components/src/scenarios/list/tablePart.tsx b/designer/submodules/packages/components/src/scenarios/list/tablePart.tsx index e5d27fad18b..fd01fa8ffe8 100644 --- a/designer/submodules/packages/components/src/scenarios/list/tablePart.tsx +++ b/designer/submodules/packages/components/src/scenarios/list/tablePart.tsx @@ -11,6 +11,7 @@ import AssessmentIcon from "@mui/icons-material/Assessment"; import { LastAction } from "./item"; import { getEventTrackingProps, EventTrackingSelector } from "nussknackerUi/eventTracking"; import { formatDateTime } from "nussknackerUi/DateUtils"; +import { useScenariosWithCategoryVisible } from "../useScenariosQuery"; export function TablePart(props: ListPartProps): JSX.Element { const { data = [], isLoading } = props; @@ -18,9 +19,10 @@ export function TablePart(props: ListPartProps): JSX.Element { const filtersContext = useFilterContext(); const _filterText = useMemo(() => filtersContext.getFilter("NAME"), [filtersContext]); const [filterText] = useDebouncedValue(_filterText, 400); + const { withCategoriesVisible } = useScenariosWithCategoryVisible(); - const columns = useMemo( - (): Columns => [ + const columns = useMemo((): Columns => { + const availableColumns: Columns = [ { field: "id", cellClassName: "noPadding stretch", @@ -31,13 +33,15 @@ export function TablePart(props: ListPartProps): JSX.Element { minWidth: 200, flex: 2, }, - { - field: "processCategory", - cellClassName: "noPadding stretch", - headerName: t("table.scenarios.title.PROCESS_CATEGORY", "Category"), - renderCell: (props) => filterKey="CATEGORY" {...props} />, - flex: 1, - }, + withCategoriesVisible + ? { + field: "processCategory", + cellClassName: "noPadding stretch", + headerName: t("table.scenarios.title.PROCESS_CATEGORY", "Category"), + renderCell: (props) => filterKey="CATEGORY" {...props} />, + flex: 1, + } + : undefined, { field: "createdBy", cellClassName: "noPadding stretch", @@ -107,9 +111,10 @@ export function TablePart(props: ListPartProps): JSX.Element { sortable: false, align: "center", }, - ], - [filterText, t], - ); + ]; + + return availableColumns.filter((data) => data !== undefined); + }, [filterText, t, withCategoriesVisible]); const [visibleColumns, setVisibleColumns] = useState( columns.reduce((previousValue, currentValue) => { diff --git a/designer/submodules/packages/components/src/scenarios/useScenariosQuery.tsx b/designer/submodules/packages/components/src/scenarios/useScenariosQuery.tsx index ac58e5f0773..84171a70822 100644 --- a/designer/submodules/packages/components/src/scenarios/useScenariosQuery.tsx +++ b/designer/submodules/packages/components/src/scenarios/useScenariosQuery.tsx @@ -2,13 +2,15 @@ import { UserData } from "nussknackerUi/common/models/User"; import { useContext, useEffect, useMemo } from "react"; import { NkApiContext } from "../settings/nkApiProvider"; import { Scenario, StatusDefinitionType } from "nussknackerUi/components/Process/types"; -import { StatusesType } from "nussknackerUi/HttpService"; +import { ScenarioParametersCombinations, StatusesType } from "nussknackerUi/HttpService"; import { useQuery, useQueryClient } from "react-query"; import { AvailableScenarioLabels } from "nussknackerUi/components/Labels/types"; import { UseQueryResult } from "react-query/types/react/types"; import { DateTime } from "luxon"; +import { groupBy } from "lodash"; const scenarioStatusesQueryKey = "scenariosStatuses"; +const scenarioParametersCombinationsQueryKey = "scenarioParametersCombinations"; function useScenariosQuery(): UseQueryResult { const api = useContext(NkApiContext); @@ -53,6 +55,22 @@ export function useScenariosStatusesQuery(): UseQueryResult { }); } +export function useScenarioParametersCombinationsQuery(): UseQueryResult { + const api = useContext(NkApiContext); + return useQuery({ + queryKey: [scenarioParametersCombinationsQueryKey], + queryFn: async () => { + const { data } = await api.fetchScenarioParametersCombinations(); + return data; + }, + enabled: !!api, + refetchInterval: 15000, + // We have to define staleTime because we set cache manually via queryClient.setQueryData during fetching scenario + // details (because we want to avoid unnecessary refetch) + staleTime: 10000, + }); +} + export function useStatusDefinitions(): UseQueryResult { const api = useContext(NkApiContext); return useQuery({ @@ -102,8 +120,22 @@ export function useScenariosWithStatus(): UseQueryResult { data: data.map((scenario) => ({ ...scenario, state: statuses?.data?.[scenario.name] || scenario.state, - id: scenario.name, // required by DataGrid when table=true + id: scenario.name, // required by DataGrid when table=true, })), } as UseQueryResult; }, [scenarios, statuses]); } + +export function useScenariosWithCategoryVisible(): { withCategoriesVisible: boolean } { + const parametersCombinations = useScenarioParametersCombinationsQuery(); + return useMemo(() => { + const { data } = parametersCombinations; + const combinations = data?.combinations || []; + + const withCategoriesVisible = Object.keys(groupBy(combinations, (combination) => combination.category)).length > 1; + + return { + withCategoriesVisible, + }; + }, [parametersCombinations]); +} diff --git a/docs/Changelog.md b/docs/Changelog.md index 9b508a0a28b..ca669c9f43a 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -105,6 +105,7 @@ * [#7159](https://github.com/TouK/nussknacker/pull/7159) Fix running scenario tests with provided fragment input validation * [#7193](https://github.com/TouK/nussknacker/pull/7193) Provide tooltips to the scenarios list and scenario details elements * [#7187](https://github.com/TouK/nussknacker/pull/7187) Fix "Failed to get node validation" when using literal lists that mixes different types of elements +* [#7183](https://github.com/TouK/nussknacker/pull/7183) Hide categories from a scenarios list and more scenario details when only one category is available * [#7192](https://github.com/TouK/nussknacker/pull/7192) Fix "Failed to get node validation" when opening node details referencing non-existing component ## 1.17