Skip to content

Commit eb4ba10

Browse files
committed
feat: add CloudWatch Logs Insights demo page for atomic table
Comprehensive demo exercising R1 features: - Sticky header with page-scroll pinning - Grid layout with explicit column widths - Row selection with multi-select checkboxes - 500 rows simulating CloudWatch Logs query results - StatusIndicator for log levels (INFO, WARN, ERROR, DEBUG)
1 parent f292ea3 commit eb4ba10

1 file changed

Lines changed: 266 additions & 0 deletions

File tree

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
/**
5+
* CloudWatch Logs Insights–style demo exercising R1 atomic table features:
6+
* - Sticky header with page-scroll pinning and horizontal-scroll alignment
7+
* - Grid columnLayout with explicit column widths (simulating query results)
8+
* - Row selection with multi-select checkboxes
9+
* - Keyboard-accessible horizontal scroll region (role='region' when overflow)
10+
* - Large dataset (500 rows) simulating query results
11+
*/
12+
import React, { useMemo, useState } from 'react';
13+
14+
import Box from '~components/box';
15+
import Checkbox from '~components/checkbox';
16+
import Container from '~components/container';
17+
import Header from '~components/header';
18+
import SegmentedControl from '~components/segmented-control';
19+
import SpaceBetween from '~components/space-between';
20+
import StatusIndicator from '~components/status-indicator';
21+
import TableBody from '~components/table-body';
22+
import TableBodyCell from '~components/table-body-cell';
23+
import TableHead from '~components/table-head';
24+
import TableHeaderCell from '~components/table-header-cell';
25+
import TableRoot, { TableRootProps } from '~components/table-root';
26+
import TableRow from '~components/table-row';
27+
28+
import styles from './styles.scss';
29+
30+
const LOG_LEVELS = ['INFO', 'WARN', 'ERROR', 'DEBUG'] as const;
31+
type LogLevel = (typeof LOG_LEVELS)[number];
32+
33+
const LAMBDA_FUNCTIONS = [
34+
'process-orders-prod',
35+
'user-auth-handler',
36+
'payment-gateway-v2',
37+
'notification-sender',
38+
'data-aggregator',
39+
'api-gateway-router',
40+
'cache-invalidator',
41+
'metric-collector',
42+
];
43+
44+
const MESSAGES = [
45+
'Request processed successfully',
46+
'Lambda execution started',
47+
'Database connection established',
48+
'Cache hit for key',
49+
'Retrying operation attempt',
50+
'Invalid input parameter',
51+
'Rate limit exceeded',
52+
'Connection timeout',
53+
'Memory usage threshold reached',
54+
'Authentication token validated',
55+
];
56+
57+
interface LogEntry {
58+
id: string;
59+
timestamp: string;
60+
requestId: string;
61+
level: LogLevel;
62+
functionName: string;
63+
message: string;
64+
duration: number;
65+
billedDuration: number;
66+
memoryUsed: number;
67+
memoryAllocated: number;
68+
}
69+
70+
const generateLogEntries = (count: number): LogEntry[] => {
71+
const baseTime = new Date('2026-09-27T10:00:00.000Z').getTime();
72+
return Array.from({ length: count }, (_, i) => {
73+
const offset = i * 127; // stagger timestamps
74+
const level = LOG_LEVELS[i % LOG_LEVELS.length];
75+
return {
76+
id: `log-${i}`,
77+
timestamp: new Date(baseTime + offset).toISOString(),
78+
requestId: `${crypto.randomUUID().slice(0, 8)}-${String(i).padStart(4, '0')}`,
79+
level,
80+
functionName: LAMBDA_FUNCTIONS[i % LAMBDA_FUNCTIONS.length],
81+
message: MESSAGES[i % MESSAGES.length],
82+
duration: Math.floor(Math.random() * 2000) + 50,
83+
billedDuration: Math.ceil((Math.floor(Math.random() * 2000) + 50) / 100) * 100,
84+
memoryUsed: Math.floor(Math.random() * 200) + 64,
85+
memoryAllocated: 256,
86+
};
87+
});
88+
};
89+
90+
const GRID_COLUMNS: ReadonlyArray<TableRootProps.ColumnDefinition> = [
91+
{ size: 40 }, // selection checkbox
92+
{ size: 220 }, // timestamp
93+
{ size: 140 }, // requestId
94+
{ size: 70 }, // level
95+
{ size: 180 }, // functionName
96+
{ size: 280 }, // message
97+
{ size: 100 }, // duration
98+
{ size: 120 }, // billedDuration
99+
{ size: 100 }, // memoryUsed
100+
{ size: 120 }, // memoryAllocated
101+
];
102+
103+
const TOTAL_COLUMNS = GRID_COLUMNS.length;
104+
void TOTAL_COLUMNS; // Column count is used for documentation/validation
105+
106+
function LevelIndicator({ level }: { level: LogLevel }) {
107+
const typeMap: Record<LogLevel, 'success' | 'warning' | 'error' | 'info'> = {
108+
INFO: 'info',
109+
WARN: 'warning',
110+
ERROR: 'error',
111+
DEBUG: 'success',
112+
};
113+
return <StatusIndicator type={typeMap[level]}>{level}</StatusIndicator>;
114+
}
115+
116+
export default function CloudWatchLogsDemo() {
117+
const [rowCount, setRowCount] = useState<'100' | '500' | '1000'>('500');
118+
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
119+
120+
const logs = useMemo(() => generateLogEntries(parseInt(rowCount, 10)), [rowCount]);
121+
122+
const allSelected = logs.length > 0 && selectedIds.size === logs.length;
123+
const someSelected = selectedIds.size > 0 && selectedIds.size < logs.length;
124+
125+
const handleSelectAll = () => {
126+
if (allSelected) {
127+
setSelectedIds(new Set());
128+
} else {
129+
setSelectedIds(new Set(logs.map(l => l.id)));
130+
}
131+
};
132+
133+
const handleSelectRow = (id: string, checked: boolean) => {
134+
setSelectedIds(prev => {
135+
const next = new Set(prev);
136+
if (checked) {
137+
next.add(id);
138+
} else {
139+
next.delete(id);
140+
}
141+
return next;
142+
});
143+
};
144+
145+
const columnLayout: TableRootProps.ColumnLayout = { type: 'grid', columns: GRID_COLUMNS };
146+
147+
return (
148+
<Box padding="l">
149+
<SpaceBetween size="l">
150+
<Header variant="h1">CloudWatch Logs Insights — R1 Atomic Table Demo</Header>
151+
<Container
152+
header={
153+
<Header
154+
description="Exercises sticky header, grid layout, row selection, and keyboard-accessible horizontal scroll"
155+
counter={`(${logs.length} log entries, ${selectedIds.size} selected)`}
156+
>
157+
Query results
158+
</Header>
159+
}
160+
>
161+
<SpaceBetween size="m">
162+
<SegmentedControl
163+
selectedId={rowCount}
164+
onChange={({ detail }) => {
165+
setRowCount(detail.selectedId as '100' | '500' | '1000');
166+
setSelectedIds(new Set());
167+
}}
168+
label="Row count"
169+
options={[
170+
{ id: '100', text: '100 rows' },
171+
{ id: '500', text: '500 rows' },
172+
{ id: '1000', text: '1000 rows' },
173+
]}
174+
/>
175+
<Box color="text-body-secondary" fontSize="body-s">
176+
Scroll down — header pins to page top. Scroll right — pinned header tracks columns. Tab to the table
177+
region and use arrow keys to scroll horizontally.
178+
</Box>
179+
</SpaceBetween>
180+
</Container>
181+
182+
<TableRoot
183+
columnLayout={columnLayout}
184+
ariaLabel="CloudWatch Logs query results"
185+
ariaRowcount={logs.length + 1}
186+
stickyHeader={
187+
<TableHead>
188+
<TableRow>
189+
<TableHeaderCell disablePaddings={true}>
190+
<div className={styles['selection-cell']}>
191+
<Checkbox
192+
checked={allSelected}
193+
indeterminate={someSelected}
194+
onChange={() => handleSelectAll()}
195+
ariaLabel="Select all log entries"
196+
/>
197+
</div>
198+
</TableHeaderCell>
199+
<TableHeaderCell>@timestamp</TableHeaderCell>
200+
<TableHeaderCell>@requestId</TableHeaderCell>
201+
<TableHeaderCell>@level</TableHeaderCell>
202+
<TableHeaderCell>@functionName</TableHeaderCell>
203+
<TableHeaderCell>@message</TableHeaderCell>
204+
<TableHeaderCell>@duration (ms)</TableHeaderCell>
205+
<TableHeaderCell>@billedDuration</TableHeaderCell>
206+
<TableHeaderCell>@memoryUsed (MB)</TableHeaderCell>
207+
<TableHeaderCell>@memoryAllocated</TableHeaderCell>
208+
</TableRow>
209+
</TableHead>
210+
}
211+
stickyHeaderOffset={0}
212+
>
213+
<TableBody>
214+
{logs.map((log, index) => {
215+
const isSelected = selectedIds.has(log.id);
216+
return (
217+
<TableRow key={log.id} selected={isSelected} ariaRowindex={index + 2}>
218+
<TableBodyCell disablePaddings={true}>
219+
<div className={styles['selection-cell']}>
220+
<Checkbox
221+
checked={isSelected}
222+
onChange={({ detail }) => handleSelectRow(log.id, detail.checked)}
223+
ariaLabel={`Select log entry ${log.requestId}`}
224+
/>
225+
</div>
226+
</TableBodyCell>
227+
<TableBodyCell>
228+
<Box variant="code" fontSize="body-s">
229+
{log.timestamp}
230+
</Box>
231+
</TableBodyCell>
232+
<TableBodyCell>
233+
<Box variant="code" fontSize="body-s">
234+
{log.requestId}
235+
</Box>
236+
</TableBodyCell>
237+
<TableBodyCell>
238+
<LevelIndicator level={log.level} />
239+
</TableBodyCell>
240+
<TableBodyCell>
241+
<Box variant="code" fontSize="body-s">
242+
{log.functionName}
243+
</Box>
244+
</TableBodyCell>
245+
<TableBodyCell>{log.message}</TableBodyCell>
246+
<TableBodyCell>
247+
<Box textAlign="right">{log.duration.toLocaleString()}</Box>
248+
</TableBodyCell>
249+
<TableBodyCell>
250+
<Box textAlign="right">{log.billedDuration.toLocaleString()} ms</Box>
251+
</TableBodyCell>
252+
<TableBodyCell>
253+
<Box textAlign="right">{log.memoryUsed}</Box>
254+
</TableBodyCell>
255+
<TableBodyCell>
256+
<Box textAlign="right">{log.memoryAllocated} MB</Box>
257+
</TableBodyCell>
258+
</TableRow>
259+
);
260+
})}
261+
</TableBody>
262+
</TableRoot>
263+
</SpaceBetween>
264+
</Box>
265+
);
266+
}

0 commit comments

Comments
 (0)