Skip to content

Commit

Permalink
Display category and scenario name as breadcrumbs on scenario list (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
gskrobisz authored Sep 20, 2024
1 parent 4ddb086 commit 838f5ce
Show file tree
Hide file tree
Showing 13 changed files with 103 additions and 45 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ const StyledAutocomplete = styled(Autocomplete)<{ isEdited: boolean }>(({ isEdit
},
}));

const StyledLabelChip = styled(Chip)({
borderRadius: "16px",
margin: "2px",
});

const filter = createFilterOptions<string | LabelOption>();

type LabelOption = {
Expand Down Expand Up @@ -332,7 +337,7 @@ export const ScenarioLabels = ({ readOnly }: Props) => {
const props = isEdited ? { ...tagProps } : {};
const labelError = labelOptionsErrors.find((error) => error.label === toLabelValue(option));
return (
<Chip
<StyledLabelChip
key={key}
data-testid={`scenario-label-${index}`}
color={labelError ? "error" : "default"}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,56 @@
import React, { useCallback, useMemo } from "react";
import { Chip } from "@mui/material";
import { Button, Chip, Typography } from "@mui/material";

interface Props {
value: string;
filterValue: string[];
category: string;
filterValues: string[];
setFilter: (value: string[]) => void;
}

export function CategoryChip({ value, filterValue, setFilter }: Props): JSX.Element {
const isSelected = useMemo(() => filterValue.includes(value), [filterValue, value]);
export function CategoryChip({ category, filterValues, setFilter }: Props): JSX.Element {
const isSelected = useMemo(() => filterValues.includes(category), [filterValues, category]);

const onClick = useCallback(
(e) => {
setFilter(isSelected ? filterValue.filter((v) => v !== value) : [...filterValue, value]);
setFilter(isSelected ? filterValues.filter((v) => v !== category) : [...filterValues, category]);
e.preventDefault();
e.stopPropagation();
},
[setFilter, isSelected, filterValue, value],
[setFilter, isSelected, filterValues, category],
);

return <Chip tabIndex={0} label={value} size="small" color={isSelected ? "primary" : "default"} onClick={onClick} />;
return <Chip tabIndex={0} label={category} size="small" color={isSelected ? "primary" : "default"} onClick={onClick} />;
}

export function CategoryButton({ category, filterValues, setFilter }: Props): JSX.Element {
const isSelected = useMemo(() => filterValues.includes(category), [filterValues, category]);

const onClick = useCallback(
(e) => {
setFilter(isSelected ? filterValues.filter((v) => v !== category) : [...filterValues, category]);
e.preventDefault();
e.stopPropagation();
},
[setFilter, isSelected, filterValues, category],
);

return (
<Typography
component={Button}
color={isSelected ? "primary" : "inherit"}
sx={{
textTransform: "capitalize",
display: "flex",
gap: 1,
alignItems: "center",
minWidth: "unset",
p: 0,
mx: 0,
}}
onClick={onClick}
aria-selected={isSelected}
>
{category}
</Typography>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export { useDefaultTheme } from "./defaultTheme";
export { HistoryProvider, useHistory, useBackHref } from "./historyContext";
export { NuIcon } from "./nuIcon";
export { TextFieldWithClear, InputWithClear } from "./forms";
export { CategoryChip } from "./categoryChip";
export { CategoryButton, CategoryChip } from "./categoryChip";
export { useFilterContext, FiltersContextProvider, createFilterRules } from "./filters";
export { ExternalLink, NavigationProvider } from "./parentNavigationProvider";
export { nodeHref, fragmentNodeHref, scenarioHref, metricsHref } from "./scenarioHref";
Expand Down
20 changes: 12 additions & 8 deletions designer/submodules/packages/components/src/common/labelChip.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import React, { useCallback, useMemo } from "react";
import { Chip } from "@mui/material";
import { Chip, styled } from "@mui/material";

interface Props {
key: string;
id: string;
value: string;
filterValue: string[];
setFilter: (value: string[]) => void;
}

export function LabelChip({ key, value, filterValue, setFilter }: Props): JSX.Element {
const StyledLabelChip = styled(Chip)({
borderRadius: "16px",
});

export function LabelChip({ id, value, filterValue, setFilter }: Props): JSX.Element {
const isSelected = useMemo(() => filterValue.includes(value), [filterValue, value]);

const onClick = useCallback(
Expand All @@ -21,13 +25,13 @@ export function LabelChip({ key, value, filterValue, setFilter }: Props): JSX.El
);

return (
<Chip
<StyledLabelChip
key={id}
color={isSelected ? "primary" : "default"}
size="small"
variant={"outlined"}
key={key}
tabIndex={0}
label={value}
size="small"
color={isSelected ? "primary" : "default"}
tabIndex={0}
onClick={onClick}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function CategoriesCell(props: CellRendererParams): JSX.Element {
return (
<TruncateWrapper>
{value.map((name) => (
<CategoryChip key={name} value={name} filterValue={filterValue} setFilter={setFilter("CATEGORY")} />
<CategoryChip key={name} category={name} filterValues={filterValue} setFilter={setFilter("CATEGORY")} />
))}
</TruncateWrapper>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { useFilterContext } from "../../common";
import { QuickFilter } from "../../scenarios/filters/quickFilter";
import { FilterMenu } from "../../scenarios/filters/filterMenu";
import { SimpleOptionsStack } from "../../scenarios/filters/simpleOptionsStack";
import { StatusOptionsStack } from "../../scenarios/filters/otherOptionsStack";
import { StatusOptionsStack } from "../../scenarios/filters/typeOptionsStack";
import { OptionsStack } from "../../scenarios/filters/optionsStack";
import { FilterListItem } from "../../scenarios/filters/filterListItem";
import React from "react";
import { useTranslation } from "react-i18next";
import { Divider, Stack } from "@mui/material";
import { xor } from "lodash";
import { EventTrackingType, EventTrackingSelector, getEventTrackingProps } from "nussknackerUi/eventTracking";
import { EventTrackingSelector, getEventTrackingProps } from "nussknackerUi/eventTracking";

interface FiltersPartProps {
isLoading: boolean;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StatusFilterOption } from "../../scenarios/filters/otherOptionsStack";
import { StatusFilterOption } from "../../scenarios/filters/typeOptionsStack";
import { ProcessingMode } from "../../scenarios/list/processingMode";

export interface UsagesFiltersModel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useScenarioLabelsQuery, useStatusDefinitions, useUserQuery } from "../u
import { QuickFilter } from "./quickFilter";
import { FilterMenu } from "./filterMenu";
import { SimpleOptionsStack } from "./simpleOptionsStack";
import { OtherOptionsStack, StatusOptionsStack } from "./otherOptionsStack";
import { TypeOptionsStack, StatusOptionsStack } from "./typeOptionsStack";
import { SortOptionsStack } from "./sortOptionsStack";
import { ActiveFilters } from "./activeFilters";
import { RowType } from "../list/listPart";
Expand Down Expand Up @@ -135,8 +135,8 @@ export function FiltersPart({ withSort, isLoading, data = [] }: { data: RowType[
})}
/>
</FilterMenu>
<FilterMenu label={t("table.filter.other", "Type")} count={getFilter("TYPE", true).length}>
<OtherOptionsStack />
<FilterMenu label={t("table.filter.TYPE", "Type")} count={getFilter("TYPE", true).length}>
<TypeOptionsStack />
</FilterMenu>
{withSort ? (
<FilterMenu label={t("table.filter.SORT_BY", "Sort")}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import ScanarioIcon from "../../assets/icons/scenario.svg";
import FragmentIcon from "../../assets/icons/fragment.svg";
import { EventTrackingSelector, getEventTrackingProps } from "nussknackerUi/eventTracking";

export function OtherOptionsStack(): JSX.Element {
export function TypeOptionsStack(): JSX.Element {
const { t } = useTranslation();
const { getFilter, setFilter } = useFilterContext<ScenariosFiltersModel>();
const otherFilters: Array<keyof ScenariosFiltersModel> = ["TYPE"];
const getTypeFilter = () => getFilter("TYPE", true);

return (
<OptionsStack
label={t("table.filter.other", "Other")}
label={t("table.filter.TYPE", "Type")}
options={otherFilters.map((name) => ({ name }))}
value={otherFilters
.flatMap((k) => getFilter(k))
Expand Down
35 changes: 24 additions & 11 deletions designer/submodules/packages/components/src/scenarios/list/item.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { History } from "@mui/icons-material";
import { Divider, Stack, Typography } from "@mui/material";
import { Divider, Stack, styled, Typography } from "@mui/material";
import React, { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { CategoryChip, Highlight, useFilterContext } from "../../common";
import { CategoryButton, Highlight, useFilterContext, TruncateWrapper } from "../../common";
import { Author } from "./author";
import { ScenariosFiltersModel } from "../filters/scenariosFiltersModel";
import { RowType } from "./listPart";
Expand All @@ -13,19 +13,24 @@ import { ScenarioStatus } from "./scenarioStatus";
import { ProcessingModeItem } from "./processingMode";
import { formatDateTime } from "nussknackerUi/DateUtils";
import { LabelChip } from "../../common/labelChip";
import { TruncateWrapper } from "../../common/utils";

function Category({ value, filtersContext }: { value: string; filtersContext: FiltersContextType<ScenariosFiltersModel> }): JSX.Element {
function Category({
category,
filtersContext,
}: {
category: string;
filtersContext: FiltersContextType<ScenariosFiltersModel>;
}): JSX.Element {
const { setFilter, getFilter } = filtersContext;
const filterValue = useMemo(() => getFilter("CATEGORY", true), [getFilter]);
return <CategoryChip value={value} filterValue={filterValue} setFilter={setFilter("CATEGORY")} />;
return <CategoryButton category={category} filterValues={filterValue} setFilter={setFilter("CATEGORY")} />;
}

function Labels({ values, filtersContext }: { values: string[]; filtersContext: FiltersContextType<ScenariosFiltersModel> }): JSX.Element {
const { setFilter, getFilter } = filtersContext;
const filterValue = useMemo(() => getFilter("LABEL", true), [getFilter]);

const elements = values.map((v) => <LabelChip key={v} value={v} filterValue={filterValue} setFilter={setFilter("LABEL")} />);
const elements = values.map((v) => <LabelChip key={v} id={v} value={v} filterValue={filterValue} setFilter={setFilter("LABEL")} />);
return <TruncateWrapper>{elements}</TruncateWrapper>;
}

Expand All @@ -47,14 +52,23 @@ export function LastAction({ lastAction }: { lastAction: ProcessActionType }): J
) : null;
}

const HighlightedName = styled(Highlight)({
fontWeight: "bold",
fontSize: "1rem",
});

export function FirstLine({ row }: { row: RowType }): JSX.Element {
const { t } = useTranslation();
const filtersContext = useFilterContext<ScenariosFiltersModel>();

return (
<CopyTooltip text={row.name} title={t("scenario.copyName", "Copy name to clipboard")}>
<Highlight value={row.name} filterText={filtersContext.getFilter("NAME")} />
</CopyTooltip>
<div style={{ display: "flex" }}>
<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>
</div>
);
}

Expand Down Expand Up @@ -84,8 +98,7 @@ export function SecondLine({ row }: { row: RowType }): JSX.Element {
</div>
{!row.isFragment && !row.isArchived && <ScenarioStatus state={row.state} filtersContext={filtersContext} />}
<ProcessingModeItem processingMode={row.processingMode} filtersContext={filtersContext} />
<Category value={row.processCategory} filtersContext={filtersContext} />
<Labels values={row.labels} filtersContext={filtersContext} />
{row.labels.length && <Labels values={row.labels} filtersContext={filtersContext} />}
</Stack>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export function TablePart(props: ListPartProps<RowType>): JSX.Element {
{
field: "createdAt",
headerName: t("table.scenarios.title.CREATION_DATE", "Creation date"),
type: "dateTime",
type: "string",
flex: 2,
renderCell: (props) => <Highlight filterText={filterText} {...props} />,
hide: true,
Expand All @@ -72,7 +72,7 @@ export function TablePart(props: ListPartProps<RowType>): JSX.Element {
{
field: "modificationDate",
headerName: t("table.scenarios.title.MODIFICATION_DATE", "Modification date"),
type: "dateTime",
type: "string",
flex: 2,
renderCell: (props) => <Highlight filterText={filterText} {...props} value={formatDateTime(props.value)} />,
sortingOrder: ["desc", "asc", null],
Expand Down Expand Up @@ -124,12 +124,14 @@ export function TablePart(props: ListPartProps<RowType>): JSX.Element {
const text = filter?.toString();
if (!text?.length) return true;
const segments = text.trim().split(/\s/);
return segments.every((segment) =>
["id"]
.map((field) => row[field]?.toString().toLowerCase())
return segments.every((segment) => {
return columns
.filter((value) => visibleColumns[value.field])
.filter((value) => ["id", "createdAt", "modificationDate"].includes(value.field))
.map(({ field }) => row[field]?.toString().toLowerCase())
.filter(Boolean)
.some((value) => value.includes(segment.toLowerCase())),
);
.some((value) => value.includes(segment.toLowerCase()));
});
},
ARCHIVED: (row, filter) => (filter ? row.isArchived : !row.isArchived),
TYPE: (row, value) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export function useScenariosWithStatus(): UseQueryResult<Scenario[]> {
data: data.map((scenario) => ({
...scenario,
state: statuses?.data?.[scenario.name] || scenario.state,
id: scenario.name, // required by DataGrid when table=true
})),
} as UseQueryResult<Scenario[]>;
}, [scenarios, statuses]);
Expand Down

0 comments on commit 838f5ce

Please sign in to comment.