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

michaelmbugua-me / DataAnalyticsPlatform / #13

28 Aug 2025 09:30AM UTC coverage: 26.838% (+2.1%) from 24.769%
#13

push

github

michaelmbugua-me
feat: Overall Improvement in terms of responsiveness
- Clean up styling to ensure the portal is responsive on mobile devices.

85 of 743 branches covered (11.44%)

Branch coverage included in aggregate %.

9 of 12 new or added lines in 4 files covered. (75.0%)

566 existing lines in 10 files now uncovered.

488 of 1392 relevant lines covered (35.06%)

1.81 hits per line

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

10.64
/src/app/features/VisualizationsModule/VisualizationsRawData/VisualizationsRawDataComponent.ts
1
import {Component, effect, inject, OnInit, signal, ChangeDetectionStrategy} from '@angular/core';
1✔
2
import {Button, ButtonDirective, ButtonIcon, ButtonLabel} from 'primeng/button';
1✔
3
import {FormsModule} from '@angular/forms';
1✔
4
import {AgChartOptions} from 'ag-charts-community';
5
import {AgCharts} from 'ag-charts-angular';
1✔
6
import {ProgressSpinner} from 'primeng/progressspinner';
1✔
7
import {DataService} from '../../../core/services/DataService';
1✔
8
import {FilterDrawerComponent} from '../../shared/components/filter-drawer';
1✔
9
import { FiltersService } from '../../../core/services/FiltersService';
1✔
10
import { applyCommonFilters } from '../../shared/utils/applyFilters';
1✔
11
import {PageHeaderComponent} from '../../shared/components/PageHeaderComponent/PageHeaderComponent';
1✔
12
import {Select} from 'primeng/select';
1✔
13

14

