[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 {
wasp: {
version: "^0.11.0"
version: "^0.11.5"
},
title: "MAGE - GPT Web App Generator ✨",
head: [

View File

@ -45,43 +45,7 @@ export function Stats() {
}, {});
}, [stats]);
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";
}
}
const filteredStats = useMemo(() => {
const filteredProjects = useMemo(() => {
const filters = [];
if (filterOutExampleApps) {
filters.push(
@ -103,9 +67,9 @@ export function Stats() {
: [];
}, [stats, stats?.projects, filterOutExampleApps, filterOutKnownUsers]);
const limitedFilteredStats = useMemo(() => {
return filteredStats.slice(0, 1000);
}, [filteredStats]);
const limitedFilteredProjects = useMemo(() => {
return filteredProjects.slice(0, 1000);
}, [filteredProjects]);
if (isLoading) {
return <p>Loading</p>;
@ -119,41 +83,11 @@ export function Stats() {
return <p>Couldn't load stats</p>;
}
const downloadStats = getDownloadStats(filteredProjects);
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 (
<>
<Header />
@ -192,7 +126,7 @@ export function Stats() {
{({ width, height }) => (
<BarChart
chartType={chartType.value}
projects={filteredStats}
projects={filteredProjects}
width={width}
height={height}
/>
@ -242,12 +176,12 @@ export function Stats() {
<span className="bg-slate-100 rounded-md px-2 py-1">
Generated:{" "}
<strong className="text-slate-800">
{filteredStats.length}
{filteredProjects.length}
</strong>
</span>
<span className="bg-slate-100 rounded-md px-2 py-1">
Downlaoded:{" "}
<strong className="text-slate-800">{`${stats.downloadStats.projectsDownloaded} (${downloadedPercentage}%)`}</strong>
<strong className="text-slate-800">{`${downloadStats.projectsDownloaded} (${downloadedPercentage}%)`}</strong>
</span>
</p>
</div>
@ -275,29 +209,29 @@ export function Stats() {
</tr>
</thead>
<tbody>
{limitedFilteredStats.map((stat) => (
<tr className="bg-white border-b" key={stat.id}>
{filteredProjects.map((project) => (
<tr className="bg-white border-b" key={project.id}>
<th
scope="row"
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
title={stat.description}
title={project.description}
className="max-w-[250px] overflow-hidden overflow-ellipsis"
>
{stat.name}
{project.name}
</span>{" "}
<span className="flex gap-1">
{stat.user && (
<span title={stat.user.email}>
{project.user && (
<span title={project.user.email}>
<WaspIcon className="w-5 h-5" />
</span>
)}
{stat.zipDownloadedAt && (
{project.zipDownloadedAt && (
<span
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"
>
@ -307,28 +241,28 @@ export function Stats() {
</span>
</th>
<td className="px-6 py-4">
<StatusPill status={getStatusName(stat.status)} sm>
{getStatusText(stat.status)}
<StatusPill status={getStatusName(project.status)} sm>
{getStatusText(project.status)}
</StatusPill>
</td>
<td
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 className="px-6 py-4">
{getWaitingInQueueDuration(stat)} &rarr;{" "}
{getDuration(stat)}
{getWaitingInQueueDuration(project, logsByProjectId)}{" "}
&rarr; {getDuration(project, logsByProjectId)}
</td>
<td
className={`px-6 py-4 creativity-${stat.creativityLevel}`}
className={`px-6 py-4 creativity-${project.creativityLevel}`}
>
{stat.creativityLevel}
{project.creativityLevel}
</td>
<td className="px-6 py-4">
<Link
to={`/result/${stat.id}`}
to={`/result/${project.id}`}
className="font-medium text-sky-600 hover:underline"
>
View the app &rarr;
@ -338,7 +272,7 @@ export function Stats() {
))}
</tbody>
</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">
Showing only the latest 1000 projects
</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 {
projects,
latestProjectsWithLogs,
downloadStats,
};
}) satisfies GetStats<{}>;
@ -229,20 +226,3 @@ export const getNumProjects = (async (_args, context) => {
const numProjects = await Project.count();
return numProjects;
}) 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,
};
}