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
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,10 @@ export function InboxSignalsTab() {
bulk_size: 1,
rank: preMutationRank,
list_size: preMutationListSize,
// Snapshot priority/actionability from the pre-mutation target —
// by the time this fires the report has been removed from `reports`.
priority: target?.priority ?? null,
actionability: target?.actionability ?? null,
...(isSnooze
? {}
: {
Expand Down Expand Up @@ -492,6 +496,38 @@ export function InboxSignalsTab() {
if (isLoading) return;
if (inboxViewedFiredRef.current) return;
inboxViewedFiredRef.current = true;
const priorityCounts = {
P0: 0,
P1: 0,
P2: 0,
P3: 0,
P4: 0,
unknown: 0,
};
const actionabilityCounts = {
immediately_actionable: 0,
requires_human_input: 0,
not_actionable: 0,
unknown: 0,
};
for (const r of reports) {
const p = r.priority;
if (p === "P0" || p === "P1" || p === "P2" || p === "P3" || p === "P4") {
priorityCounts[p] += 1;
} else {
priorityCounts.unknown += 1;
}
const a = r.actionability;
if (
a === "immediately_actionable" ||
a === "requires_human_input" ||
a === "not_actionable"
) {
actionabilityCounts[a] += 1;
} else {
actionabilityCounts.unknown += 1;
}
}
track(ANALYTICS_EVENTS.INBOX_VIEWED, {
report_count: reports.length,
total_count: totalCount,
Expand All @@ -501,11 +537,23 @@ export function InboxSignalsTab() {
status_filter_count: statusFilter.length,
is_empty: totalCount === 0,
is_gated_due_to_scale: false,
priority_p0_count: priorityCounts.P0,
priority_p1_count: priorityCounts.P1,
priority_p2_count: priorityCounts.P2,
priority_p3_count: priorityCounts.P3,
priority_p4_count: priorityCounts.P4,
priority_unknown_count: priorityCounts.unknown,
actionability_immediately_actionable_count:
actionabilityCounts.immediately_actionable,
actionability_requires_human_input_count:
actionabilityCounts.requires_human_input,
actionability_not_actionable_count: actionabilityCounts.not_actionable,
actionability_unknown_count: actionabilityCounts.unknown,
});
}, [
isInboxView,
isLoading,
reports.length,
reports,
totalCount,
readyCount,
hasActiveFilters,
Expand Down
10 changes: 10 additions & 0 deletions apps/code/src/renderer/features/inbox/components/InboxView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ export function InboxView() {
status_filter_count: 0,
is_empty: true,
is_gated_due_to_scale: true,
priority_p0_count: 0,
priority_p1_count: 0,
priority_p2_count: 0,
priority_p3_count: 0,
priority_p4_count: 0,
priority_unknown_count: 0,
actionability_immediately_actionable_count: 0,
actionability_requires_human_input_count: 0,
actionability_not_actionable_count: 0,
actionability_unknown_count: 0,
});
}, [isGatedDueToScale]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,13 @@ interface ReportDetailPaneProps {
suppressDisabledReason: string | null;
isDismissMutationPending?: boolean;
onReportAction?: (
action: Omit<InboxReportActionProperties, "rank" | "list_size">,
action: Omit<
InboxReportActionProperties,
"rank" | "list_size" | "priority" | "actionability"
> & {
priority?: string | null;
actionability?: string | null;
},
) => void;
onScroll?: () => void;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,14 @@ interface SignalsToolbarProps {
isDismissMutationPending?: boolean;
/** Optional analytics callback fired when a bulk action succeeds. */
onReportAction?: (
action: Omit<InboxReportActionProperties, "rank" | "list_size"> & {
action: Omit<
InboxReportActionProperties,
"rank" | "list_size" | "priority" | "actionability"
> & {
rank?: number;
list_size?: number;
priority?: string | null;
actionability?: string | null;
},
) => void;
}
Expand Down Expand Up @@ -340,14 +345,36 @@ export function SignalsToolbar({

/**
* Snapshot of the visible list captured at action-confirm time, so analytics
* record rank + list_size as the user saw them — not the post-mutation refetch.
* record rank/list_size/priority/actionability as the user saw them — not the
* post-mutation refetch (by then the affected reports are gone).
*/
type ListSnapshotEntry = {
rank: number;
title: string | null;
createdAt: string | null;
priority: string | null;
actionability: string | null;
};
type ListSnapshot = {
rankById: Map<string, number>;
byId: Map<string, ListSnapshotEntry>;
listSize: number;
};
const snapshotList = (): ListSnapshot => ({
rankById: new Map(reports.map((r, i) => [r.id, i] as const)),
byId: new Map(
reports.map(
(r, i) =>
[
r.id,
{
rank: i,
title: r.title,
createdAt: r.created_at,
priority: r.priority ?? null,
actionability: r.actionability ?? null,
} satisfies ListSnapshotEntry,
] as const,
),
),
listSize: reports.length,
});

Expand All @@ -358,10 +385,9 @@ export function SignalsToolbar({
) => {
if (!onReportAction) return;
const isBulk = targetIds.length > 1;
const reportById = new Map(reports.map((r) => [r.id, r]));
for (const reportId of targetIds) {
const target = reportById.get(reportId);
const createdAt = target?.created_at;
const entry = snapshot.byId.get(reportId);
const createdAt = entry?.createdAt;
const ageMs = createdAt
? Date.now() - new Date(createdAt).getTime()
: Number.NaN;
Expand All @@ -370,14 +396,16 @@ export function SignalsToolbar({
: 0;
onReportAction({
report_id: reportId,
report_title: target?.title ?? null,
report_title: entry?.title ?? null,
report_age_hours: reportAgeHours,
action_type: actionType,
surface: "toolbar",
is_bulk: isBulk,
bulk_size: targetIds.length,
rank: snapshot.rankById.get(reportId) ?? -1,
rank: entry?.rank ?? -1,
list_size: snapshot.listSize,
priority: entry?.priority ?? null,
actionability: entry?.actionability ?? null,
});
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ interface OpenInfo {
reportId: string;
reportTitle: string | null;
reportCreatedAt: string | null;
reportPriority: string | null;
reportActionability: string | null;
openedAt: number;
rank: number;
listSize: number;
Expand All @@ -32,16 +34,22 @@ export interface InboxEngagementTracker {
/**
* Fires INBOX_REPORT_ACTION for the current open or an explicit report id.
*
* `rank` and `list_size` default to the live tracker state. Callers that fire after an
* async mutation (bulk dismiss/delete/snooze/reingest, single-report dismiss confirm)
* should snapshot the pre-mutation values and pass them through — by the time the
* promise resolves the visible list has usually been re-queried without the affected
* report.
* `rank`, `list_size`, `priority`, and `actionability` default to the live tracker
* state (or a lookup in the visible list for non-current reports). Callers that fire
* after an async mutation (bulk dismiss/delete/snooze/reingest, single-report dismiss
* confirm) should snapshot the pre-mutation values and pass them through — by the
* time the promise resolves the visible list has usually been re-queried without the
* affected report.
*/
signalAction(
action: Omit<InboxReportActionProperties, "rank" | "list_size"> & {
action: Omit<
InboxReportActionProperties,
"rank" | "list_size" | "priority" | "actionability"
> & {
rank?: number;
list_size?: number;
priority?: string | null;
actionability?: string | null;
},
): void;
}
Expand Down Expand Up @@ -74,6 +82,8 @@ export function useInboxEngagementTracker(
report_id: info.reportId,
report_title: info.reportTitle,
report_age_hours: reportAgeHours(info.reportCreatedAt),
priority: info.reportPriority,
actionability: info.reportActionability,
time_spent_ms: Date.now() - info.openedAt,
scrolled: info.hasScrolled,
close_method: closeMethod,
Expand Down Expand Up @@ -103,6 +113,8 @@ export function useInboxEngagementTracker(
reportId: currentReportId,
reportTitle: report?.title ?? null,
reportCreatedAt: report?.created_at ?? null,
reportPriority: report?.priority ?? null,
reportActionability: report?.actionability ?? null,
openedAt: Date.now(),
rank,
listSize,
Expand All @@ -115,7 +127,8 @@ export function useInboxEngagementTracker(
report_title: info.reportTitle,
report_age_hours: reportAgeHours(info.reportCreatedAt),
status: report?.status ?? null,
priority: report?.priority ?? null,
priority: info.reportPriority,
actionability: info.reportActionability,
source_products: report?.source_products ?? [],
rank,
list_size: listSize,
Expand Down Expand Up @@ -152,6 +165,8 @@ export function useInboxEngagementTracker(
report_id: info.reportId,
report_title: info.reportTitle,
report_age_hours: reportAgeHours(info.reportCreatedAt),
priority: info.reportPriority,
actionability: info.reportActionability,
rank: info.rank,
list_size: info.listSize,
time_since_open_ms: Date.now() - info.openedAt,
Expand All @@ -160,32 +175,62 @@ export function useInboxEngagementTracker(

const signalAction = useCallback(
(
action: Omit<InboxReportActionProperties, "rank" | "list_size"> & {
action: Omit<
InboxReportActionProperties,
"rank" | "list_size" | "priority" | "actionability"
> & {
rank?: number;
list_size?: number;
priority?: string | null;
actionability?: string | null;
},
) => {
const info = openInfoRef.current;
const visibleReports = reportsRef.current;
const {
rank: rankOverride,
list_size: listSizeOverride,
priority: priorityOverride,
actionability: actionabilityOverride,
...rest
} = action;
// Prefer the live open-info snapshot for the current report; otherwise
// fall back to a one-shot visible-list lookup. Callers firing after an
// async mutation should pass pre-mutation overrides — by then the visible
// list has been re-queried without the affected report.
const currentInfo =
info && info.reportId === action.report_id ? info : null;
const matchedReport = currentInfo
? null
: (visibleReports.find((r) => r.id === action.report_id) ?? null);
const rank =
rankOverride !== undefined
? rankOverride
: info && info.reportId === action.report_id
? info.rank
: currentInfo
? currentInfo.rank
: visibleReports.findIndex((r) => r.id === action.report_id);
const listSize =
listSizeOverride !== undefined
? listSizeOverride
: visibleReports.length;
const priority =
priorityOverride !== undefined
? priorityOverride
: currentInfo
? currentInfo.reportPriority
: (matchedReport?.priority ?? null);
const actionability =
actionabilityOverride !== undefined
? actionabilityOverride
: currentInfo
? currentInfo.reportActionability
: (matchedReport?.actionability ?? null);
track(ANALYTICS_EVENTS.INBOX_REPORT_ACTION, {
...rest,
rank,
list_size: listSize,
priority,
actionability,
});
},
[],
Expand Down
19 changes: 19 additions & 0 deletions apps/code/src/shared/types/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,18 @@ export interface InboxViewedProperties {
is_empty: boolean;
/** True when the inbox is scale-gated (GatedDueToScalePane shown, data not loaded). */
is_gated_due_to_scale: boolean;
/** Breakdown of the visible report_count by priority (P0–P4, or "unknown"). */
priority_p0_count: number;
priority_p1_count: number;
priority_p2_count: number;
priority_p3_count: number;
priority_p4_count: number;
priority_unknown_count: number;
/** Breakdown of the visible report_count by actionability. */
actionability_immediately_actionable_count: number;
actionability_requires_human_input_count: number;
actionability_not_actionable_count: number;
actionability_unknown_count: number;
}

export interface InboxReportOpenedProperties {
Expand All @@ -462,6 +474,7 @@ export interface InboxReportOpenedProperties {
report_age_hours: number;
status: string | null;
priority: string | null;
actionability: string | null;
source_products: string[];
rank: number;
list_size: number;
Expand All @@ -473,6 +486,8 @@ export interface InboxReportClosedProperties {
report_id: string;
report_title: string | null;
report_age_hours: number;
priority: string | null;
actionability: string | null;
time_spent_ms: number;
scrolled: boolean;
close_method: InboxReportCloseMethod;
Expand All @@ -482,6 +497,8 @@ export interface InboxReportScrolledProperties {
report_id: string;
report_title: string | null;
report_age_hours: number;
priority: string | null;
actionability: string | null;
rank: number;
list_size: number;
time_since_open_ms: number;
Expand All @@ -491,6 +508,8 @@ export interface InboxReportActionProperties {
report_id: string;
report_title: string | null;
report_age_hours: number;
priority: string | null;
actionability: string | null;
action_type: InboxReportActionType;
surface: InboxReportActionSurface;
is_bulk: boolean;
Expand Down
Loading