2022-02-08 12:06:25 +01:00
|
|
|
import React, { useMemo } from 'react';
|
2022-03-25 12:34:20 +01:00
|
|
|
import { getBasePath } from 'utils/formatPath';
|
2022-02-23 15:08:44 +01:00
|
|
|
import { createPersistentGlobalStateHook } from './usePersistentGlobalState';
|
2022-03-25 15:30:52 +01:00
|
|
|
import {
|
|
|
|
expired,
|
|
|
|
getDiffInDays,
|
|
|
|
toggleExpiryByTypeMap,
|
|
|
|
} from 'component/Reporting/utils';
|
2022-04-26 10:53:46 +02:00
|
|
|
import { FeatureSchema } from 'openapi';
|
2022-02-08 12:06:25 +01:00
|
|
|
|
2022-05-02 12:52:33 +02:00
|
|
|
export type FeaturesSortType =
|
2022-02-08 12:06:25 +01:00
|
|
|
| 'name'
|
2022-03-25 15:30:52 +01:00
|
|
|
| 'expired'
|
2022-02-08 12:06:25 +01:00
|
|
|
| 'type'
|
|
|
|
| 'enabled'
|
|
|
|
| 'stale'
|
|
|
|
| 'created'
|
|
|
|
| 'last-seen'
|
2022-03-25 15:30:52 +01:00
|
|
|
| 'status'
|
2022-02-08 12:06:25 +01:00
|
|
|
| 'project';
|
|
|
|
|
2022-05-02 12:52:33 +02:00
|
|
|
export interface IFeaturesSort {
|
2022-02-08 12:06:25 +01:00
|
|
|
type: FeaturesSortType;
|
2022-03-25 15:30:52 +01:00
|
|
|
desc?: boolean;
|
2022-02-08 12:06:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface IFeaturesSortOutput {
|
|
|
|
sort: IFeaturesSort;
|
2022-04-26 10:53:46 +02:00
|
|
|
sorted: FeatureSchema[];
|
2022-02-18 09:51:10 +01:00
|
|
|
setSort: React.Dispatch<React.SetStateAction<IFeaturesSort>>;
|
2022-02-08 12:06:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface IFeaturesFilterSortOption {
|
|
|
|
type: FeaturesSortType;
|
|
|
|
name: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store the features sort state globally, and in localStorage.
|
|
|
|
// When changing the format of IFeaturesSort, change the version as well.
|
2022-02-23 15:08:44 +01:00
|
|
|
const useFeaturesSortState = createPersistentGlobalStateHook<IFeaturesSort>(
|
2022-02-08 12:06:25 +01:00
|
|
|
`${getBasePath()}:useFeaturesSort:v1`,
|
|
|
|
{ type: 'name' }
|
|
|
|
);
|
|
|
|
|
|
|
|
export const useFeaturesSort = (
|
2022-04-26 10:53:46 +02:00
|
|
|
features: FeatureSchema[]
|
2022-02-08 12:06:25 +01:00
|
|
|
): IFeaturesSortOutput => {
|
|
|
|
const [sort, setSort] = useFeaturesSortState();
|
|
|
|
|
|
|
|
const sorted = useMemo(() => {
|
|
|
|
return sortFeatures(features, sort);
|
|
|
|
}, [features, sort]);
|
|
|
|
|
|
|
|
return {
|
|
|
|
setSort,
|
|
|
|
sort,
|
|
|
|
sorted,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
export const createFeaturesFilterSortOptions =
|
|
|
|
(): IFeaturesFilterSortOption[] => {
|
|
|
|
return [
|
|
|
|
{ type: 'name', name: 'Name' },
|
|
|
|
{ type: 'type', name: 'Type' },
|
|
|
|
{ type: 'enabled', name: 'Enabled' },
|
|
|
|
{ type: 'stale', name: 'Stale' },
|
|
|
|
{ type: 'created', name: 'Created' },
|
|
|
|
{ type: 'last-seen', name: 'Last seen' },
|
|
|
|
{ type: 'project', name: 'Project' },
|
|
|
|
];
|
|
|
|
};
|
|
|
|
|
2022-03-25 15:30:52 +01:00
|
|
|
const sortAscendingFeatures = (
|
2022-04-26 10:53:46 +02:00
|
|
|
features: FeatureSchema[],
|
2022-02-08 12:06:25 +01:00
|
|
|
sort: IFeaturesSort
|
2022-04-26 10:53:46 +02:00
|
|
|
): FeatureSchema[] => {
|
2022-02-08 12:06:25 +01:00
|
|
|
switch (sort.type) {
|
|
|
|
case 'enabled':
|
|
|
|
return sortByEnabled(features);
|
|
|
|
case 'stale':
|
|
|
|
return sortByStale(features);
|
|
|
|
case 'created':
|
|
|
|
return sortByCreated(features);
|
|
|
|
case 'last-seen':
|
|
|
|
return sortByLastSeen(features);
|
|
|
|
case 'name':
|
|
|
|
return sortByName(features);
|
|
|
|
case 'project':
|
|
|
|
return sortByProject(features);
|
|
|
|
case 'type':
|
|
|
|
return sortByType(features);
|
2022-03-25 15:30:52 +01:00
|
|
|
case 'expired':
|
|
|
|
return sortByExpired(features);
|
|
|
|
case 'status':
|
|
|
|
return sortByStatus(features);
|
2022-02-08 12:06:25 +01:00
|
|
|
default:
|
|
|
|
console.error(`Unknown feature sort type: ${sort.type}`);
|
|
|
|
return features;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-03-25 15:30:52 +01:00
|
|
|
const sortFeatures = (
|
2022-04-26 10:53:46 +02:00
|
|
|
features: FeatureSchema[],
|
2022-03-25 15:30:52 +01:00
|
|
|
sort: IFeaturesSort
|
2022-04-26 10:53:46 +02:00
|
|
|
): FeatureSchema[] => {
|
2022-03-25 15:30:52 +01:00
|
|
|
const sorted = sortAscendingFeatures(features, sort);
|
|
|
|
|
|
|
|
if (sort.desc) {
|
|
|
|
return [...sorted].reverse();
|
|
|
|
}
|
|
|
|
|
|
|
|
return sorted;
|
|
|
|
};
|
|
|
|
|
2022-02-08 12:06:25 +01:00
|
|
|
const sortByEnabled = (
|
2022-04-26 10:53:46 +02:00
|
|
|
features: Readonly<FeatureSchema[]>
|
|
|
|
): FeatureSchema[] => {
|
2022-02-08 12:06:25 +01:00
|
|
|
return [...features].sort((a, b) =>
|
|
|
|
a.enabled === b.enabled ? 0 : a.enabled ? -1 : 1
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2022-04-26 10:53:46 +02:00
|
|
|
const sortByStale = (features: Readonly<FeatureSchema[]>): FeatureSchema[] => {
|
2022-02-08 12:06:25 +01:00
|
|
|
return [...features].sort((a, b) =>
|
|
|
|
a.stale === b.stale ? 0 : a.stale ? -1 : 1
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const sortByLastSeen = (
|
2022-04-26 10:53:46 +02:00
|
|
|
features: Readonly<FeatureSchema[]>
|
|
|
|
): FeatureSchema[] => {
|
2022-02-08 12:06:25 +01:00
|
|
|
return [...features].sort((a, b) =>
|
|
|
|
a.lastSeenAt && b.lastSeenAt
|
2022-04-26 10:53:46 +02:00
|
|
|
? compareNullableDates(b.lastSeenAt, a.lastSeenAt)
|
2022-03-01 10:35:20 +01:00
|
|
|
: a.lastSeenAt
|
|
|
|
? -1
|
|
|
|
: b.lastSeenAt
|
|
|
|
? 1
|
2022-04-26 10:53:46 +02:00
|
|
|
: compareNullableDates(b.createdAt, a.createdAt)
|
2022-02-08 12:06:25 +01:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const sortByCreated = (
|
2022-04-26 10:53:46 +02:00
|
|
|
features: Readonly<FeatureSchema[]>
|
|
|
|
): FeatureSchema[] => {
|
|
|
|
return [...features].sort((a, b) =>
|
|
|
|
compareNullableDates(b.createdAt, a.createdAt)
|
|
|
|
);
|
2022-02-08 12:06:25 +01:00
|
|
|
};
|
|
|
|
|
2022-04-26 10:53:46 +02:00
|
|
|
const sortByName = (features: Readonly<FeatureSchema[]>): FeatureSchema[] => {
|
2022-02-08 12:06:25 +01:00
|
|
|
return [...features].sort((a, b) => a.name.localeCompare(b.name));
|
|
|
|
};
|
|
|
|
|
|
|
|
const sortByProject = (
|
2022-04-26 10:53:46 +02:00
|
|
|
features: Readonly<FeatureSchema[]>
|
|
|
|
): FeatureSchema[] => {
|
2022-02-08 12:06:25 +01:00
|
|
|
return [...features].sort((a, b) => a.project.localeCompare(b.project));
|
|
|
|
};
|
|
|
|
|
2022-04-26 10:53:46 +02:00
|
|
|
const sortByType = (features: Readonly<FeatureSchema[]>): FeatureSchema[] => {
|
|
|
|
return [...features].sort((a, b) =>
|
|
|
|
a.type && b.type
|
|
|
|
? a.type.localeCompare(b.type)
|
|
|
|
: a.type
|
|
|
|
? 1
|
|
|
|
: b.type
|
|
|
|
? -1
|
|
|
|
: 0
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const compareNullableDates = (
|
|
|
|
a: Date | null | undefined,
|
|
|
|
b: Date | null | undefined
|
|
|
|
): number => {
|
|
|
|
return a && b ? a.getTime() - b.getTime() : a ? 1 : b ? -1 : 0;
|
2022-02-08 12:06:25 +01:00
|
|
|
};
|
2022-03-25 15:30:52 +01:00
|
|
|
const sortByExpired = (
|
2022-04-26 10:53:46 +02:00
|
|
|
features: Readonly<FeatureSchema[]>
|
|
|
|
): FeatureSchema[] => {
|
2022-03-25 15:30:52 +01:00
|
|
|
return [...features].sort((a, b) => {
|
|
|
|
const now = new Date();
|
2022-04-26 10:53:46 +02:00
|
|
|
const dateA = a.createdAt!;
|
|
|
|
const dateB = b.createdAt!;
|
2022-03-25 15:30:52 +01:00
|
|
|
|
|
|
|
const diffA = getDiffInDays(dateA, now);
|
|
|
|
const diffB = getDiffInDays(dateB, now);
|
|
|
|
|
2022-04-26 10:53:46 +02:00
|
|
|
if (!expired(diffA, a.type!) && expired(diffB, b.type!)) {
|
2022-03-25 15:30:52 +01:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2022-04-26 10:53:46 +02:00
|
|
|
if (expired(diffA, a.type!) && !expired(diffB, b.type!)) {
|
2022-03-25 15:30:52 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const expiration = toggleExpiryByTypeMap as Record<string, number>;
|
2022-04-26 10:53:46 +02:00
|
|
|
const expiredByA = a.type ? diffA - expiration[a.type] : 0;
|
|
|
|
const expiredByB = b.type ? diffB - expiration[b.type] : 0;
|
2022-03-25 15:30:52 +01:00
|
|
|
|
|
|
|
return expiredByB - expiredByA;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2022-04-26 10:53:46 +02:00
|
|
|
const sortByStatus = (features: Readonly<FeatureSchema[]>): FeatureSchema[] => {
|
2022-03-25 15:30:52 +01:00
|
|
|
return [...features].sort((a, b) => {
|
|
|
|
if (a.stale) {
|
|
|
|
return 1;
|
|
|
|
} else if (b.stale) {
|
|
|
|
return -1;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|