Skip to content

Commit

Permalink
feat(panel): improve crash sorting on player drops page
Browse files Browse the repository at this point in the history
  • Loading branch information
tabarra committed Sep 29, 2024
1 parent d8f269f commit 5b5bb85
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 72 deletions.
2 changes: 1 addition & 1 deletion core/components/StatsManager/playerDrop/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export default class PlayerDropStatsManager {
}
return {
changes: allChanges,
crashTypes: crashTypes.toSortedKeysArray(true),
crashTypes: crashTypes.toSortedValuesArray(true),
dropTypes: dropTypes.toSortedValuesArray(true),
};
}
Expand Down
2 changes: 1 addition & 1 deletion docs/dev_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Legend:
- [x] add window selection buttons to drilldown card
- [x] when range is selected, single click should remove it
- [x] tune in swr caching/reloading behavior
- [ ] improve crash sorting
- [x] improve crash sorting
- change logic of backend to sort by count by default
- then on the frontend if it's `crashesSortByReason`, then array.slice.sort(...)
- copy the sort code from [](/core/components/StatsManager/statsUtils.ts#L87)
Expand Down
62 changes: 42 additions & 20 deletions panel/src/pages/PlayerDropsPage/DrilldownCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const DrilldownCardInner = function DrilldownCard({
displayLod,
}: DrilldownCardProps) {
const [crashesTargetLimit, setCrashesTargetLimit] = useState(50);
const [crashesGroupReasons, setCrashesGroupReasons] = useState(false);

//Window indicator
const windowStartDate = new Date(windowStart);
Expand Down Expand Up @@ -111,30 +112,51 @@ const DrilldownCardInner = function DrilldownCard({
<div className='hidden xs:block'><SkullIcon className="size-4" /></div>
<h2 className="font-mono text-sm">Crash Reasons</h2>
</div>
<Select
value={crashesTargetLimit.toString()}
onValueChange={(value) => setCrashesTargetLimit(parseInt(value))}
>
<SelectTrigger
className="w-32 h-6 px-3 py-1 text-sm"
<div className="flex gap-2">
<Select
value={crashesTargetLimit.toString()}
onValueChange={(value) => setCrashesTargetLimit(parseInt(value))}
>
<SelectValue placeholder="Filter by admin" />
</SelectTrigger>
<SelectContent className="px-0">
<SelectItem value={'50'} className="cursor-pointer">
Top 50
</SelectItem>
<SelectItem value={'100'} className="cursor-pointer">
Top 100
</SelectItem>
<SelectItem value={'0'} className="cursor-pointer">
Show All
</SelectItem>
</SelectContent>
</Select>
<SelectTrigger
className="w-32 h-6 px-3 py-1 text-sm"
>
<SelectValue placeholder="Filter by admin" />
</SelectTrigger>
<SelectContent className="px-0">
<SelectItem value={'50'} className="cursor-pointer">
Top ~50
</SelectItem>
<SelectItem value={'100'} className="cursor-pointer">
Top ~100
</SelectItem>
<SelectItem value={'0'} className="cursor-pointer">
Show All
</SelectItem>
</SelectContent>
</Select>
<Select
value={crashesGroupReasons.toString()}
onValueChange={(value) => setCrashesGroupReasons(value === 'true')}
>
<SelectTrigger
className="w-36 h-6 px-3 py-1 text-sm"
>
<SelectValue placeholder="Filter by admin" />
</SelectTrigger>
<SelectContent className="px-0">
<SelectItem value={'false'} className="cursor-pointer">
Sort by Count
</SelectItem>
<SelectItem value={'true'} className="cursor-pointer">
Group Reasons
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<DrilldownCrashesSubcard
crashTypes={windowData.crashTypes}
crashesGroupReasons={crashesGroupReasons}
crashesTargetLimit={crashesTargetLimit}
setCrashesTargetLimit={setCrashesTargetLimit}
/>
Expand Down
71 changes: 26 additions & 45 deletions panel/src/pages/PlayerDropsPage/DrilldownCrashesSubcard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,13 @@ import { PlayerDropsMessage } from "./PlayerDropsGenericSubcards";
import { compressMultipleCounter, splitPrefixedStrings } from "./utils";


type CrashTypeData = {
type CrashDatumData = {
key: string;
pctStr: string;
cntStr: string;
prefix: string | false;
suffix: string;
};
type CrashGroupData = {
key: string;
pctStr: number | null;
cntStr: number | null;
subgroup: CrashTypeData[];
};
type CrashDatumData = CrashTypeData | CrashGroupData;


type CrashTypeRowProps = {
datum: CrashDatumData;
Expand All @@ -28,36 +20,13 @@ type CrashTypeRowProps = {

function CrashTypeRow({ datum, isLast, isOdd }: CrashTypeRowProps) {
let dataCellNode = null;
if ('subgroup' in datum) {
dataCellNode = (
<>
<div className="block group-hover:hidden">
<span className="text-info-inline">
{datum.subgroup.length}x
</span> {' '}
<span className="text-warning-inline">
{datum.key}
</span>
</div>
<div className="hidden group-hover:block">
{datum.subgroup.map((crash) => (
<p>
<span className="text-muted-foreground/50">{crash.prefix}</span>
<span>{crash.suffix}</span>
</p>
))}
</div>
</>
);
if (datum.prefix) {
dataCellNode = <>
<span className="text-muted-foreground/50">{datum.prefix}</span>
<span>{datum.suffix}</span>
</>
} else {
if (datum.prefix) {
dataCellNode = <>
<span className="text-muted-foreground/50">{datum.prefix}</span>
<span>{datum.suffix}</span>
</>
} else {
dataCellNode = datum.suffix;
}
dataCellNode = datum.suffix;
}

return (
Expand All @@ -84,13 +53,15 @@ function CrashTypeRow({ datum, isLast, isOdd }: CrashTypeRowProps) {


type DrilldownCrashesSubcardProps = {
crashTypes: [string, number][];
crashTypes: [reasonType: string, count: number][];
crashesGroupReasons: boolean;
crashesTargetLimit: number;
setCrashesTargetLimit: (limit: number) => void;
};

export default function DrilldownCrashesSubcard({
crashTypes,
crashesGroupReasons,
crashesTargetLimit,
setCrashesTargetLimit
}: DrilldownCrashesSubcardProps) {
Expand All @@ -99,22 +70,32 @@ export default function DrilldownCrashesSubcard({
}

const crashesData = useMemo(() => {
//Sort the data - the default api sort is by count (NOTE: we are mutating the array)
if (crashesGroupReasons) {
crashTypes.sort((a, b) => a[0].localeCompare(b[0]));
} else {
crashTypes.sort((a, b) => b[1] - a[1]);
}

//Calculate the total crashes and compress the data
const totalCrashes = crashTypes.reduce((acc, [, cnt]) => acc + cnt, 0);
const { filteredIn, filteredOut } = crashesTargetLimit
? compressMultipleCounter(crashTypes, crashesTargetLimit)
? compressMultipleCounter(crashTypes, crashesTargetLimit, crashesGroupReasons)
: { filteredIn: crashTypes, filteredOut: false as const };
const processedStrings = splitPrefixedStrings(filteredIn.map(([str, cnt]) => str));

//Prepare the display data
const display: CrashDatumData[] = [];
let displayCrashCount = 0;
for (let index = 0; index < filteredIn.length; index++) {
const [crashType, crashCount] = filteredIn[index];
for (let i = 0; i < filteredIn.length; i++) {
const [crashType, crashCount] = filteredIn[i];
displayCrashCount += crashCount;
const fraction = (crashCount / totalCrashes);
display.push({
key: crashType,
pctStr: numberToLocaleString((crashCount / totalCrashes) * 100, 1) + '%',
pctStr: numberToLocaleString(fraction * 100, 1) + '%',
cntStr: numberToLocaleString(crashCount),
...processedStrings[index],
...processedStrings[i],
});
}
return {
Expand All @@ -126,7 +107,7 @@ export default function DrilldownCrashesSubcard({
}
};

}, [crashTypes, crashesTargetLimit, setCrashesTargetLimit]);
}, [crashTypes, crashesGroupReasons, crashesTargetLimit, setCrashesTargetLimit]);

return (
<table className="w-full px-4 pt-2">
Expand Down
17 changes: 12 additions & 5 deletions panel/src/pages/PlayerDropsPage/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,12 @@ export type PrefixedString = {
/**
* Compress MultipleCounter array up to a desired length by dropping the elements with fewer counts
*/
export const compressMultipleCounter = (data: [string, number][], targetLength: number) => {
export const compressMultipleCounter = (
data: [reasonType: string, count: number][],
targetLength: number,
crashesGroupReasons: boolean
) => {
//Cutoff count
if (data.length <= targetLength) {
return {
filteredIn: data,
Expand All @@ -131,16 +136,18 @@ export const compressMultipleCounter = (data: [string, number][], targetLength:

let filteredOutTypes = 0;
let filteredOutCounts = 0;
const filteredIn: [string, number][] = [];
for (const [key, count] of data) {
if (count >= cutoff) {
const filteredIn: [reasonType: string, count: number][] = [];
for (let i = 0; i < data.length; i++) {
const [key, count] = data[i];
const shouldDisplay = crashesGroupReasons ? count >= cutoff : i < targetLength;
if (shouldDisplay) {
filteredIn.push([key, count]);
} else {
filteredOutTypes++;
filteredOutCounts += count;
}
}
filteredIn.sort((a, b) => a[0].localeCompare(b[0]));

return {
filteredIn,
filteredOut: !!filteredOutTypes && {
Expand Down

0 comments on commit 5b5bb85

Please sign in to comment.