• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

michaelmbugua-me / DataAnalyticsPlatform / #5

26 Aug 2025 06:25PM UTC coverage: 20.788% (+0.6%) from 20.144%
#5

push

github

michaelmbugua-me
Add a data exportation service that allows exporting excel and pdf

62 of 502 branches covered (12.35%)

Branch coverage included in aggregate %.

6 of 73 new or added lines in 1 file covered. (8.22%)

479 existing lines in 7 files now uncovered.

265 of 1071 relevant lines covered (24.74%)

1.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

10.6
/src/app/features/DataExplorerModule/RawEvents/RawEventsComponent.ts
1
import {Component, inject, OnInit, signal, computed, Signal, ChangeDetectionStrategy} from '@angular/core';
1✔
2
import {AgGridAngular} from 'ag-grid-angular';
1✔
3
import {ColDef, GridOptions, GridReadyEvent, ValueFormatterParams, CellClassParams} from 'ag-grid-community';
4
import {Button, ButtonDirective, ButtonIcon, ButtonLabel} from 'primeng/button';
1✔
5
import {FormsModule} from '@angular/forms';
1✔
6
import {DataService} from '../../../core/services/DataService';
1✔
7
import {FilterDrawerComponent} from '../../shared/components/filter-drawer';
1✔
8
import {
9
  RawEvent,
10
  AnalyticsEvent,
11
  PerformanceEvent,
12
  CrashEvent,
13
  EventSource,
14
  Platform,
15
  Country,
16
  ReleaseChannel
17
} from '../../../core/models/DataModels';
18
import { getSourceCellStyle, getReleaseChannelStyle, getDurationCellStyle } from '../../shared/utils/gridCellStyles';
1✔
19
import { AllCommunityModule, ModuleRegistry } from 'ag-grid-community';
1✔
20

1✔
21
// Register AG Grid modules lazily for this feature chunk
1✔
22
ModuleRegistry.registerModules([AllCommunityModule]);
1✔
23