15
@Component({
16
  selector: 'app-visualizations',
17
  templateUrl: './VisualizationsRawDataComponent.html',
18
  standalone: true,
19
  changeDetection: ChangeDetectionStrategy.OnPush,
20
  imports: [ButtonDirective, ButtonIcon, ButtonLabel, FormsModule, Button, AgCharts, ProgressSpinner, FilterDrawerComponent, PageHeaderComponent, Select],
21
  providers: [],
22
  styleUrls: ['./VisualizationsRawDataComponent.scss']
23
})
24
export class VisualizationsRawDataComponent implements OnInit {
1✔
25

26
  private dataService = inject(DataService);
1✔
27
  protected filtersService = inject(FiltersService);
1✔
28

29
  public data = this.dataService.filteredRawData;
1✔
30
  error = this.dataService.error;
1✔
31

32
  // Facet selections
33
  selectedSource = signal<string | null>(null);
1✔
34
  selectedPlatform = signal<string | null>(null);
1✔
35
  selectedCountry = signal<string | null>(null);
1✔
36
  selectedReleaseChannel = signal<string | null>(null);
1✔
37

38
  // Options derived from current data
39
  sourceOptions = () => uniqSorted((this.data() || []).map((r: any) => r.source).filter(Boolean));
1!
40
  platformOptions = () => uniqSorted((this.data() || []).map((r: any) => r.platform).filter(Boolean));
1!
41
  countryOptions = () => uniqSorted((this.data() || []).map((r: any) => r.country).filter(Boolean));
1!
42
  releaseChannelOptions = () => uniqSorted((this.data() || []).map((r: any) => r.release_channel).filter(Boolean));
1!
43

44
  // Derived filtered data applying search/query/facets in addition to date range
45
  public viewData = () => applyCommonFilters(this.data() || [], {
1!
46
    searchText: this.filtersService.searchText(),
47
    query: this.filtersService.customQuery(),
48
    facets: {
49
      source: this.selectedSource(),
50
      platform: this.selectedPlatform(),
51
      country: this.selectedCountry(),
52
      release_channel: this.selectedReleaseChannel(),
53
    },
54
    stringify: (row: any) => [row.id, row.event_name, row.platform, row.country, row.app_id, row.source, row.release_channel]
×
55
      .map((v: any) => String(v ?? '')).join(' ')
×
56
  });
57

58
  performanceChartOptions!: AgChartOptions;
59
  eventsChartOptions!: AgChartOptions;
60
  platformChartOptions!: AgChartOptions;
61
  deviceTierChartOptions!: AgChartOptions;
62
  countryChartOptions!: AgChartOptions;
63
  networkChartOptions!: AgChartOptions;
64

65
  filters!: Filter[];
66

67
  savedConfigNames = () => (this.filtersService.savedConfigs() || []).map(c => c.name);
68

69
  visible = signal(false);
70

71
  // Charts
1!
72
  chartLoaded: any = false;
73

1✔
74
  constructor() {
75

76
    effect(() => {
1✔
77

78
      // Derive datasets from current filtered rows
79
      const performanceData = this.getPerformanceData();
80
      const eventDistribution = this.getEventDistribution();
81
      const platformData = this.getPlatformData();
1✔
82
      const deviceTierData = this.getDeviceTierData();
83
      const countryChartData = this.getCountryData();
84
      const networkChartData = this.getNetworkData();
×
UNCOV
85

×
86
      this.chartLoaded = true;
×
UNCOV
87

×
UNCOV
88
      // Chart 1: Performance Over Time (App Start Duration)
×
89
      this.performanceChartOptions = {
×
UNCOV
90
        title: {
×
91
          text: 'App Start Performance Over Time',
UNCOV
92
        }, data: [...performanceData], series: [{
×
93
          type: 'line', xKey: 'day', yKey: 'duration_ms', yName: 'App Start Time (ms)',
94
        },], axes: [{
UNCOV
95
          type: 'category', position: 'bottom', title: {
×
96
            text: 'Date',
97
          },
98
        }, {
99
          type: 'number', position: 'left', title: {
100
            text: 'Duration (ms)',
101
          },
102
        },],
103
      };
104

105
      // Chart 2: Event Distribution
106
      this.eventsChartOptions = {
107
        title: {
108
          text: 'User Event Distribution',
109
        }, data: [...eventDistribution], series: [{
110
          type: 'pie', angleKey: 'count', legendItemKey: 'event', calloutLabelKey: 'event'
111
        }],
112
      };
113

114
      // Chart 3: Platform Usage
115
      this.platformChartOptions = {
116
        title: {
117
          text: 'Usage by Platform',
118
        },
119
        data: [...platformData],
120
        series: [
121
          {
122
            type: 'bar',
123
            xKey: 'platform',
124
            yKey: 'count',
125
          },
126
        ],
127
        axes: [
128
          {
129
            type: 'category',
130
            position: 'bottom',
131
          },
132
          {
133
            type: 'number',
134
            position: 'left',
135
            title: {
136
              text: 'Number of Events',
137
            },
138
          },
139
        ],
140
      };
141

142
      // Chart 4: Performance by Device Tier
143
      this.deviceTierChartOptions = {
144
        title: {
145
          text: 'Performance by Device Tier',
146
        },
147
        data: [...deviceTierData],
148
        series: [
149
          {
150
            type: 'bar',
151
            xKey: 'device_tier',
152
            yKey: 'avg_duration',
153
            yName: 'Average Duration (ms)',
154
          },
155
        ],
156
        axes: [
157
          {
158
            type: 'category',
159
            position: 'bottom',
160
            title: {
161
              text: 'Device Tier',
162
            },
163
          },
164
          {
165
            type: 'number',
166
            position: 'left',
167
            title: {
168
              text: 'Average Duration (ms)',
169
            },
170
          },
171
        ],
172
      };
UNCOV
173

×
174
      // Chart 5: Usage by Country
175
      this.countryChartOptions = {
176
        title: {
177
          text: 'Usage by Country',
178
        },
179
        data: [...countryChartData],
180
        series: [
181
          {
UNCOV
182
            type: 'pie',
×
183
            angleKey: 'count',
184
            legendItemKey: 'country',
185
            calloutLabelKey: 'country',
186
          },
UNCOV
187
        ],
×
188
      };
189

190
      // Chart 6: Performance by Network Type
191
      this.networkChartOptions = {
192
        title: {
193
          text: 'Performance by Network Type',
194
        },
195
        data: [...networkChartData],
196
        series: [
197
          {
198
            type: 'bar',
199
            xKey: 'network_type',
200
            yKey: 'avg_duration',
201
            yName: 'Average Duration (ms)',
202
          },
203
        ],
204
        axes: [
205
          {
206
            type: 'category',
207
            position: 'bottom',
208
            title: {
209
              text: 'Network Type',
UNCOV
210
            },
×
211
          },
212
          {
213
            type: 'number',
214
            position: 'left',
215
            title: {
216
              text: 'Average Duration (ms)',
217
            },
218
          },
219
        ],
220
      };
221

222
    });
223

224
  }
225

226
  getEventDistribution() {
227
    const eventCounts: any = {};
228
    (this.viewData() || []).forEach((item: any) => {
229
      if (item.event_name) {
230
        eventCounts[item.event_name] = (eventCounts[item.event_name] || 0) + 1;
231
      }
232
    });
233

234
    return Object.keys(eventCounts).map(event => ({
235
      event, count: eventCounts[event]
236
    }));
237
  }
238

239
  getPerformanceData() {
240
    return (this.viewData() || [])
241
      .filter((item: any) => item.source === 'performance' && item.perf_type === 'app_start')
242
      .map((item: any) => ({
×
243
        day: item.day, duration_ms: item.duration_ms, platform: item.platform
244
      }));
245
  }
246

247
  getPlatformData() {
248
    const platformCounts: any = {};
249
    (this.viewData() || []).forEach((item: any) => {
250
      platformCounts[item.platform] = (platformCounts[item.platform] || 0) + 1;
251
    });
252

253
    return Object.keys(platformCounts).map((platform) => ({
254
      platform,
255
      count: platformCounts[platform]
256
    }));
257
  }
UNCOV
258

×
259
  getDeviceTierData() {
260
    const tierData: any = {};
261
    const tierCounts: any = {};
262

263
    (this.viewData() || [])
264
      .filter((item: any) => item.duration_ms)
265
      .forEach((item: any) => {
266
        if (!tierData[item.device_tier]) {
267
          tierData[item.device_tier] = 0;
268
          tierCounts[item.device_tier] = 0;
269
        }
270
        tierData[item.device_tier] += item.duration_ms;
271
        tierCounts[item.device_tier]++;
272
      });
273

274
    return Object.keys(tierData).map(tier => ({
275
      device_tier: tier,
276
      avg_duration: Math.round(tierData[tier] / (tierCounts[tier] || 1))
277
    }));
278
  }
279

280
  getCountryData() {
281
    const countryCounts: any = {};
282
    (this.viewData() || []).forEach(item => {
283
      const key = item.country || 'unknown';
284
      countryCounts[key] = (countryCounts[key] || 0) + 1;
285
    });
286

287
    return Object.keys(countryCounts).map(country => ({
288
      country,
289
      count: countryCounts[country]
UNCOV
290
    }));
×
291
  }
292

293
  getNetworkData() {
294
    const networkData: any = {};
295
    const networkCounts: any = {};
×
296

297
    (this.viewData() || [])
298
      .filter((item: any) => item.duration_ms && item.network_type)
299
      .forEach((item: any) => {
300
        if (!networkData[item.network_type]) {
301
          networkData[item.network_type] = 0;
302
          networkCounts[item.network_type] = 0;
303
        }
304
        networkData[item.network_type] += item.duration_ms;
305
        networkCounts[item.network_type]++;
306
      });
307

308
    return Object.keys(networkData).map(network => ({
309
      network_type: network,
UNCOV
310
      avg_duration: Math.round(networkData[network] / (networkCounts[network] || 1))
×
311
    }));
312
  }
313

314

UNCOV
315
  async ngOnInit() {
×
316

317
    this.filters = [{name: 'Today\'s records', code: 'NY'}, {
318
      name: 'This weeks\'s records', code: 'RM'
319
    }, {name: 'This month\'s records', code: 'LDN'}, {name: 'This year\'s records', code: 'IST'}];
320
  }
321

322
  toggleFilterVisibility() {
323
    this.visible.update(v => !v);
324
  }
325

326

327

328
  protected readonly Date = Date;
329

330
  applySelectedConfig(name: string) {
331
    const cfg = this.filtersService.loadConfig(name);
332
    if (cfg?.dateRange) {
333
      try {
NEW
334
        const from = new Date(cfg.dateRange.from);
×
NEW
335
        const to = new Date(cfg.dateRange.to);
×
NEW
336
        if (!isNaN(from.getTime()) && !isNaN(to.getTime())) this.dataService.setDateRange({ from, to });
×
337
      } catch {}
338
    }
339
  }
340

341
  printScreen() {
342
    window.print();
343
  }
344
}
UNCOV
345

×
346
function uniqSorted(arr: (string | null | undefined)[]): string[] {
347
  const set = new Set<string>();
348
  for (const v of arr) { if (v != null) set.add(String(v)); }
×
349
  return Array.from(set).sort((a,b) => a.localeCompare(b));
UNCOV
350
}
×
UNCOV
351

×
352

UNCOV
353
interface Filter {
×
UNCOV
354
  name: string,
×
355
  code: string
356
}
357

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