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

michaelmbugua-me / DataAnalyticsPlatform / #6

28 Aug 2025 06:06AM UTC coverage: 22.047% (+1.3%) from 20.788%
#6

push

github

michaelmbugua-me
feat: FilterManagerComponent

- Add a dialog before deleting a configuration.
- Also add a toastr notification after user action.

74 of 616 branches covered (12.01%)

Branch coverage included in aggregate %.

331 of 1221 relevant lines covered (27.11%)

2.01 hits per line

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

8.15
/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

12

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

24
  private dataService = inject(DataService);
1✔
25
  protected filtersService = inject(FiltersService);
×
26

27
  public data = this.dataService.filteredRawData;
×
28
  error = this.dataService.error;
×
29

30
  // Facet selections
31
  selectedSource = signal<string | null>(null);
×
32
  selectedPlatform = signal<string | null>(null);
×
33
  selectedCountry = signal<string | null>(null);
×
34
  selectedReleaseChannel = signal<string | null>(null);
×
35

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

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

56
  performanceChartOptions!: AgChartOptions;
57
  eventsChartOptions!: AgChartOptions;
58
  platformChartOptions!: AgChartOptions;
59
  deviceTierChartOptions!: AgChartOptions;
60
  countryChartOptions!: AgChartOptions;
61
  networkChartOptions!: AgChartOptions;
62

63
  filters!: Filter[];
64

65
  savedConfigNames = () => (this.filtersService.savedConfigs() || []).map(c => c.name);
×
66

67
  visible = signal(false);
×
68

69
  // Charts
70
  chartLoaded: any = false;
×
71

72
  constructor() {
73

74
    effect(() => {
×
75

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

84
      this.chartLoaded = true;
×
85

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

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

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

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

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

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

220
    });
221

222
  }
223

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

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

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

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

251
    return Object.keys(platformCounts).map((platform) => ({
×
252
      platform,
253
      count: platformCounts[platform]
254
    }));
255
  }
256

257
  getDeviceTierData() {
258
    const tierData: any = {};
×
259
    const tierCounts: any = {};
×
260

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

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

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

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

291
  getNetworkData() {
292
    const networkData: any = {};
×
293
    const networkCounts: any = {};
×
294

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

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

312

313
  async ngOnInit() {
314

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

320
  toggleFilterVisibility() {
321
    this.visible.update(v => !v);
×
322
  }
323

324

325

326
  protected readonly Date = Date;
×
327
}
328

329
function uniqSorted(arr: (string | null | undefined)[]): string[] {
330
  const set = new Set<string>();
×
331
  for (const v of arr) { if (v != null) set.add(String(v)); }
×
332
  return Array.from(set).sort((a,b) => a.localeCompare(b));
×
333
}
334

335

336
interface Filter {
337
  name: string,
338
  code: string
339
}
340

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