Skip to content

Commit

Permalink
feat(RawData): raw data view improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
jakeaturner committed May 23, 2024
1 parent 5870147 commit 74d7fc3
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 20 deletions.
24 changes: 15 additions & 9 deletions components/DebouncedInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use string";
import useDebounce from "@/hooks/useDebounce";
import { useEffect, useState } from "react";
import { Form, InputGroup } from "react-bootstrap";

interface DebouncedInputProps {
Expand All @@ -12,20 +12,26 @@ interface DebouncedInputProps {
}

const DebouncedInput: React.FC<DebouncedInputProps> = ({
value,
value: initialValue,
onChange,
placeholder = "Search...",
delay = 500,
delay = 250,
prefix = false,
...props
}) => {
const { debounce } = useDebounce();
const [value, setValue] = useState(initialValue);

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
onChange(e.target.value);
};
useEffect(() => {
setValue(initialValue);
}, [initialValue]);

const debouncedHandleInputChange = debounce(handleInputChange, delay);
useEffect(() => {
const timeout = setTimeout(() => {
onChange(value);
}, delay);

return () => clearTimeout(timeout);
}, [value]);

return (
<InputGroup>
Expand All @@ -38,7 +44,7 @@ const DebouncedInput: React.FC<DebouncedInputProps> = ({
type="text"
placeholder={placeholder}
value={value}
onChange={debouncedHandleInputChange}
onChange={(e) => setValue(e.target.value)}
/>
</InputGroup>
);
Expand Down
91 changes: 85 additions & 6 deletions components/RawDataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,70 @@ import {
createColumnHelper,
flexRender,
getCoreRowModel,
getSortedRowModel,
useReactTable,
FilterFn,
SortingFn,
sortingFns,
getFilteredRowModel
} from "@tanstack/react-table";
import {
RankingInfo,
rankItem,
compareItems
} from '@tanstack/match-sorter-utils'
import { useEffect, useState } from "react";
import DebouncedInput from "./DebouncedInput";
import { Search } from "react-bootstrap-icons";
import { AnalyticsRawData } from "@/lib/types";
import { truncateString } from "@/utils/text-helpers";
import { useGlobalContext } from "@/state/globalContext";

declare module '@tanstack/react-table' {
//add fuzzy filter to the filterFns
interface FilterFns {
fuzzy: FilterFn<unknown>
}
interface FilterMeta {
itemRank: RankingInfo
}
}

interface RawDataTableProps {
getData: (course_id: string) => Promise<AnalyticsRawData[]>;
getData: (
course_id: string,
privacy_mode: boolean
) => Promise<AnalyticsRawData[]>;
}

// Define a custom fuzzy filter function that will apply ranking info to rows (using match-sorter utils)
const fuzzyFilter: FilterFn<any> = (row, columnId, value, addMeta) => {
// Rank the item
const itemRank = rankItem(row.getValue(columnId), value)

// Store the itemRank info
addMeta({
itemRank,
})

// Return if the item should be filtered in/out
return itemRank.passed
}

// Define a custom fuzzy sort function that will sort by rank if the row has ranking information
const fuzzySort: SortingFn<any> = (rowA, rowB, columnId) => {
let dir = 0

// Only sort by rank if the column has ranking information
if (rowA.columnFiltersMeta[columnId]) {
dir = compareItems(
rowA.columnFiltersMeta[columnId]?.itemRank!,
rowB.columnFiltersMeta[columnId]?.itemRank!
)
}

// Provide an alphanumeric fallback for when the item ranks are equal
return dir === 0 ? sortingFns.alphanumeric(rowA, rowB, columnId) : dir
}

const RawDataTable: React.FC<RawDataTableProps> = ({ getData }) => {
Expand All @@ -26,11 +79,11 @@ const RawDataTable: React.FC<RawDataTableProps> = ({ getData }) => {
useEffect(() => {
if (!globalState.courseID) return;
fetchData();
}, [globalState.courseID]);
}, [globalState.courseID, globalState.ferpaPrivacy]);

async function fetchData() {
if (!globalState.courseID) return;
const _data = await getData(globalState.courseID);
const _data = await getData(globalState.courseID, globalState.ferpaPrivacy);
setData(_data);
}

Expand All @@ -39,11 +92,13 @@ const RawDataTable: React.FC<RawDataTableProps> = ({ getData }) => {
cell: (info) => (
<div>
<span className="tw-font-semibold">
{truncateString(info.getValue(), 40)}
{truncateString(info.getValue(), 30)}
</span>
</div>
),
header: "Student",
filterFn: 'fuzzy',
sortingFn: fuzzySort
}),
columnHelper.accessor("pagesAccessed", {
cell: (info) => <div>{info.getValue()}</div>,
Expand Down Expand Up @@ -71,16 +126,26 @@ const RawDataTable: React.FC<RawDataTableProps> = ({ getData }) => {
data: data,
columns: defaultColumns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
filterFns: {
fuzzy: fuzzyFilter
},
globalFilterFn: 'fuzzy',
onGlobalFilterChange: (value) => setSearchInput(value),
state: {
globalFilter: searchInput
},
});

return (
<div className="">
<div className="tw-flex tw-flex-row tw-w-1/4 tw-mb-2">
<DebouncedInput
value={searchInput}
onChange={setSearchInput}
onChange={(val) => setSearchInput(val)}
placeholder={"Search by name or email..."}
delay={500}
delay={250}
prefix
prefixElement={<Search />}
/>
Expand All @@ -91,18 +156,32 @@ const RawDataTable: React.FC<RawDataTableProps> = ({ getData }) => {
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id} className="tw-p-3 tw-text-sm tw-border-r">
<div onClick={header.column.getToggleSortingHandler()} className="!tw-cursor-pointer">
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
{
header.column.getIsSorted() ? header.column.getIsSorted() === 'desc' ? ' 🔽' : ' 🔼' : ''
}
</div>
</th>
))}
</tr>
))}
</thead>
<tbody>
{
table.getRowCount() === 0 && (
<tr>
<td colSpan={table.getAllColumns().length} className="tw-text-center tw-p-3">
No data available
</td>
</tr>
)
}
{table.getRowModel().rows.map((row) => (
<tr key={row.id} className="hover:tw-bg-slate-100">
{row.getVisibleCells().map((cell) => (
Expand Down
18 changes: 15 additions & 3 deletions lib/Analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ class Analytics {
}
}

public async getRawData(): Promise<AnalyticsRawData[]> {
public async getRawData(privacy_mode: boolean): Promise<AnalyticsRawData[]> {
try {
await connectDB();

Expand Down Expand Up @@ -526,11 +526,23 @@ class Analytics {
percentSeen: d.percent_seen,
coursePercent: d.course_percent,
// round percentile to two decimal places
classPercentile: Math.round(getPercentile(d.course_percent) * 100) / 100,
classPercentile:
Math.round(getPercentile(d.course_percent) * 100) / 100,
classQuartile: getQuartile(d.course_percent),
}));

return data;
if (privacy_mode) {
return data;
}

const decrypted = await Promise.all(
data.map(async (d) => ({
...d,
name: await decryptStudent(d.actor_id),
}))
);

return decrypted.sort((a, b) => a.name.localeCompare(b.name));
} catch (err) {
console.error(err);
return [];
Expand Down
4 changes: 2 additions & 2 deletions lib/analytics-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ export async function updateCourseAnalyticsSettings(
await analytics.updateCourseAnalyticsSettings(newSettings);
}

export async function getCourseRawData(course_id: string) {
export async function getCourseRawData(course_id: string, privacy_mode: boolean) {
const analytics = new Analytics(course_id);
const rawData = await analytics.getRawData();
const rawData = await analytics.getRawData(privacy_mode);
return rawData;
}
21 changes: 21 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"dependencies": {
"@lucia-auth/adapter-mongodb": "^1.0.3",
"@tanstack/match-sorter-utils": "^8.15.1",
"@tanstack/react-query": "^5.20.5",
"@tanstack/react-query-devtools": "^5.21.0",
"@tanstack/react-query-next-experimental": "^5.20.5",
Expand Down

0 comments on commit 74d7fc3

Please sign in to comment.