Skip to content

Commit

Permalink
chore/sort_funcs_refactor (#499)
Browse files Browse the repository at this point in the history
  • Loading branch information
EduardZaydler authored Apr 10, 2024
1 parent 23778a1 commit 69fc860
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 133 deletions.
1 change: 0 additions & 1 deletion .prettierrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"parser": "typescript",
"printWidth": 100,
"tabWidth": 4,
"trailingComma": "es5",
Expand Down
23 changes: 14 additions & 9 deletions src/Components/PatternList/PatternList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,51 @@ import TrashIcon from "@skbkontur/react-icons/Trash";
import { Pattern } from "../../Domain/Pattern";
import { getPageLink } from "../../Domain/Global";
import RouterLink from "../RouterLink/RouterLink";
import { ISortConfig } from "../../hooks/useSortData";
import classNames from "classnames/bind";

import styles from "./PatternList.less";

const cn = classNames.bind(styles);

export type SortingColumn = "metric" | "trigger";
type SortingColumn = "metrics" | "triggers";

type Props = {
items: Array<Pattern>;
sortConfig: ISortConfig;
onRemove: (pattern: string) => void;
sortingColumn: SortingColumn;
sortingDown?: boolean;
onSort?: (sorting: SortingColumn) => void;
};

export default function PatternList(props: Props): React.ReactElement {
const { items, onRemove, sortingColumn, sortingDown, onSort } = props;
const sortingIcon = sortingDown ? <ArrowBoldDownIcon /> : <ArrowBoldUpIcon />;
const {
items,
sortConfig: { sortingColumn, direction },
onRemove,
onSort,
} = props;
const sortingIcon = direction === "desc" ? <ArrowBoldDownIcon /> : <ArrowBoldUpIcon />;
return (
<div>
<div className={cn("row", "header", "italic-font")}>
<div className={cn("name")}>Pattern</div>
<button
type="button"
className={cn("trigger-counter", { sorting: onSort })}
onClick={onSort && (() => onSort("trigger"))}
onClick={onSort && (() => onSort("triggers"))}
>
Triggers{" "}
{sortingColumn === "trigger" && (
{sortingColumn === "triggers" && (
<span className={cn("icon")}>{sortingIcon}</span>
)}
</button>
<button
type="button"
className={cn("metric-counter", { sorting: onSort })}
onClick={onSort && (() => onSort("metric"))}
onClick={onSort && (() => onSort("metrics"))}
>
Metric{" "}
{sortingColumn === "metric" && (
{sortingColumn === "metrics" && (
<span className={cn("icon")}>{sortingIcon}</span>
)}
</button>
Expand Down
2 changes: 1 addition & 1 deletion src/Components/SubscriptionList/SubscriptionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const SubscriptionList: React.FC<Props> = ({
const { sortedData, sortConfig, handleSort } = useSortData(subscriptions, "contacts");

const SortingIcon =
sortConfig.direction === "descending" ? <ArrowBoldDownIcon /> : <ArrowBoldUpIcon />;
sortConfig.direction === "desc" ? <ArrowBoldDownIcon /> : <ArrowBoldUpIcon />;
return (
<div className={cn("items-container")}>
<table ref={tableRef} className={cn("items")}>
Expand Down
2 changes: 1 addition & 1 deletion src/Components/TagList/TagList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const TagList: FC<ITagListProps> = ({
const listRef = useRef<FixedSizeList>(null);

const SortingIcon =
sortConfig.direction === "descending" ? <ArrowBoldDownIcon /> : <ArrowBoldUpIcon />;
sortConfig.direction === "desc" ? <ArrowBoldDownIcon /> : <ArrowBoldUpIcon />;

const scrollToRow = (row: string) => {
if (Number.isNaN(Number(row))) return;
Expand Down
130 changes: 40 additions & 90 deletions src/Containers/PatternListContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,111 +1,61 @@
import * as React from "react";
import React, { useState, useEffect } from "react";
import MoiraApi from "../Api/MoiraApi";
import { Pattern } from "../Domain/Pattern";
import { SortingColumn } from "../Components/PatternList/PatternList";
import { withMoiraApi } from "../Api/MoiraApiInjection";
import PatternList from "../Components/PatternList/PatternList";
import { Layout, LayoutContent, LayoutTitle } from "../Components/Layout/Layout";
import { setDocumentTitle } from "../helpers/setDocumentTitle";
import { toggleLoading, setError } from "../store/Reducers/UIReducer.slice";
import { useAppDispatch, useAppSelector } from "../store/hooks";
import { UIState } from "../store/selectors";
import { useSortData } from "../hooks/useSortData";

type Props = { moiraApi: MoiraApi };
type State = {
loading: boolean;
error?: string;
list?: Array<Pattern>;
sortingColumn: SortingColumn;
sortingDown: boolean;
};

class PatternListContainer extends React.Component<Props, State> {
public state: State = {
sortingColumn: "trigger",
sortingDown: false,
loading: false,
};
const PatternListContainer: React.FC<Props> = ({ moiraApi }) => {
const dispatch = useAppDispatch();
const { isLoading, error } = useAppSelector(UIState);
const [list, setList] = useState<Pattern[] | undefined>();
const { sortedData, sortConfig, handleSort } = useSortData(list ?? [], "metrics");

public componentDidMount() {
useEffect(() => {
setDocumentTitle("Patterns");
this.getData(this.props);
}

public UNSAFE_componentWillReceiveProps(nextProps: Props) {
this.getData(nextProps);
}
getData();
}, []);

public render(): React.ReactElement {
const { loading, error, list, sortingColumn, sortingDown } = this.state;
return (
<Layout loading={loading} error={error}>
<LayoutContent>
<LayoutTitle>Patterns</LayoutTitle>
{list && (
<PatternList
items={this.sortPatterns(list)}
onSort={(sorting) => {
if (sorting === sortingColumn) {
this.setState({ sortingDown: !sortingDown });
} else {
this.setState({
sortingColumn: sorting,
sortingDown: true,
});
}
}}
sortingColumn={sortingColumn}
sortingDown={sortingDown}
onRemove={this.removePattern}
/>
)}
</LayoutContent>
</Layout>
);
}

private async getData(props: Props) {
const getData = async () => {
dispatch(toggleLoading(true));
try {
const { list } = await props.moiraApi.getPatternList();
this.setState({ list });
const { list } = await moiraApi.getPatternList();
setList(list);
} catch (error) {
this.setState({ error: error.message });
dispatch(setError(error.message));
} finally {
this.setState({ loading: false });
dispatch(toggleLoading(false));
}
}
};

private removePattern = async (pattern: string) => {
this.setState({ loading: true });
await this.props.moiraApi.delPattern(pattern);
this.getData(this.props);
const removePattern = async (pattern: string) => {
dispatch(toggleLoading(true));
await moiraApi.delPattern(pattern);
getData();
};

private sortPatterns(patterns: Array<Pattern>): Array<Pattern> {
const { sortingColumn, sortingDown } = this.state;
const sorting = {
trigger: (x: Pattern, y: Pattern) => {
const valA = x.triggers.length || 0;
const valB = y.triggers.length || 0;
if (valA < valB) {
return sortingDown ? -1 : 1;
}
if (valA > valB) {
return sortingDown ? 1 : -1;
}
return 0;
},
metric: (x: Pattern, y: Pattern) => {
const valA = x.metrics.length || 0;
const valB = y.metrics.length || 0;
if (valA < valB) {
return sortingDown ? -1 : 1;
}
if (valA > valB) {
return sortingDown ? 1 : -1;
}
return 0;
},
};
return patterns.slice(0).sort(sorting[sortingColumn]);
}
}
return (
<Layout loading={isLoading} error={error}>
<LayoutContent>
<LayoutTitle>Patterns</LayoutTitle>
{list && (
<PatternList
items={sortedData}
onSort={handleSort}
sortConfig={sortConfig}
onRemove={removePattern}
/>
)}
</LayoutContent>
</Layout>
);
};

export default withMoiraApi(PatternListContainer);
6 changes: 5 additions & 1 deletion src/Stories/PatternList.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,9 @@ export default {
};

export const Default = () => (
<PatternList items={items} onRemove={action("onRemove")} sortingColumn="metric" />
<PatternList
items={items}
onRemove={action("onRemove")}
sortConfig={{ direction: "asc", sortingColumn: "metrics" }}
/>
);
51 changes: 21 additions & 30 deletions src/hooks/useSortData.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,46 @@
import { useCallback, useState, useMemo } from "react";
import orderBy from "lodash/orderBy";

type TSortingColumn = {
sortingColumn: string;
};

export interface ISortConfig extends TSortingColumn {
direction: "ascending" | "descending";
direction: "asc" | "desc";
}

const compare = <T extends Record<string, unknown>>(
a: T,
b: T,
sortConfig: ISortConfig
): number => {
const argA = a[sortConfig.sortingColumn];
const argB = b[sortConfig.sortingColumn];

const isArray = Array.isArray(argA);

if (isArray) {
return sortConfig.direction === "ascending"
? (argA as T[]).length - (argB as T[]).length
: (argB as T[]).length - (argA as T[]).length;
}

const collator = new Intl.Collator();
return sortConfig.direction === "ascending"
? collator.compare(argA as string, argB as string)
: collator.compare(argB as string, argA as string);
};

export const useSortData = <T extends Record<string, unknown>>(
data: T[],
sortingColumn: string
) => {
const [sortConfig, setSortConfig] = useState<ISortConfig>({
sortingColumn,
direction: "ascending",
direction: "asc",
});

const sortedData = useMemo(() => {
const sortableData = [...data];
if (sortConfig !== null) {
sortableData.sort((a, b) => compare(a, b, sortConfig));
}
return sortableData;
const sorted = orderBy(
data,
[
(item) => {
const value = item[sortConfig.sortingColumn];

if (typeof value === "string") {
return value.toLowerCase();
} else if (Array.isArray(value)) {
return value.length;
}
return value;
},
],
[sortConfig.direction]
);
return sorted;
}, [data, sortConfig]);

const handleSort = useCallback(
(sortingColumn: string) => {
const direction = sortConfig.direction === "ascending" ? "descending" : "ascending";
const direction = sortConfig.direction === "asc" ? "desc" : "asc";
setSortConfig({ sortingColumn, direction } as ISortConfig);
},
[sortConfig]
Expand Down

0 comments on commit 69fc860

Please sign in to comment.