[MAGE] Implement filter-dependent stats (#1490)

This commit is contained in:
Filip Sodić 2023-10-10 12:54:18 +02:00 committed by GitHub
parent fb39f86ce9
commit 3c45043728
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 118 additions and 116 deletions

View File

@ -1,6 +1,6 @@
app waspAi { app waspAi {
wasp: { wasp: {
version: "^0.11.0" version: "^0.11.5"
}, },
title: "MAGE - GPT Web App Generator ✨", title: "MAGE - GPT Web App Generator ✨",
head: [ head: [

View File

@ -45,43 +45,7 @@ export function Stats() {
}, {}); }, {});
}, [stats]); }, [stats]);
function getColorValue(colorName) { const filteredProjects = useMemo(() => {
return availableColors.find((color) => color.name === colorName).color;
}
function getStatusName(status) {
switch (status) {
case "in-progress":
return "inProgress";
case "success":
return "success";
case "failure":
return "error";
case "cancelled":
return "cancelled";
default:
return "idle";
}
}
function getStatusText(status) {
switch (status) {
case "in-progress":
return "In progress";
case "success":
return "Success";
case "failure":
return "Error";
case "cancelled":
return "Cancelled";
case "pending":
return "Pending";
default:
return "Unknown";
}
}
const filteredStats = useMemo(() => {
const filters = []; const filters = [];
if (filterOutExampleApps) { if (filterOutExampleApps) {
filters.push( filters.push(
@ -103,9 +67,9 @@ export function Stats() {
: []; : [];
}, [stats, stats?.projects, filterOutExampleApps, filterOutKnownUsers]); }, [stats, stats?.projects, filterOutExampleApps, filterOutKnownUsers]);
const limitedFilteredStats = useMemo(() => { const limitedFilteredProjects = useMemo(() => {
return filteredStats.slice(0, 1000); return filteredProjects.slice(0, 1000);
}, [filteredStats]); }, [filteredProjects]);
if (isLoading) { if (isLoading) {
return <p>Loading</p>; return <p>Loading</p>;
@ -119,41 +83,11 @@ export function Stats() {
return <p>Couldn't load stats</p>; return <p>Couldn't load stats</p>;
} }
const downloadStats = getDownloadStats(filteredProjects);
const downloadedPercentage = const downloadedPercentage =
Math.round(stats.downloadStats.downloadRatio * 10000) / 100; Math.round(downloadStats.downloadRatio * 10000) / 100;
function getFormattedDiff(start, end) {
const diff = (end - start) / 1000;
const minutes = Math.round(diff / 60);
const remainingSeconds = Math.round(diff % 60);
return `${minutes}m ${remainingSeconds}s`;
}
function getDuration(stat) {
if (!logsByProjectId[stat.id]) {
return "-";
}
const logs = logsByProjectId[stat.id];
if (logs.length < 2) {
return "-";
}
const start = logs[logs.length - 1].createdAt;
const end = logs[0].createdAt;
return getFormattedDiff(start, end);
}
function getWaitingInQueueDuration(stat) {
if (!logsByProjectId[stat.id]) {
return "-";
}
const logs = logsByProjectId[stat.id];
if (logs.length < 2) {
return "-";
}
const start = stat.createdAt;
const end = logs[logs.length - 1].createdAt;
return getFormattedDiff(start, end);
}
return ( return (
<> <>
<Header /> <Header />
@ -192,7 +126,7 @@ export function Stats() {
{({ width, height }) => ( {({ width, height }) => (
<BarChart <BarChart
chartType={chartType.value} chartType={chartType.value}
projects={filteredStats} projects={filteredProjects}
width={width} width={width}
height={height} height={height}
/> />
@ -242,12 +176,12 @@ export function Stats() {
<span className="bg-slate-100 rounded-md px-2 py-1"> <span className="bg-slate-100 rounded-md px-2 py-1">
Generated:{" "} Generated:{" "}
<strong className="text-slate-800"> <strong className="text-slate-800">
{filteredStats.length} {filteredProjects.length}
</strong> </strong>
</span> </span>
<span className="bg-slate-100 rounded-md px-2 py-1"> <span className="bg-slate-100 rounded-md px-2 py-1">
Downlaoded:{" "} Downlaoded:{" "}
<strong className="text-slate-800">{`${stats.downloadStats.projectsDownloaded} (${downloadedPercentage}%)`}</strong> <strong className="text-slate-800">{`${downloadStats.projectsDownloaded} (${downloadedPercentage}%)`}</strong>
</span> </span>
</p> </p>
</div> </div>
@ -275,29 +209,29 @@ export function Stats() {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{limitedFilteredStats.map((stat) => ( {filteredProjects.map((project) => (
<tr className="bg-white border-b" key={stat.id}> <tr className="bg-white border-b" key={project.id}>
<th <th
scope="row" scope="row"
className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap flex items-center gap-2" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap flex items-center gap-2"
> >
<Color value={getColorValue(stat.primaryColor)} />{" "} <Color value={getColorValue(project.primaryColor)} />{" "}
<span <span
title={stat.description} title={project.description}
className="max-w-[250px] overflow-hidden overflow-ellipsis" className="max-w-[250px] overflow-hidden overflow-ellipsis"
> >
{stat.name} {project.name}
</span>{" "} </span>{" "}
<span className="flex gap-1"> <span className="flex gap-1">
{stat.user && ( {project.user && (
<span title={stat.user.email}> <span title={project.user.email}>
<WaspIcon className="w-5 h-5" /> <WaspIcon className="w-5 h-5" />
</span> </span>
)} )}
{stat.zipDownloadedAt && ( {project.zipDownloadedAt && (
<span <span
title={`Downlaoded ${format( title={`Downlaoded ${format(
stat.zipDownloadedAt project.zipDownloadedAt
)}`} )}`}
className="w-5 h-5 bg-sky-100 rounded-full flex items-center justify-center text-sky-800 border border-sky-200" className="w-5 h-5 bg-sky-100 rounded-full flex items-center justify-center text-sky-800 border border-sky-200"
> >
@ -307,28 +241,28 @@ export function Stats() {
</span> </span>
</th> </th>
<td className="px-6 py-4"> <td className="px-6 py-4">
<StatusPill status={getStatusName(stat.status)} sm> <StatusPill status={getStatusName(project.status)} sm>
{getStatusText(stat.status)} {getStatusText(project.status)}
</StatusPill> </StatusPill>
</td> </td>
<td <td
className="px-6 py-4" className="px-6 py-4"
title={`${stat.createdAt.toLocaleDateString()} ${stat.createdAt.toLocaleTimeString()}`} title={`${project.createdAt.toLocaleDateString()} ${project.createdAt.toLocaleTimeString()}`}
> >
{format(stat.createdAt)} {format(project.createdAt)}
</td> </td>
<td className="px-6 py-4"> <td className="px-6 py-4">
{getWaitingInQueueDuration(stat)} &rarr;{" "} {getWaitingInQueueDuration(project, logsByProjectId)}{" "}
{getDuration(stat)} &rarr; {getDuration(project, logsByProjectId)}
</td> </td>
<td <td
className={`px-6 py-4 creativity-${stat.creativityLevel}`} className={`px-6 py-4 creativity-${project.creativityLevel}`}
> >
{stat.creativityLevel} {project.creativityLevel}
</td> </td>
<td className="px-6 py-4"> <td className="px-6 py-4">
<Link <Link
to={`/result/${stat.id}`} to={`/result/${project.id}`}
className="font-medium text-sky-600 hover:underline" className="font-medium text-sky-600 hover:underline"
> >
View the app &rarr; View the app &rarr;
@ -338,7 +272,7 @@ export function Stats() {
))} ))}
</tbody> </tbody>
</table> </table>
{filteredStats.length > limitedFilteredStats.length && ( {filteredProjects.length > limitedFilteredProjects.length && (
<div className="relative px-6 py-3 bg-gray-50 text-sm text-slate-500 text-center"> <div className="relative px-6 py-3 bg-gray-50 text-sm text-slate-500 text-center">
Showing only the latest 1000 projects Showing only the latest 1000 projects
</div> </div>
@ -350,3 +284,91 @@ export function Stats() {
</> </>
); );
} }
function getDownloadStats(projects) {
const projectsAfterDownloadTracking = projects.filter(
(project) =>
// This is the time of the first recorded download (after we rolled out download tracking).
project.createdAt > new Date("2023-07-14 10:36:45.12") &&
project.status === "success"
);
const downloadedProjects = projectsAfterDownloadTracking.filter(
(project) => project.zipDownloadedAt !== null
);
return {
projectsDownloaded: downloadedProjects.length,
downloadRatio:
projectsAfterDownloadTracking.length > 0
? downloadedProjects.length / projectsAfterDownloadTracking.length
: 0,
};
}
function getFormattedDiff(start, end) {
const diff = (end - start) / 1000;
const minutes = Math.round(diff / 60);
const remainingSeconds = Math.round(diff % 60);
return `${minutes}m ${remainingSeconds}s`;
}
function getDuration(stat, logsByProjectId) {
if (!logsByProjectId[stat.id]) {
return "-";
}
const logs = logsByProjectId[stat.id];
if (logs.length < 2) {
return "-";
}
const start = logs[logs.length - 1].createdAt;
const end = logs[0].createdAt;
return getFormattedDiff(start, end);
}
function getWaitingInQueueDuration(stat, logsByProjectId) {
if (!logsByProjectId[stat.id]) {
return "-";
}
const logs = logsByProjectId[stat.id];
if (logs.length < 2) {
return "-";
}
const start = stat.createdAt;
const end = logs[logs.length - 1].createdAt;
return getFormattedDiff(start, end);
}
function getColorValue(colorName) {
return availableColors.find((color) => color.name === colorName).color;
}
function getStatusName(status) {
switch (status) {
case "in-progress":
return "inProgress";
case "success":
return "success";
case "failure":
return "error";
case "cancelled":
return "cancelled";
default:
return "idle";
}
}
function getStatusText(status) {
switch (status) {
case "in-progress":
return "In progress";
case "success":
return "Success";
case "failure":
return "Error";
case "cancelled":
return "Cancelled";
case "pending":
return "Pending";
default:
return "Unknown";
}
}

View File

@ -215,12 +215,9 @@ export const getStats = (async (_args, context) => {
}, },
}); });
const downloadStats = getDownloadStats(projects);
return { return {
projects, projects,
latestProjectsWithLogs, latestProjectsWithLogs,
downloadStats,
}; };
}) satisfies GetStats<{}>; }) satisfies GetStats<{}>;
@ -229,20 +226,3 @@ export const getNumProjects = (async (_args, context) => {
const numProjects = await Project.count(); const numProjects = await Project.count();
return numProjects; return numProjects;
}) satisfies GetNumProjects<{}>; }) satisfies GetNumProjects<{}>;
function getDownloadStats(projects: Project[]) {
const projectsAfterDownloadTracking = projects.filter(
(project) =>
// This is the time of the first recorded download (after we rolled out download tracking).
project.createdAt > new Date("2023-07-14 10:36:45.12") &&
project.status === "success"
);
const downloadedProjects = projectsAfterDownloadTracking.filter(
(project) => project.zipDownloadedAt !== null
);
return {
projectsDownloaded: downloadedProjects.length,
downloadRatio:
downloadedProjects.length / projectsAfterDownloadTracking.length,
};
}