Skip to content

Commit

Permalink
[Nu-1890] hide categories from a scenarios list and more scenario det…
Browse files Browse the repository at this point in the history
…ails when only one category is available (#7183)

* NU-1890 hide categories from a scenarios list and more scenario details when only one category is available
  • Loading branch information
Dzuming authored Nov 21, 2024
1 parent 6ff45eb commit b89a0f3
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 70 deletions.
27 changes: 16 additions & 11 deletions designer/client/src/components/AddProcessDialog.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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;
Expand All @@ -22,16 +23,26 @@ export function AddProcessDialog(props: AddProcessDialogProps): JSX.Element {
const { t } = useTranslation();
const { isFragment = false, errors = [], ...passProps } = props;
const nameValidators = useProcessNameValidators();
const [value, setState] = useState<FormValue>({ processName: "", processCategory: "", processingMode: "", processEngine: "" });
const [value, setState] = useState<FormValue>({
processName: "",
processCategory: "",
processingMode: "" as ProcessingMode,
processEngine: "",
});
const [touched, setTouched] = useState<TouchedValue>({
processName: false,
processCategory: false,
processingMode: false,
processEngine: false,
});
const [processNameFromBackend, setProcessNameFromBackendError] = useState<NodeValidationError[]>([]);
const [engineSetupErrors, setEngineSetupErrors] = useState<Record<string, string[]>>({});
const [allCombinations, setAllCombinations] = useState<ScenarioParametersCombination[]>([]);

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",
Expand Down Expand Up @@ -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 (
<WindowContent buttons={buttons} {...passProps}>
<AddProcessForm
Expand Down
2 changes: 1 addition & 1 deletion designer/client/src/components/AddProcessForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { InfoOutlined } from "@mui/icons-material";
import Input from "./graph/node-modal/editors/field/Input";
import { formLabelWidth } from "../containers/theme/styles";

export type FormValue = { processName: string; processCategory: string; processingMode: string; processEngine: string };
export type FormValue = { processName: string; processCategory: string; processingMode: ProcessingMode; processEngine: string };

export type TouchedValue = Record<keyof FormValue, boolean>;

Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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" });

Expand Down Expand Up @@ -38,10 +40,19 @@ function MoreScenarioDetailsDialog(props: WindowContentProps<WindowKind, Props>)
],
[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 <LoaderSpinner show={true} />;
}

return (
<WindowContent
{...props}
Expand Down Expand Up @@ -73,10 +84,12 @@ function MoreScenarioDetailsDialog(props: WindowContentProps<WindowKind, Props>)
<ItemLabelStyled>{i18next.t("scenarioDetails.label.processingMode", "Processing mode")}</ItemLabelStyled>
<Typography variant={"caption"}>{getProcessingModeVariantName(scenario.processingMode)}</Typography>
</ItemWrapperStyled>
<ItemWrapperStyled>
<ItemLabelStyled>{i18next.t("scenarioDetails.label.category", "Category")}</ItemLabelStyled>
<Typography variant={"caption"}>{scenario.processCategory}</Typography>
</ItemWrapperStyled>
{isCategoryFieldVisible && (
<ItemWrapperStyled>
<ItemLabelStyled>{i18next.t("scenarioDetails.label.category", "Category")}</ItemLabelStyled>
<Typography variant={"caption"}>{scenario.processCategory}</Typography>
</ItemWrapperStyled>
)}
<ItemWrapperStyled>
<ItemLabelStyled>{i18next.t("scenarioDetails.label.engine", "Engine")}</ItemLabelStyled>
<Typography variant={"caption"}>{scenario.engineSetupName}</Typography>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ScenarioParametersCombination[]>([]);
const [isAllCombinationsLoading, setIsAllCombinationsLoading] = useState<boolean>(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 ? (
Expand Down
37 changes: 37 additions & 0 deletions designer/client/src/components/useGetAllCombinations.ts
Original file line number Diff line number Diff line change
@@ -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<ScenarioParametersCombination[]>([]);
const [engineSetupErrors, setEngineSetupErrors] = useState<Record<string, string[]>>({});
const [isAllCombinationsLoading, setIsAllCombinationsLoading] = useState<boolean>(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 };
};
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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(() => {
Expand All @@ -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;
Expand Down Expand Up @@ -102,17 +103,19 @@ export function FiltersPart({ withSort, isLoading, data = [] }: { data: RowType[
})}
/>
</FilterMenu>
<FilterMenu label={t("table.filter.CATEGORY", "Category")} count={getFilter("CATEGORY", true).length}>
<SimpleOptionsStack
label={t("table.filter.CATEGORY", "Category")}
options={filterableValues.processCategory}
value={getFilter("CATEGORY", true)}
onChange={setFilter("CATEGORY")}
{...getEventTrackingProps({
selector: EventTrackingSelector.ScenariosByCategory,
})}
/>
</FilterMenu>
{withCategoriesVisible && (
<FilterMenu label={t("table.filter.CATEGORY", "Category")} count={getFilter("CATEGORY", true).length}>
<SimpleOptionsStack
label={t("table.filter.CATEGORY", "Category")}
options={filterableValues.processCategory}
value={getFilter("CATEGORY", true)}
onChange={setFilter("CATEGORY")}
{...getEventTrackingProps({
selector: EventTrackingSelector.ScenariosByCategory,
})}
/>
</FilterMenu>
)}
<FilterMenu label={t("table.filter.LABEL", "Label")} count={getFilter("LABEL", true).length}>
<SimpleOptionsStack
label={t("table.filter.LABEL", "Label")}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ScenarioStatus } from "./scenarioStatus";
import { ProcessingModeItem } from "./processingMode";
import { formatDateTime } from "nussknackerUi/DateUtils";
import { LabelChip } from "../../common/labelChip";
import { useScenariosWithCategoryVisible } from "../useScenariosQuery";

function Category({
category,
Expand Down Expand Up @@ -60,11 +61,16 @@ const HighlightedName = styled(Highlight)({
export function FirstLine({ row }: { row: RowType }): JSX.Element {
const { t } = useTranslation();
const filtersContext = useFilterContext<ScenariosFiltersModel>();
const { withCategoriesVisible } = useScenariosWithCategoryVisible();

return (
<div style={{ display: "flex" }}>
<Category category={row.processCategory} filtersContext={filtersContext} />
<span style={{ paddingLeft: 8, paddingRight: 8 }}>/</span>
{withCategoriesVisible && (
<>
<Category category={row.processCategory} filtersContext={filtersContext} />
<span style={{ paddingLeft: 8, paddingRight: 8 }}>/</span>
</>
)}
<CopyTooltip text={row.name} title={t("scenario.copyName", "Copy name to clipboard")}>
<HighlightedName value={row.name} filterText={filtersContext.getFilter("NAME")} />
</CopyTooltip>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,18 @@ 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<RowType>): JSX.Element {
const { data = [], isLoading } = props;
const { t } = useTranslation();
const filtersContext = useFilterContext<ScenariosFiltersModel>();
const _filterText = useMemo(() => filtersContext.getFilter("NAME"), [filtersContext]);
const [filterText] = useDebouncedValue(_filterText, 400);
const { withCategoriesVisible } = useScenariosWithCategoryVisible();

const columns = useMemo(
(): Columns<RowType> => [
const columns = useMemo((): Columns<RowType> => {
const availableColumns: Columns<RowType | undefined> = [
{
field: "id",
cellClassName: "noPadding stretch",
Expand All @@ -31,13 +33,15 @@ export function TablePart(props: ListPartProps<RowType>): JSX.Element {
minWidth: 200,
flex: 2,
},
{
field: "processCategory",
cellClassName: "noPadding stretch",
headerName: t("table.scenarios.title.PROCESS_CATEGORY", "Category"),
renderCell: (props) => <FilterLinkCell<ScenariosFiltersModel> filterKey="CATEGORY" {...props} />,
flex: 1,
},
withCategoriesVisible
? {
field: "processCategory",
cellClassName: "noPadding stretch",
headerName: t("table.scenarios.title.PROCESS_CATEGORY", "Category"),
renderCell: (props) => <FilterLinkCell<ScenariosFiltersModel> filterKey="CATEGORY" {...props} />,
flex: 1,
}
: undefined,
{
field: "createdBy",
cellClassName: "noPadding stretch",
Expand Down Expand Up @@ -107,9 +111,10 @@ export function TablePart(props: ListPartProps<RowType>): 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) => {
Expand Down
Loading

0 comments on commit b89a0f3

Please sign in to comment.