Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add select for sorting flow list #4137

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion editor.planx.uk/src/lib/featureFlags.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
// add/edit/remove feature flags in array below
const AVAILABLE_FEATURE_FLAGS = ["FEE_BREAKDOWN", "EXCLUSIVE_OR"] as const;
const AVAILABLE_FEATURE_FLAGS = [
"FEE_BREAKDOWN",
"EXCLUSIVE_OR",
"SORT_FLOWS",
] as const;

type FeatureFlag = (typeof AVAILABLE_FEATURE_FLAGS)[number];

Expand Down
45 changes: 31 additions & 14 deletions editor.planx.uk/src/pages/FlowEditor/lib/store/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { gql } from "@apollo/client";
import { getPathForNode, sortFlow } from "@opensystemslab/planx-core";
import {
ComponentType as TYPES,
flatFlags,
FlowGraph,
NodeId,
OrderedFlow,
flatFlags,
} from "@opensystemslab/planx-core/types";
import {
add,
Expand Down Expand Up @@ -132,18 +132,27 @@ interface PublishFlowResponse {
message: string;
}

export type PublishedFlowSummary = {
publishedAt: string;
hasSendComponent: boolean;
};

export type FlowSummaryOperations = {
createdAt: string;
actor: {
firstName: string;
lastName: string;
};
};

export interface FlowSummary {
id: string;
name: string;
slug: string;
status: "online" | "offline";
updatedAt: string;
operations: {
createdAt: string;
actor: {
firstName: string;
lastName: string;
};
}[];
operations: FlowSummaryOperations[];
publishedFlows: PublishedFlowSummary[];
}

export interface EditorStore extends Store.Store {
Expand Down Expand Up @@ -189,7 +198,7 @@ export interface EditorStore extends Store.Store {
href: string;
}) => void;
getURLForNode: (nodeId: string) => string;
getFlowSchema: () => { nodes?: string[], options?: string[] } | undefined;
getFlowSchema: () => { nodes?: string[]; options?: string[] } | undefined;
}

export const editorStore: StateCreator<
Expand Down Expand Up @@ -382,6 +391,7 @@ export const editorStore: StateCreator<
id
name
slug
status
updatedAt: updated_at
operations(limit: 1, order_by: { created_at: desc }) {
createdAt: created_at
Expand All @@ -390,6 +400,13 @@ export const editorStore: StateCreator<
lastName: last_name
}
}
publishedFlows: published_flows(
order_by: { created_at: desc }
limit: 1
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seen this was only used by the Team page, so though it felt better to expand this initial query

) {
publishedAt: created_at
hasSendComponent: has_send_component
}
}
}
`,
Expand Down Expand Up @@ -614,14 +631,14 @@ export const editorStore: StateCreator<
Object.entries(flow).map(([_id, node]) => {
if (node.data?.fn) {
// Exclude Filter fn value as not exposed to editors
if (node.data?.fn !== "flag") nodes.add(node.data.fn)
};
if (node.data?.fn !== "flag") nodes.add(node.data.fn);
}

if (node.data?.val) {
// Exclude Filter Option flag values as not exposed to editors
const flagVals = flatFlags.map((flag) => flag.value);
if (!flagVals.includes(node.data.val)) options.add(node.data.val)
};
if (!flagVals.includes(node.data.val)) options.add(node.data.val);
}
});

return {
Expand Down
6 changes: 5 additions & 1 deletion editor.planx.uk/src/pages/Team.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import { styled } from "@mui/material/styles";
import Typography from "@mui/material/Typography";
import { flow } from "lodash";
import { hasFeatureFlag } from "lib/featureFlags";
import React, { useCallback, useEffect, useState } from "react";
import { Link, useNavigation } from "react-navi";
import { FONT_WEIGHT_SEMI_BOLD } from "theme";
import { borderedFocusStyle } from "theme";
import { AddButton } from "ui/editor/AddButton";
import { SortFlowsSelect } from "ui/editor/SortFlowsSelect";
import { slugify } from "utils";

import { client } from "../lib/graphql";
Expand Down Expand Up @@ -351,6 +352,9 @@ const Team: React.FC = () => {
</Box>
{showAddFlowButton && <AddFlowButton flows={flows} />}
</Box>
{hasFeatureFlag("SORT_FLOWS") && flows && (
<SortFlowsSelect flows={flows} setFlows={setFlows} />
)}
{teamHasFlows && (
<DashboardList>
{flows.map((flow) => (
Expand Down
152 changes: 152 additions & 0 deletions editor.planx.uk/src/ui/editor/SortFlowsSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import TrendingDownIcon from "@mui/icons-material/TrendingDown";
import TrendingUpIcon from "@mui/icons-material/TrendingUp";
import Box from "@mui/material/Box";
import IconButton from "@mui/material/IconButton";
import MenuItem from "@mui/material/MenuItem";
import {
FlowSummary,
FlowSummaryOperations,
PublishedFlowSummary,
} from "pages/FlowEditor/lib/store/editor";
import React, { useEffect, useState } from "react";
import { useNavigation } from "react-navi";

import SelectInput from "./SelectInput/SelectInput";

type SortDirection = "asc" | "desc";
type SortKeys = keyof FlowSummary;
type SortNestedKeys = keyof PublishedFlowSummary | keyof FlowSummaryOperations;
type SortTypes = SortKeys | SortNestedKeys;

interface BasicSort {
displayName: string;
sortKey: Exclude<SortKeys, "publishedFlows" | "operations">;
}

interface PublishedFlowSort {
displayName: string;
sortKey: "publishedFlows";
nestedKey: keyof PublishedFlowSummary;
}

type SortObject = PublishedFlowSort | BasicSort;

const sortArray: SortObject[] = [
{ displayName: "Name", sortKey: "name" },
{ displayName: "Last updated", sortKey: "updatedAt" },
{ displayName: "Status", sortKey: "status" },
{
displayName: "Last published",
sortKey: "publishedFlows",
nestedKey: "publishedAt",
},
];

const sortFlowList = (
a: string | boolean,
b: string | boolean,
sortDirection: SortDirection,
) => {
if (a < b) {
return sortDirection === "asc" ? 1 : -1;
}
if (a > b) {
return sortDirection === "asc" ? -1 : 1;
}
return 0;
};

export const SortFlowsSelect = ({
flows,
setFlows,
}: {
flows: FlowSummary[];
setFlows: React.Dispatch<React.SetStateAction<FlowSummary[] | null>>;
}) => {
const [sortBy, setSortBy] = useState<SortObject>(sortArray[0]);
const [sortDirection, setSortDirection] = useState<SortDirection>("asc");

const navigation = useNavigation();

const addToSearchParams = (sortKey: SortTypes) => {
navigation.navigate(
{
pathname: window.location.pathname,
search: `?sort=${sortKey}`,
},
{
replace: true,
},
);
};

useEffect(() => {
const params = new URLSearchParams(window.location.search);
const urlSort = params.get("sort") as SortTypes;
const newSortObj = sortArray.find(
(sort) =>
sort.sortKey === urlSort ||
(sort.sortKey === "publishedFlows" && sort.nestedKey === urlSort),
);
newSortObj && setSortBy(newSortObj);
}, []);

useEffect(() => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went with a useEffect here to provide stability for adding more sorting types, would be interested to know if there's cleaner techniques

const { sortKey } = sortBy;

if (sortKey === "publishedFlows") {
const sortedFlows = flows?.sort((a: FlowSummary, b: FlowSummary) => {
const { nestedKey } = sortBy;

// auto sort unpublished flows to bottom
if (!a[sortKey][0]) return 1;
if (!b[sortKey][0]) return -1;

const aValue = a[sortKey][0][nestedKey];
const bValue = b[sortKey][0][nestedKey];
Copy link
Contributor Author

@RODO94 RODO94 Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although this possibly looks overly complicated, it allowed me to use the type safety to build the sortArray while not having to map sorting names to data fields with ternaries or conditional statements.

I iterated this a bit and found that the extra code came a lot from the nested keys in the flows array, but the extra lines always provided better maintainability for adding or removing sorting criteria while remaining aligned with what data is provided to us from the getFlows query


return sortFlowList(aValue, bValue, sortDirection);
});
sortedFlows && setFlows([...sortedFlows]);
addToSearchParams(sortBy.nestedKey);
} else {
const sortedFlows = flows?.sort((a: FlowSummary, b: FlowSummary) =>
sortFlowList(a[sortKey], b[sortKey], sortDirection),
);
sortedFlows && setFlows([...sortedFlows]);
addToSearchParams(sortBy.sortKey);
}
}, [sortBy, sortDirection]);

return (
<Box display={"flex"}>
<SelectInput
value={sortBy.sortKey}
onChange={(e) => {
const targetKey = e.target.value as SortTypes;
const newSortObject = sortArray.find(
(sortObject) => sortObject.sortKey === targetKey,
);
newSortObject && setSortBy(newSortObject);
}}
>
{sortArray.map(({ displayName, sortKey }) => (
<MenuItem key={sortKey} value={sortKey}>
{displayName}
</MenuItem>
))}
</SelectInput>
<IconButton
title="ordering"
aria-label="ordering"
onClick={() =>
sortDirection === "asc"
? setSortDirection("desc")
: setSortDirection("asc")
}
>
{sortDirection === "asc" ? <TrendingUpIcon /> : <TrendingDownIcon />}
</IconButton>
</Box>
);
};
Loading