Skip to content

Commit ccd53ca

Browse files
abueideclaude
andauthored
fix(e2e-shared): track event flush status and show status codes (#1281)
Events now have three states: queued (orange), sent (green), failed (red) instead of just sent/unsent. The Logger tracks HTTP status codes per event. Connection status: green when events post successfully, orange when server responds with an error (e.g. 401), red when server is unreachable. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f3376f3 commit ccd53ca

3 files changed

Lines changed: 103 additions & 34 deletions

File tree

examples/e2e-shared/src/app/Home.tsx

Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
} from 'react-native';
1515
import { useAnalytics } from '@segment/analytics-react-native';
1616
import { segmentClient, logger, reconnect, onError } from './client';
17-
import type { EventEntry } from './plugins/Logger';
17+
import type { EventEntry, EventStatus } from './plugins/Logger';
1818

1919
if (
2020
Platform.OS === 'android' &&
@@ -23,11 +23,7 @@ if (
2323
UIManager.setLayoutAnimationEnabledExperimental(true);
2424
}
2525

26-
type ConnectionStatus =
27-
| 'connected'
28-
| 'network_error'
29-
| 'server_error'
30-
| 'unknown';
26+
type ConnectionStatus = 'connected' | 'network_error' | 'server_error' | 'idle';
3127

3228
interface ErrorInfo {
3329
message: string;
@@ -44,7 +40,7 @@ const Home = ({ navigation }: { navigation: any }) => {
4440
const [writeKey, setWriteKey] = useState('yup');
4541
const [events, setEvents] = useState<EventEntry[]>([]);
4642
const [connectionStatus, setConnectionStatus] =
47-
useState<ConnectionStatus>('unknown');
43+
useState<ConnectionStatus>('idle');
4844
const [lastError, setLastError] = useState<ErrorInfo | null>(null);
4945

5046
useEffect(() => {
@@ -55,8 +51,8 @@ const Home = ({ navigation }: { navigation: any }) => {
5551
useEffect(() => {
5652
const unsub = onError((error: any) => {
5753
const statusCode = error?.statusCode ?? -1;
58-
const isServerError = statusCode > 0;
59-
setConnectionStatus(isServerError ? 'server_error' : 'network_error');
54+
const hasServerResponse = statusCode > 0;
55+
setConnectionStatus(hasServerResponse ? 'server_error' : 'network_error');
6056

6157
const message = error?.message ?? String(error);
6258
const parts = [message];
@@ -75,7 +71,17 @@ const Home = ({ navigation }: { navigation: any }) => {
7571

7672
useEffect(() => {
7773
if (autoFlush) {
78-
segmentClient.flush();
74+
let hadError = false;
75+
const unsub = onError(() => {
76+
hadError = true;
77+
});
78+
segmentClient.flush().then(() => {
79+
unsub();
80+
if (!hadError) {
81+
const hasSent = logger.getEvents().some((e) => e.status === 'sent');
82+
if (hasSent) setConnectionStatus('connected');
83+
}
84+
});
7985
}
8086
}, [autoFlush]);
8187

@@ -92,11 +98,23 @@ const Home = ({ navigation }: { navigation: any }) => {
9298
setAutoFlush(newValue);
9399
}, [autoFlush]);
94100

101+
const handleFlush = useCallback(async () => {
102+
let hadError = false;
103+
const unsub = onError(() => {
104+
hadError = true;
105+
});
106+
await flush();
107+
unsub();
108+
if (!hadError) {
109+
const hasSent = logger.getEvents().some((e) => e.status === 'sent');
110+
if (hasSent) setConnectionStatus('connected');
111+
}
112+
}, [flush]);
113+
95114
const handleReconnect = useCallback(() => {
96-
setConnectionStatus('unknown');
115+
setConnectionStatus('idle');
97116
setLastError(null);
98117
reconnect(writeKey);
99-
setConnectionStatus('connected');
100118
}, [writeKey]);
101119

102120
const toggleSection = (
@@ -106,8 +124,9 @@ const Home = ({ navigation }: { navigation: any }) => {
106124
setter((prev) => !prev);
107125
};
108126

109-
const sentCount = events.filter((e) => e.sent).length;
110-
const queuedCount = events.filter((e) => !e.sent).length;
127+
const sentCount = events.filter((e) => e.status === 'sent').length;
128+
const failedCount = events.filter((e) => e.status === 'failed').length;
129+
const queuedCount = events.filter((e) => e.status === 'queued').length;
111130

112131
const statusColor =
113132
connectionStatus === 'connected'
@@ -118,6 +137,13 @@ const Home = ({ navigation }: { navigation: any }) => {
118137
? colors.orange
119138
: colors.yellow;
120139

140+
const eventColor = (status: EventStatus) =>
141+
status === 'sent'
142+
? colors.green
143+
: status === 'failed'
144+
? colors.red
145+
: colors.orange;
146+
121147
return (
122148
<View style={styles.container}>
123149
<StatusBar barStyle="light-content" />
@@ -219,7 +245,7 @@ const Home = ({ navigation }: { navigation: any }) => {
219245
<View style={styles.buttonRow}>
220246
<TouchableOpacity
221247
style={[styles.wideButton, { backgroundColor: colors.pink }]}
222-
onPress={() => flush()}
248+
onPress={handleFlush}
223249
testID="BUTTON_FLUSH"
224250
>
225251
<Text style={styles.buttonText}>Flush</Text>
@@ -270,6 +296,12 @@ const Home = ({ navigation }: { navigation: any }) => {
270296
</Text>
271297
<Text style={styles.statLabel}>Queued</Text>
272298
</View>
299+
<View style={styles.stat}>
300+
<Text style={[styles.statNumber, { color: colors.red }]}>
301+
{failedCount}
302+
</Text>
303+
<Text style={styles.statLabel}>Failed</Text>
304+
</View>
273305
<View style={styles.stat}>
274306
<View
275307
style={[styles.statusDot, { backgroundColor: statusColor }]}
@@ -288,15 +320,7 @@ const Home = ({ navigation }: { navigation: any }) => {
288320

289321
{lastError && (
290322
<Text
291-
style={[
292-
styles.errorText,
293-
{
294-
color:
295-
connectionStatus === 'network_error'
296-
? colors.red
297-
: colors.orange,
298-
},
299-
]}
323+
style={[styles.errorText, { color: statusColor }]}
300324
numberOfLines={2}
301325
>
302326
{lastError.message}
@@ -325,17 +349,16 @@ const Home = ({ navigation }: { navigation: any }) => {
325349
<View
326350
style={[
327351
styles.eventDot,
328-
{
329-
backgroundColor: entry.sent
330-
? colors.green
331-
: colors.orange,
332-
},
352+
{ backgroundColor: eventColor(entry.status) },
333353
]}
334354
/>
335355
<Text style={styles.eventType}>{entry.type}</Text>
336356
<Text style={styles.eventName} numberOfLines={1}>
337357
{entry.name}
338358
</Text>
359+
{entry.statusCode !== undefined && (
360+
<Text style={styles.eventStatus}>{entry.statusCode}</Text>
361+
)}
339362
<Text style={styles.eventTime}>
340363
{new Date(entry.timestamp).toLocaleTimeString()}
341364
</Text>
@@ -481,6 +504,7 @@ const styles = StyleSheet.create({
481504
eventDot: { width: 8, height: 8, borderRadius: 4, marginRight: 8 },
482505
eventType: { color: '#aaa', fontSize: 11, width: 50 },
483506
eventName: { color: 'white', fontSize: 13, flex: 1, marginRight: 8 },
507+
eventStatus: { color: '#aaa', fontSize: 11, marginRight: 6 },
484508
eventTime: { color: '#888', fontSize: 11 },
485509
});
486510

examples/e2e-shared/src/app/client.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
createClient,
33
CountFlushPolicy,
44
SegmentClient,
5+
ErrorType,
56
} from '@segment/analytics-react-native';
67
import { Platform } from 'react-native';
78
import { Logger } from './plugins/Logger';
@@ -16,6 +17,9 @@ const errorListeners: ErrorListener[] = [];
1617

1718
function buildClient(writeKey: string): SegmentClient {
1819
const useProxy = writeKey === 'yup';
20+
let pendingDropCount = 0;
21+
let lastStatusCode: number | undefined;
22+
1923
const client = createClient({
2024
writeKey,
2125
maxBatchSize: 1000,
@@ -25,6 +29,12 @@ function buildClient(writeKey: string): SegmentClient {
2529
collectDeviceId: true,
2630
debug: true,
2731
errorHandler: (error: any) => {
32+
if (error?.type === ErrorType.EventsDropped) {
33+
pendingDropCount += error.metadata?.droppedCount ?? 0;
34+
}
35+
if (error?.statusCode !== undefined) {
36+
lastStatusCode = error.statusCode;
37+
}
2838
errorListeners.forEach((fn) => fn(error));
2939
},
3040
...(useProxy
@@ -47,8 +57,21 @@ function buildClient(writeKey: string): SegmentClient {
4757

4858
const originalFlush = client.flush.bind(client);
4959
client.flush = async () => {
60+
const queuedCount = logger
61+
.getEvents()
62+
.filter((e) => e.status === 'queued').length;
63+
pendingDropCount = 0;
64+
lastStatusCode = undefined;
65+
5066
await originalFlush();
51-
logger.markAllSent();
67+
68+
if (pendingDropCount > 0) {
69+
logger.markFailed(pendingDropCount, lastStatusCode);
70+
}
71+
const sentCount = queuedCount - pendingDropCount;
72+
if (sentCount > 0) {
73+
logger.markSent(sentCount);
74+
}
5275
};
5376

5477
return client;

examples/e2e-shared/src/app/plugins/Logger.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import {
44
SegmentEvent,
55
} from '@segment/analytics-react-native';
66

7+
export type EventStatus = 'queued' | 'sent' | 'failed';
8+
79
export type EventEntry = {
810
type: string;
911
name: string;
1012
timestamp: string;
11-
sent: boolean;
13+
status: EventStatus;
14+
statusCode?: number;
1215
};
1316

1417
type Listener = (events: EventEntry[]) => void;
@@ -26,15 +29,34 @@ export class Logger extends Plugin {
2629
type: event.type,
2730
name: this.getEventName(event),
2831
timestamp: event.timestamp ?? new Date().toISOString(),
29-
sent: false,
32+
status: 'queued',
3033
};
3134
this.events = [...this.events, entry];
3235
this.notify();
3336
return event;
3437
}
3538

36-
markAllSent() {
37-
this.events = this.events.map((e) => (e.sent ? e : { ...e, sent: true }));
39+
markSent(count: number) {
40+
this.updateQueued(count, 'sent', 200);
41+
}
42+
43+
markFailed(count: number, statusCode?: number) {
44+
this.updateQueued(count, 'failed', statusCode);
45+
}
46+
47+
private updateQueued(
48+
count: number,
49+
newStatus: EventStatus,
50+
statusCode?: number
51+
) {
52+
let remaining = count;
53+
this.events = this.events.map((e) => {
54+
if (remaining > 0 && e.status === 'queued') {
55+
remaining--;
56+
return { ...e, status: newStatus, statusCode };
57+
}
58+
return e;
59+
});
3860
this.notify();
3961
}
4062

0 commit comments

Comments
 (0)