feat(engagement): replace Graph email counts with real-time mimecast data
- Broaden mimecast retention from 30 days to 18 months rolling
- Re-enable mimecast-sync schedule (was disabled since March 17)
- Full sync triggered: 35,559 messages loaded for last 30 days
- Users list API: LATERAL join on mimecast_messages for emails_sent/received
- User detail API: add emails{d7,d30,d90} field from mimecast
- Engagement page: prefer mimecast email counts in detail panel sub-label
Graph API has 48-72hr reporting lag; mimecast is same-day
This commit is contained in:
parent
078e087d5f
commit
758b7e7f15
4 changed files with 38 additions and 7 deletions
|
|
@ -207,6 +207,22 @@ export async function GET(
|
|||
[user.email, periodDays]
|
||||
).catch(() => null);
|
||||
|
||||
// Mimecast real-time email counts
|
||||
const mimecastEmailsResult = await postgresClient.query(
|
||||
`SELECT
|
||||
COUNT(*) FILTER (WHERE LOWER(sender_address) = LOWER($1) AND direction IN ('outbound','internal') AND sent_datetime >= NOW() - INTERVAL '7 days') AS sent_d7,
|
||||
COUNT(*) FILTER (WHERE LOWER(sender_address) = LOWER($1) AND direction IN ('outbound','internal') AND sent_datetime >= NOW() - INTERVAL '30 days') AS sent_d30,
|
||||
COUNT(*) FILTER (WHERE LOWER(sender_address) = LOWER($1) AND direction IN ('outbound','internal') AND sent_datetime >= NOW() - INTERVAL '90 days') AS sent_d90,
|
||||
COUNT(*) FILTER (WHERE LOWER(recipient_address) = LOWER($1) AND direction IN ('inbound','internal') AND status IN ('archived','accepted') AND sent_datetime >= NOW() - INTERVAL '7 days') AS received_d7,
|
||||
COUNT(*) FILTER (WHERE LOWER(recipient_address) = LOWER($1) AND direction IN ('inbound','internal') AND status IN ('archived','accepted') AND sent_datetime >= NOW() - INTERVAL '30 days') AS received_d30,
|
||||
COUNT(*) FILTER (WHERE LOWER(recipient_address) = LOWER($1) AND direction IN ('inbound','internal') AND status IN ('archived','accepted') AND sent_datetime >= NOW() - INTERVAL '90 days') AS received_d90
|
||||
FROM mimecast_messages
|
||||
WHERE LOWER(sender_address) = LOWER($1)
|
||||
OR (LOWER(recipient_address) = LOWER($1) AND direction IN ('inbound','internal') AND status IN ('archived','accepted'))`,
|
||||
[user.email]
|
||||
).catch(() => null);
|
||||
const me = mimecastEmailsResult?.rows[0];
|
||||
|
||||
// After-hours meetings (5:30 PM – 7:00 AM America/New_York)
|
||||
const afterHoursMeetingsResult = await postgresClient.query(
|
||||
`SELECT COUNT(*) as count
|
||||
|
|
@ -496,6 +512,11 @@ export async function GET(
|
|||
messagesPct: totalMessages > 0 ? Math.round((afterHoursMessages / totalMessages) * 100) : 0,
|
||||
meetingsPct: totalMeetings > 0 ? Math.round((afterHoursMeetings / totalMeetings) * 100) : 0,
|
||||
},
|
||||
emails: me ? {
|
||||
d7: { sent: parseInt(me.sent_d7 ?? 0), received: parseInt(me.received_d7 ?? 0) },
|
||||
d30: { sent: parseInt(me.sent_d30 ?? 0), received: parseInt(me.received_d30 ?? 0) },
|
||||
d90: { sent: parseInt(me.sent_d90 ?? 0), received: parseInt(me.received_d90 ?? 0) },
|
||||
} : null,
|
||||
snapshots: snapshotsResult.rows,
|
||||
hours: hours
|
||||
? {
|
||||
|
|
|
|||
|
|
@ -82,8 +82,8 @@ export async function GET(request: NextRequest) {
|
|||
COALESCE(es.teams_calls, 0) as teams_calls,
|
||||
COALESCE(es.teams_meetings_attended, 0) as teams_meetings_attended,
|
||||
COALESCE(es.teams_meetings_organized, 0) as teams_meetings_organized,
|
||||
COALESCE(es.emails_sent, 0) as emails_sent,
|
||||
COALESCE(es.emails_received, 0) as emails_received,
|
||||
COALESCE(mc.emails_sent, 0) as emails_sent,
|
||||
COALESCE(mc.emails_received, 0) as emails_received,
|
||||
COALESCE(es.emails_read, 0) as emails_read,
|
||||
COALESCE(es.audio_duration_seconds, 0) as audio_duration_seconds,
|
||||
COALESCE(es.meeting_duration_seconds, 0) as meeting_duration_seconds,
|
||||
|
|
@ -139,6 +139,14 @@ export async function GET(request: NextRequest) {
|
|||
WHERE start_time >= NOW() - INTERVAL '${interval}'
|
||||
GROUP BY host_email
|
||||
) zm ON LOWER(r.email) = LOWER(zm.host_email)
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE LOWER(mm.sender_address) = LOWER(gu.email) AND mm.direction IN ('outbound', 'internal')) AS emails_sent,
|
||||
COUNT(*) FILTER (WHERE LOWER(mm.recipient_address) = LOWER(gu.email) AND mm.direction IN ('inbound', 'internal') AND mm.status IN ('archived', 'accepted')) AS emails_received
|
||||
FROM mimecast_messages mm
|
||||
WHERE (LOWER(mm.sender_address) = LOWER(gu.email) OR LOWER(mm.recipient_address) = LOWER(gu.email))
|
||||
AND mm.sent_datetime >= NOW() - INTERVAL '${interval}'
|
||||
) mc ON true
|
||||
WHERE gu.account_enabled = true
|
||||
AND LOWER(gu.email) LIKE '%@wulfconsulting.%'
|
||||
AND LOWER(gu.email) NOT LIKE '%#ext#%'
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ interface UserDetail {
|
|||
end_date_time: string | null;
|
||||
}>;
|
||||
}>;
|
||||
emails: Record<'d7' | 'd30' | 'd90', { sent: number; received: number }> | null;
|
||||
meetingCounts: { total: number; withClients: number };
|
||||
zoom: {
|
||||
calls: Record<'d7' | 'd30' | 'd90', { total: number; client: number; outbound: number; inbound: number; durationSeconds: number }>;
|
||||
|
|
@ -989,7 +990,7 @@ export default function EngagementPage() {
|
|||
},
|
||||
...(snap ? [{
|
||||
label: 'Messages',
|
||||
sub: `${snap.emails_sent} emails`,
|
||||
sub: userDetail.emails?.[pKey] ? `${userDetail.emails[pKey].sent} emails sent` : `${snap.emails_sent} emails`,
|
||||
value: snap.teams_chat_messages + snap.teams_private_messages,
|
||||
peerMax: userDetail.peerMax?.messages || (period === 'D7' ? 200 : period === 'D30' ? 800 : 2400),
|
||||
prevValue: null,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue