Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions migrations/20260730_000000_backfill_promise_categories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import type {
MigrateDownArgs,
MigrateUpArgs,
MongooseAdapter,
} from "@payloadcms/db-mongodb";

const DEFAULT_BACKFILL_BATCH_SIZE = 100;

type RawExtractionItem = {
category?: string;
checkMediaId?: string;
};

type RawAIExtraction = {
_id: unknown;
extractions?: RawExtractionItem[];
};

type RawPromise = {
_id: unknown;
meedanId?: string;
category?: string;
};

const getBackfillBatchSize = (): number => {
const raw = Number(
process.env.PAYLOAD_PROMISE_CATEGORY_BACKFILL_BATCH_SIZE,
);
return Number.isFinite(raw) && raw > 0
? Math.floor(raw)
: DEFAULT_BACKFILL_BATCH_SIZE;
};

const backfillPromiseCategories = async ({
payload,
}: MigrateUpArgs): Promise<void> => {
const db = payload.db as MongooseAdapter;
const aiExtractionsModel = db.collections["ai-extractions"];
const promisesModel = db.collections.promises;

if (!aiExtractionsModel || !promisesModel) {
throw new Error(
"Failed to resolve one or more collections for promise category backfill",
);
}

const categoryByCheckMediaId = new Map<string, string>();

const extractionDocs = (await aiExtractionsModel.find(
{},
{ extractions: 1 },
{ lean: true },
)) as RawAIExtraction[];

for (const extractionDoc of extractionDocs) {
for (const extraction of extractionDoc.extractions ?? []) {
const checkMediaId = extraction.checkMediaId?.trim();
const category = extraction.category?.trim();
if (checkMediaId && category) {
categoryByCheckMediaId.set(checkMediaId, category);
}
}
}

const batchSize = getBackfillBatchSize();
let lastID: unknown;
let updated = 0;

payload.logger.info({
msg: "promiseCategoryBackfill:: Starting migration backfill",
batchSize,
knownCategories: categoryByCheckMediaId.size,
});

while (true) {
const batch = (await promisesModel.find(
lastID ? { _id: { $gt: lastID } } : {},
{ meedanId: 1, category: 1 },
{ lean: true, limit: batchSize, sort: { _id: 1 } },
)) as RawPromise[];

if (batch.length === 0) {
break;
}

lastID = batch[batch.length - 1]?._id;

for (const promise of batch) {
const meedanId = promise.meedanId?.trim();
const category = meedanId ? categoryByCheckMediaId.get(meedanId) : undefined;

if (!category || promise.category?.trim()) {
continue;
}

await promisesModel.collection.updateOne(
{ _id: promise._id } as Record<string, unknown>,
{ $set: { category } },
);
updated += 1;
}
}

payload.logger.info({
msg: "promiseCategoryBackfill:: Completed migration backfill",
updated,
});
};

export async function up(args: MigrateUpArgs): Promise<void> {
await backfillPromiseCategories(args);
}

export async function down({ payload }: MigrateDownArgs): Promise<void> {
const db = payload.db as MongooseAdapter;
const promisesModel = db.collections.promises;

if (!promisesModel) {
throw new Error(
"Failed to resolve promises collection for migration rollback",
);
}

await promisesModel.collection.updateMany({}, { $unset: { category: "" } });
}
11 changes: 11 additions & 0 deletions src/collections/Promises.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,17 @@ export const Promises: CollectionConfig = {
position: "sidebar",
},
},
{
name: "category",
type: "text",
label: {
en: "Category",
fr: "Catégorie",
},
admin: {
position: "sidebar",
},
},
{
name: "politicalEntity",
type: "relationship",
Expand Down
2 changes: 1 addition & 1 deletion src/components/Hero/Hero.Client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export const HeroClient = ({ data }: HeroClientProps) => {
<Grid size={{ xs: 12, lg: 4 }}>
<Profile
name={entity.name}
headline={headline}
profileTitle={copy.profileTitle}
updatedAtLabel={copy.updatedAtLabel}
updatedAtDisplay={entity.updatedAtDisplay}
Expand All @@ -60,6 +59,7 @@ export const HeroClient = ({ data }: HeroClientProps) => {
</Grid>
<Grid size={{ xs: 12, lg: 8 }}>
<ProfileChart
headline={headline}
promiseLabel={copy.promiseLabel}
trailText={copy.trailText}
name={entity.name}
Expand Down
29 changes: 0 additions & 29 deletions src/components/Hero/Profile/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ type ProfileProps = {
updatedAtLabel: string;
updatedAtDisplay: string;
image: ProfileImage | null;
headline: {
tagline?: string;
name: string;
};
};

const MOBILE_SIZE = 149;
Expand All @@ -30,7 +26,6 @@ export const Profile = ({
updatedAtLabel,
updatedAtDisplay,
image,
headline,
}: ProfileProps) => {
const dateLine = [updatedAtLabel?.trim(), updatedAtDisplay]
.filter(Boolean)
Expand Down Expand Up @@ -98,30 +93,6 @@ export const Profile = ({
},
}}
>
{headline.tagline || headline.name ? (
<Typography
component="h1"
variant="h1"
sx={(theme) => ({
mb: theme.typography.pxToRem(12),
})}
>
{headline.tagline ? (
<>
<Typography
component="span"
variant="inherit"
sx={{ color: "#005DFD" }}
>
{headline.tagline}
</Typography>{" "}
{headline.name}
</>
) : (
headline.name
)}
</Typography>
) : null}
<Typography
variant="h5"
sx={(theme) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ export const ProgressChart = ({
const accentColor = statuses[0]?.color ?? "";

return (
<Stack spacing={2} alignItems="stretch" sx={{ px: 2 }}>
<Stack
spacing={2}
alignItems="stretch"
sx={{ px: 2 }}
height={"100%"}
justifyContent={"space-between"}
>
<Typography
variant="caption"
sx={(theme) => ({
Expand Down
43 changes: 41 additions & 2 deletions src/components/Hero/ProfileChart/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
"use client";

import { useMemo, useState } from "react";
import { Box } from "@mui/material";
import { useEffect, useMemo, useState } from "react";
import { Box, Typography } from "@mui/material";

import type { HeroChartGroup, HeroStatusSummary } from "../index";
import DesktopChart from "./DesktopChart";
import MobileChart from "./MobileChart";
import ProfileDetails from "./ProfileDetails";
import RectChart from "./RectChart";

const AUTO_TOGGLE_INTERVAL_MS = 6000;

type ProfileChartProps = {
headline: {
tagline?: string;
name: string;
};
promiseLabel: string;
trailText: string;
name: string;
Expand All @@ -22,6 +28,7 @@ type ProfileChartProps = {
};

export const ProfileChart = ({
headline,
promiseLabel,
trailText,
name,
Expand All @@ -34,6 +41,14 @@ export const ProfileChart = ({
}: ProfileChartProps) => {
const [showRectChart, setShowRectChart] = useState(false);

useEffect(() => {
const id = setInterval(() => {
setShowRectChart((prev) => !prev);
}, AUTO_TOGGLE_INTERVAL_MS);

return () => clearInterval(id);
}, [showRectChart]);

const orderedRectStatuses = useMemo(() => {
return statuses
.slice()
Expand All @@ -42,6 +57,30 @@ export const ProfileChart = ({

return (
<Box sx={{ display: "flex", flexDirection: "column", height: "100%" }}>
{headline.tagline || headline.name ? (
<Typography
component="h1"
variant="h1"
sx={(theme) => ({
mb: theme.typography.pxToRem(12),
})}
>
{headline.tagline ? (
<>
<Typography
component="span"
variant="inherit"
sx={{ color: "#005DFD" }}
>
{headline.tagline}
</Typography>{" "}
{headline.name}
</>
) : (
headline.name
)}
</Typography>
) : null}
<ProfileDetails
name={name}
position={position}
Expand Down
36 changes: 20 additions & 16 deletions src/components/KeyPromises/KeyPromises.Client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,33 +102,37 @@ const KeyPromiseCard = ({
sx={{ minHeight: { lg: theme.typography.pxToRem(387) } }}
>
<Grid>
<Typography
component="h3"
<Box
sx={(theme) => ({
typography: { xs: "h4", lg: "h2" },
p: "0 !important",
position: "relative",
overflow: "hidden",
display: "-webkit-box",
WebkitBoxOrient: "vertical",
WebkitLineClamp: { xs: 3, lg: 2 },
minHeight: { lg: theme.typography.pxToRem(56) },
textTransform: "none",
lineHeight: "48px",
pb: 1,
pb: theme.typography.pxToRem(20),
"&::after": {
content: '""',
width: theme.typography.pxToRem(72),
borderBottom: { lg: `8px solid ${statusColor}` },
marginTop: { lg: theme.typography.pxToRem(8) },
position: "absolute",
bottom: -2,
bottom: 0,
left: 0,
},
})}
>
{item.title}
</Typography>
<Typography
component="h3"
sx={(theme) => ({
typography: { xs: "h4", lg: "h2" },
p: "0 !important",
overflow: "hidden",
display: "-webkit-box",
WebkitBoxOrient: "vertical",
WebkitLineClamp: { xs: 3, lg: 2 },
minHeight: { lg: theme.typography.pxToRem(56) },
textTransform: "none",
lineHeight: "48px",
})}
>
{item.title}
</Typography>
</Box>
{item.description ? (
<Typography
variant="body2"
Expand Down
Loading
Loading