24
@Component({
25
  selector: 'app-raw-events-component',
1✔
26
  templateUrl: './RawEventsComponent.html',
27
  imports: [
28
    AgGridAngular,
29
    ButtonDirective,
30
    ButtonIcon,
31
    ButtonLabel,
32
    FormsModule,
33
    Button,
34
    FilterDrawerComponent
35
  ],
36
  providers: [],
37
  styleUrls: ['./RawEventsComponent.scss'],
38
  standalone: true,
39
  changeDetection: ChangeDetectionStrategy.OnPush,
40
})
41
export class RawEventsComponent implements OnInit {
42

43
  private dataService = inject(DataService);
44

1✔
45
  // Type-safe data with proper typing
46
  public data: Signal<RawEvent[]> = this.dataService.filteredRawData;
1✔
47
  error: Signal<string | null> = this.dataService.error;
48

49
  filters!: Filter[];
1✔
UNCOV
50
  visible = signal(false);
×
UNCOV
51

×
52
  // Computed signals for different event types (optional - for additional functionality)
×
53
  analyticsEvents = computed(() =>
UNCOV
54
    this.data()?.filter((event): event is AnalyticsEvent => event.source === 'analytics') ?? []
×
55
  );
×
56

UNCOV
57
  performanceEvents = computed(() =>
×
UNCOV
58
    this.data()?.filter((event): event is PerformanceEvent => event.source === 'performance') ?? []
×
UNCOV
59
  );
×
60

61
  crashEvents = computed(() =>
UNCOV
62
    this.data()?.filter((event): event is CrashEvent => event.source === 'crash') ?? []
×
UNCOV
63
  );
×
UNCOV
64

×
65
  // Enhanced column definitions with type-aware formatters
66
  columnDefs: ColDef<RawEvent>[] = [
UNCOV
67
    {
×
68
      field: 'id',
69
      headerName: 'ID',
70
      filter: 'agTextColumnFilter',
1✔
UNCOV
71
      minWidth: 200
×
72
    },
73
    {
UNCOV
74
      field: 'timestamp',
×
UNCOV
75
      headerName: 'Timestamp',
×
76
      filter: 'agDateColumnFilter',
77
      minWidth: 180,
78
      valueFormatter: (params: any) =>
×
79
        new Date(params.value).toLocaleString()
80
    },
UNCOV
81
    {
×
82
      field: 'day',
×
83
      headerName: 'Day',
84
      filter: 'agDateColumnFilter',
UNCOV
85
      minWidth: 120
×
UNCOV
86
    },
×
87
    {
88
      field: 'hour',
UNCOV
89
      headerName: 'Hour',
×
UNCOV
90
      filter: 'agNumberColumnFilter',
×
91
      minWidth: 80
92
    },
93
    {
UNCOV
94
      field: 'source',
×
95
      headerName: 'Source',
96
      filter: 'agTextColumnFilter',
97
      minWidth: 160,
98
      cellStyle: (params: CellClassParams) => getSourceCellStyle(params.value as EventSource)
99
    },
100
    {
101
      field: 'event_name',
102
      headerName: 'Event Name',
103
      filter: 'agTextColumnFilter',
104
      minWidth: 150
105
    },
106
    {
UNCOV
107
      field: 'platform',
×
108
      headerName: 'Platform',
109
      filter: 'agTextColumnFilter',
110
      minWidth: 100,
111
      valueFormatter: (params: any) => this.getPlatformIcon(params.value)
112
    },
113
    {
114
      field: 'app_id',
115
      headerName: 'App ID',
116
      filter: 'agTextColumnFilter',
117
      minWidth: 140
118
    },
119
    {
120
      field: 'country',
121
      headerName: 'Country',
122
      filter: 'agTextColumnFilter',
123
      minWidth: 100
124
    },
125
    {
UNCOV
126
      field: 'release_channel',
×
127
      headerName: 'Release',
128
      filter: 'agTextColumnFilter',
129
      minWidth: 100,
130
      cellStyle: (params: CellClassParams) => getReleaseChannelStyle(params.value as ReleaseChannel)
131
    },
132
    {
133
      field: 'session_id',
134
      headerName: 'Session ID',
135
      filter: 'agTextColumnFilter',
136
      minWidth: 160
137
    },
138
    {
UNCOV
139
      field: 'user_pseudo_id',
×
140
      headerName: 'User ID',
141
      filter: 'agTextColumnFilter',
142
      minWidth: 160
143
    },
144
    // Conditional columns based on event type
145
    {
146
      field: 'analytics_event',
147
      headerName: 'Analytics Event',
148
      filter: 'agTextColumnFilter',
149
      minWidth: 150,
150
      hide: false // Show/hide based on filters
151
    },
152
    {
153
      field: 'duration_ms',
154
      headerName: 'Duration (ms)',
155
      filter: 'agNumberColumnFilter',
156
      minWidth: 160,
157
      valueFormatter: (params: ValueFormatterParams<RawEvent, number | undefined>) =>
UNCOV
158
        params.value ? `${params.value}ms` : '-',
×
159
      cellStyle: (params: CellClassParams) => getDurationCellStyle(params.value as number)
160
    },
161
    {
162
      field: 'status_code',
163
      headerName: 'Status',
164
      filter: 'agNumberColumnFilter',
165
      minWidth: 100,
166
      cellStyle: (params: CellClassParams) => this.getStatusCodeStyle(params.value as number)
167
    },
168
    {
169
      field: 'crash_type',
170
      headerName: 'Crash Type',
171
      filter: 'agTextColumnFilter',
172
      minWidth: 160,
173
      cellStyle: { color: '#dc3545', fontWeight: 'bold' }
174
    }
175
  ];
176

177
  public defaultColDef: ColDef<RawEvent> = {
178
    sortable: true,
179
    resizable: true,
180
    filter: true
181
  };
182

183
  public gridOptions: GridOptions<RawEvent> = {
184
    rowModelType: 'clientSide',
185
    pagination: true,
UNCOV
186
    paginationPageSize: 100,
×
UNCOV
187
    enableCellTextSelection: true,
×
188
    ensureDomOrder: true,
189
    animateRows: true,
190
    suppressMenuHide: true,
191
    domLayout: 'normal'
192
  };
193

UNCOV
194
  async ngOnInit() {
×
195
    this.filters = [
196
      {name: 'Today\'s records', code: 'TODAY'},
197
      {name: 'This week\'s records', code: 'WEEK'},
198
      {name: 'This month\'s records', code: 'MONTH'},
199
      {name: 'This year\'s records', code: 'YEAR'}
200
    ];
201
  }
202

203
  onGridReady(params: GridReadyEvent<RawEvent>) {
204
    console.log('Grid is ready');
UNCOV
205
    params.api.sizeColumnsToFit();
×
206

207
    // Apply initial column visibility based on data
208
    this.updateColumnVisibility(params);
209
  }
210

UNCOV
211
  toggleFilterVisibility() {
×
212
    this.visible.update(v => !v);
213
  }
214

215
  // Type-safe helper methods using the model types
216
  private getPlatformIcon(platform: Platform): string {
217
    const icons = {
218
      ios: 'iOS',
219
      android: 'Android',
220
      web: 'Web'
221
    };
222
    return icons[platform] || platform;
223
  }
UNCOV
224

×
UNCOV
225
  private getStatusCodeStyle(statusCode: number): Record<string, string> {
×
UNCOV
226
    if (!statusCode) return {};
×
227

228
    if (statusCode >= 500) return { backgroundColor: '#d32f2f', color: 'white' };
229
    if (statusCode >= 400) return { backgroundColor: '#ff9800', color: 'white' };
230
    if (statusCode >= 200 && statusCode < 300) return { backgroundColor: '#4caf50', color: 'white' };
231
    return {};
232
  }
233

234
  private updateColumnVisibility(params: GridReadyEvent<RawEvent>) {
UNCOV
235
    const data = this.data();
×
UNCOV
236
    if (!data || data.length === 0) return;
×
237

238
    const hasAnalytics = data.some(event => event.source === 'analytics');
UNCOV
239
    const hasPerformance = data.some(event => event.source === 'performance');
×
240
    const hasCrashes = data.some(event => event.source === 'crash');
241

242
    params.api.applyColumnState({
UNCOV
243
      state: [
×
244
        { colId: 'analytics_event', hide: !hasAnalytics },
245
        { colId: 'duration_ms', hide: !hasPerformance },
246
        { colId: 'status_code', hide: !hasPerformance },
247
        { colId: 'crash_type', hide: !hasCrashes }
UNCOV
248
      ]
×
249
    });
250
  }
251

252
  // Type-safe filter methods
UNCOV
253
  filterBySource(source: EventSource) {
×
254
    // Implementation for filtering by event source
255
    console.log(`Filtering by source: ${source}`);
256
  }
UNCOV
257

×
258
  filterByPlatform(platform: Platform) {
UNCOV
259
    // Implementation for filtering by platform
×
UNCOV
260
    console.log(`Filtering by platform: ${platform}`);
×
UNCOV
261
  }
×
UNCOV
262

×
263
  filterByCountry(country: Country) {
264
    // Implementation for filtering by country
265
    console.log(`Filtering by country: ${country}`);
UNCOV
266
  }
×
UNCOV
267

×
268
  // Export functionality with proper typing
UNCOV
269
  exportAnalyticsEvents() {
×
UNCOV
270
    const analyticsData = this.analyticsEvents();
×
UNCOV
271
    console.log('Exporting analytics events:', analyticsData.length);
×
272
    // Implementation for export
UNCOV
273
  }
×
274

275
  exportPerformanceEvents() {
276
    const performanceData = this.performanceEvents();
277
    console.log('Exporting performance events:', performanceData.length);
278
    // Implementation for export
279
  }
280

281
  exportCrashEvents() {
282
    const crashData = this.crashEvents();
283
    console.log('Exporting crash events:', crashData.length);
284
    // Implementation for export
285
  }
UNCOV
286
}
×
UNCOV
287

×
UNCOV
288
interface Filter {
×
289
  name: string;
UNCOV
290
  code: string;
×
291
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc