Skip to content

Commit

Permalink
DEVPROD-5790: Make parseSortString more generic (#375)
Browse files Browse the repository at this point in the history
SupaJoon authored Sep 16, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 857a283 commit b9fd244
Showing 6 changed files with 167 additions and 51 deletions.
16 changes: 14 additions & 2 deletions apps/spruce/src/gql/generated/types.ts
Original file line number Diff line number Diff line change
@@ -2230,7 +2230,7 @@ export type Query = {
userSettings?: Maybe<UserSettings>;
version: Version;
viewableProjectRefs: Array<GroupedProjects>;
waterfall?: Maybe<Waterfall>;
waterfall: Waterfall;
};

export type QueryBbGetCreatedTicketsArgs = {
@@ -3428,7 +3428,9 @@ export type VolumeHost = {
export type Waterfall = {
__typename?: "Waterfall";
buildVariants: Array<WaterfallBuildVariant>;
versions: Array<Version>;
nextPageOrder: Scalars["Int"]["output"];
prevPageOrder: Scalars["Int"]["output"];
versions: Array<WaterfallVersion>;
};

export type WaterfallBuild = {
@@ -3449,6 +3451,10 @@ export type WaterfallBuildVariant = {

export type WaterfallOptions = {
limit?: InputMaybe<Scalars["Int"]["input"]>;
/** Return versions with an order lower than maxOrder. Used for paginating forward. */
maxOrder?: InputMaybe<Scalars["Int"]["input"]>;
/** Return versions with an order greater than minOrder. Used for paginating backward. */
minOrder?: InputMaybe<Scalars["Int"]["input"]>;
projectIdentifier: Scalars["String"]["input"];
requesters?: InputMaybe<Array<Scalars["String"]["input"]>>;
};
@@ -3460,6 +3466,12 @@ export type WaterfallTask = {
status: Scalars["String"]["output"];
};

export type WaterfallVersion = {
__typename?: "WaterfallVersion";
inactiveVersions?: Maybe<Array<Version>>;
version?: Maybe<Version>;
};

export type Webhook = {
__typename?: "Webhook";
endpoint: Scalars["String"]["output"];
20 changes: 16 additions & 4 deletions apps/spruce/src/pages/task/taskTabs/ExecutionTasksTable.tsx
Original file line number Diff line number Diff line change
@@ -136,10 +136,22 @@ const getInitialSorting = (queryParams: {
},
];
} else if (sorts) {
const parsedSorts = parseSortString(sorts);
initialSorting = parsedSorts.map(({ Direction, Key }) => ({
id: Key as TaskSortCategory,
desc: Direction === SortDirection.Desc,
const parsedSorts = parseSortString<
"id",
"direction",
TaskSortCategory,
{
id: TaskSortCategory;
direction: SortDirection;
}
>(sorts, {
sortCategoryEnum: TaskSortCategory,
sortByKey: "id",
sortDirKey: "direction",
});
initialSorting = parsedSorts.map(({ direction, id }) => ({
id,
desc: direction === SortDirection.Desc,
}));
}

13 changes: 9 additions & 4 deletions apps/spruce/src/pages/task/taskTabs/TestsTable.tsx
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@ import {
TestSortCategory,
TestResult,
TaskQuery,
TestSortOptions,
} from "gql/generated/types";
import { TASK_TESTS } from "gql/queries";
import { useTableSort, useUpdateURLQueryParams, usePolling } from "hooks";
@@ -266,12 +267,17 @@ const getQueryVariables = (
? SortDirection.Desc
: SortDirection.Asc;

// @ts-expect-error: FIXME. This comment was added by an automated script.
let sort = [];
let sort: TestSortOptions[] = [];
if (sortBy && direction) {
sort = [{ sortBy, direction }];
} else if (sorts) {
sort = parseSortString(sorts, {
sort = parseSortString<
"sortBy",
"direction",
TestSortCategory,
TestSortOptions
>(sorts, {
sortCategoryEnum: TestSortCategory,
sortByKey: "sortBy",
sortDirKey: "direction",
});
@@ -286,7 +292,6 @@ const getQueryVariables = (
return {
id: taskId,
execution: queryParamAsNumber(execution),
// @ts-expect-error: FIXME. This comment was added by an automated script.
sort,
limitNum: getLimit(queryParams[PaginationQueryParams.Limit]),
statusList,
22 changes: 18 additions & 4 deletions apps/spruce/src/pages/version/useQueryVariables/index.ts
Original file line number Diff line number Diff line change
@@ -27,12 +27,27 @@ export const useQueryVariables = (

// This should be reworked once the antd tables are removed.
// At the current state, sorts & duration will never both be defined.
let sortsToApply: SortOrder[];
let sortsToApply: SortOrder[] = [];
const opts = {
sortByKey: "Key" as "Key",
sortDirKey: "Direction" as "Direction",
sortCategoryEnum: TaskSortCategory,
};
if (sorts) {
sortsToApply = parseSortString(sorts);
sortsToApply = parseSortString<
"Key",
"Direction",
TaskSortCategory,
SortOrder
>(sorts, opts);
}
if (duration) {
sortsToApply = parseSortString(`${TaskSortCategory.Duration}:${duration}`);
sortsToApply = parseSortString<
"Key",
"Direction",
TaskSortCategory,
SortOrder
>(`${TaskSortCategory.Duration}:${duration}`, opts);
}

return {
@@ -42,7 +57,6 @@ export const useQueryVariables = (
taskName: getString(taskName),
statuses: toArray(statuses),
baseStatuses: toArray(baseStatuses),
// @ts-expect-error: FIXME. This comment was added by an automated script.
sorts: sortsToApply,
limit,
page,
70 changes: 66 additions & 4 deletions apps/spruce/src/utils/queryString/sortString.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
import { SorterResult } from "antd/es/table/interface";
import { SortDirection, TaskSortCategory, Task } from "gql/generated/types";
import {
SortDirection,
TaskSortCategory,
Task,
SortOrder,
} from "gql/generated/types";
import { parseSortString, toSortString } from "./sortString";

describe("parseSortString", () => {
it("should parse a sort string with multiple sorts", () => {
expect(parseSortString("NAME:ASC;STATUS:DESC")).toStrictEqual([
expect(
parseSortString<"Key", "Direction", TaskSortCategory, SortOrder>(
"NAME:ASC;STATUS:DESC",
{
sortByKey: "Key",
sortDirKey: "Direction",
sortCategoryEnum: TaskSortCategory,
},
),
).toStrictEqual([
{
Key: TaskSortCategory.Name,
Direction: SortDirection.Asc,
@@ -15,8 +29,56 @@ describe("parseSortString", () => {
},
]);
});
it("should not parse an invalid sort string", () => {
expect(parseSortString("FOO:ASC")).toStrictEqual([]);
enum Categories {
Apple = "apple",
Banana = "banana",
Pear = "pear",
}
it("should partially process invalid sort strings", () => {
expect(
parseSortString<
"cat",
"dir",
Categories,
{ cat: Categories; dir: SortDirection }
>("apple:ASC;pear:DESC;invalidCat:DESC", {
sortByKey: "cat",
sortDirKey: "dir",
sortCategoryEnum: Categories,
}),
).toStrictEqual([
{
cat: Categories.Apple,
dir: SortDirection.Asc,
},
{
cat: Categories.Pear,
dir: SortDirection.Desc,
},
]);
});
it("can accept an array of strings", () => {
expect(
parseSortString<
"cat",
"dir",
Categories,
{ cat: Categories; dir: SortDirection }
>(["apple:ASC", "pear:DESC"], {
sortByKey: "cat",
sortDirKey: "dir",
sortCategoryEnum: Categories,
}),
).toStrictEqual([
{
cat: Categories.Apple,
dir: SortDirection.Asc,
},
{
cat: Categories.Pear,
dir: SortDirection.Desc,
},
]);
});
});

77 changes: 44 additions & 33 deletions apps/spruce/src/utils/queryString/sortString.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Key, SorterResult } from "antd/es/table/interface";
import { Task, SortDirection, TaskSortCategory } from "gql/generated/types";
import { Task, SortDirection } from "gql/generated/types";

export const getSortString = (columnKey: Key, direction: SortDirection) =>
columnKey && direction ? `${columnKey}:${direction}` : undefined;
@@ -34,43 +34,54 @@ export const toSortString = (
: undefined;
};

// takes a sort query string and parses it into valid GQL params
// By default, uses keys for task's SortOrder type, but sort field keys can be passed in for use with e.g. tests' TestSortOptions
/**
* Parses a sort query string or array into an array of sort objects.
* The result is in the shape [{ [SortByKey]: SortCategoryEnum, [SortDirectionKey]: SortDirection }, ...]
* @template SortByKey - The key for the sort category.
* @template SortDirectionKey - The key for the sort direction.
* @template SortCategoryEnum - The enum type for sort categories.
* @template T - The type of the resulting sort objects, which must include
* both the sort category and direction.
* @param sortQuery - A string or an array of strings representing the sort
* criteria in the format "category:direction".
* @param options - An object containing the following properties:
* @param options.sortByKey - The key to use for the sort category in the resulting objects.
* @param options.sortDirKey - The key to use for the sort direction in the resulting objects.
* @param options.sortCategoryEnum - An object that maps valid sort categories to their
* corresponding enum values.
* @returns An array of sort objects, each containing the specified sort
* category and direction.
*/
export const parseSortString = <
T extends Record<string, SortDirection | TaskSortCategory>,
SortByKey extends string,
SortDirectionKey extends string,
SortCategoryEnum extends string,
T extends Record<SortByKey, SortCategoryEnum> &
Record<SortDirectionKey, SortDirection>,
>(
sortQuery: string | string[],
options: {
sortByKey: keyof T;
sortDirKey: keyof T;
} = { sortByKey: "Key", sortDirKey: "Direction" },
sortByKey: SortByKey;
sortDirKey: SortDirectionKey;
sortCategoryEnum: Record<string, SortCategoryEnum>;
},
): T[] => {
let sorts: T[] = [];
let sortArray: string[] = [];
if (typeof sortQuery === "string") {
sortArray = sortQuery.split(";");
} else {
sortArray = sortQuery;
}
if (sortArray?.length > 0) {
sortArray.forEach((singleSort) => {
const parts = singleSort.split(":");
if (parts.length !== 2) {
return;
}
if (
!Object.values(TaskSortCategory).includes(parts[0] as TaskSortCategory)
) {
return;
}
if (!Object.values(SortDirection).includes(parts[1] as SortDirection)) {
return;
}
sorts = sorts.concat({
[options.sortByKey]: parts[0] as TaskSortCategory,
[options.sortDirKey]: parts[1] as SortDirection,
const { sortByKey, sortCategoryEnum, sortDirKey } = options;
const sortArray = Array.isArray(sortQuery) ? sortQuery : sortQuery.split(";");
const sorts: T[] = sortArray.reduce((accum: T[], singleSort: string) => {
const [category, direction] = singleSort.split(":");
if (
category &&
direction &&
Object.values(sortCategoryEnum).includes(category as SortCategoryEnum) &&
Object.values(SortDirection).includes(direction as SortDirection)
) {
accum.push({
[sortByKey]: category as SortCategoryEnum,
[sortDirKey]: direction as SortDirection,
} as T);
});
}
}
return accum;
}, []);
return sorts;
};

0 comments on commit b9fd244

Please sign in to comment.