Skip to content

Commit 6e59183

Browse files
Enable telemetry in the for the Application
Add telemetry events and metrics tracking throughout the application to monitor user interactions and file operations. All telemetry respects user privacy by only sending statistical counts, not content. * Add telemetry events for cluster and extension file loading * Add error reporting for XML parsing failures * Implement sendClusterSaveMetrics() to track cluster save statistics (counts of attributes, commands, events, enums, structs) * Implement sendClusterExtensionSaveMetrics() to track extension save statistics (counts of attributes, commands, events) * Call metrics methods in getSerializedCluster() and getSerializedClusterExtension() * Add telemetry events for successful file save operations * Track cluster file saves separately from extension file saves * Add telemetry event when components are opened/rendered Track which component types users are working with * Enable telemetry at application startup - Only element counts are transmitted, never content or values - No sensitive data (names, codes, descriptions) is included - Separate tracking for cluster vs. cluster extension operations - All events respect user's telemetry opt-in/opt-out settings Signed-off-by: Arkadiusz Balys <arkadiusz.balys@nordicsemi.no>
1 parent 3352ac9 commit 6e59183

File tree

6 files changed

+109
-2
lines changed

6 files changed

+109
-2
lines changed

src/app/Components/ClusterFile.tsx

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
/* eslint-disable @typescript-eslint/no-explicit-any */
88

9-
import { logger } from '@nordicsemiconductor/pc-nrfconnect-shared';
9+
import { logger, telemetry } from '@nordicsemiconductor/pc-nrfconnect-shared';
1010

1111
import { defaultXMLConfigurator } from '../defaults';
1212
import {
@@ -159,13 +159,15 @@ class ClusterFile {
159159
const configurator = await parseClusterXML(content);
160160
this.file = configurator as XMLFile;
161161
logger.info('Loaded cluster file:', fileUrl.name);
162+
telemetry.sendEvent('Loaded cluster file');
162163
this.loadedClusterExtension = false;
163164
return (
164165
Array.isArray(this.file.cluster) ||
165166
this.file.cluster !== undefined ||
166167
this.file.deviceType !== undefined
167168
);
168169
} catch (error) {
170+
telemetry.sendEvent('Error parsing XML file while loading cluster');
169171
logger.error('Error parsing XML:', error);
170172
return false;
171173
}
@@ -269,9 +271,14 @@ class ClusterFile {
269271
this.loadedClusterExtension = true;
270272

271273
eventEmitter.emit('xmlInstanceChanged');
274+
275+
telemetry.sendEvent('Loaded cluster exension file');
272276
logger.info('Loaded cluster extension file:', fileUrl.name);
273277
return true;
274278
} catch (error) {
279+
telemetry.sendErrorReport(
280+
'Error parsing XML while loading cluster extension'
281+
);
275282
logger.error('Error parsing XML:', error);
276283
return false;
277284
}
@@ -639,6 +646,34 @@ class ClusterFile {
639646
return this.XMLCurrentInstance.deviceType;
640647
}
641648

649+
/**
650+
* Sends telemetry metrics for cluster extension save operation.
651+
* Only sends counts of elements, not their content.
652+
*
653+
* @function ClusterFile.sendClusterExtensionSaveMetrics
654+
* @param {XMLAttribute[]} attributes - The attributes to count
655+
* @param {XMLCommand[]} commands - The commands to count
656+
* @param {XMLEvent[]} events - The events to count
657+
* @param {XMLDeviceType | string} deviceType - The device type (if any)
658+
* @returns {void}
659+
*/
660+
private static sendClusterExtensionSaveMetrics(
661+
attributes: XMLAttribute[],
662+
commands: XMLCommand[],
663+
events: XMLEvent[],
664+
deviceType: XMLDeviceType | string
665+
) {
666+
const metrics = {
667+
attributesCount: attributes?.length || 0,
668+
commandsCount: commands?.length || 0,
669+
eventsCount: events?.length || 0,
670+
hasDeviceType: !!deviceType && deviceType !== '',
671+
};
672+
673+
telemetry.sendEvent('Saved cluster extension', metrics);
674+
logger.info('Cluster extension save metrics:', metrics);
675+
}
676+
642677
/**
643678
* Gets the serialized cluster extension.
644679
*
@@ -675,9 +710,51 @@ class ClusterFile {
675710
clusterExtensionInstance.clusterExtension.event = newEvents;
676711
clusterExtensionInstance.clusterExtension.deviceType =
677712
newDeviceType as XMLDeviceType;
713+
714+
// Send telemetry about what was saved
715+
this.sendClusterExtensionSaveMetrics(
716+
newAttributes || [],
717+
newCommands || [],
718+
newEvents || [],
719+
newDeviceType || ''
720+
);
721+
678722
return serializeClusterXML(clusterExtensionInstance);
679723
}
680724

725+
/**
726+
* Sends telemetry metrics for cluster save operation.
727+
* Only sends counts of elements, not their content.
728+
*
729+
* @function ClusterFile.sendClusterSaveMetrics
730+
* @param {XMLCluster} cluster - The cluster to count elements from
731+
* @param {XMLEnum[]} enums - The enums to count
732+
* @param {XMLStruct[]} structs - The structs to count
733+
* @param {boolean} hasDeviceType - Whether device type is present
734+
* @param {boolean} hasClusterExtension - Whether cluster extension is present
735+
* @returns {void}
736+
*/
737+
private static sendClusterSaveMetrics(
738+
cluster: XMLCluster | undefined,
739+
enums: XMLEnum[] | undefined,
740+
structs: XMLStruct[] | undefined,
741+
hasDeviceType: boolean,
742+
hasClusterExtension: boolean
743+
) {
744+
const metrics = {
745+
attributesCount: cluster?.attribute?.length || 0,
746+
commandsCount: cluster?.command?.length || 0,
747+
eventsCount: cluster?.event?.length || 0,
748+
enumsCount: enums?.length || 0,
749+
structsCount: structs?.length || 0,
750+
hasDeviceType,
751+
hasClusterExtension,
752+
};
753+
754+
telemetry.sendEvent('Saved cluster', metrics);
755+
logger.info('Cluster save metrics:', metrics);
756+
}
757+
681758
/**
682759
* Gets the serialized cluster.
683760
*
@@ -718,6 +795,15 @@ class ClusterFile {
718795
xmlFile.clusterExtension = currentExt || originalExt;
719796
}
720797

798+
// Send telemetry about what was saved
799+
this.sendClusterSaveMetrics(
800+
this.XMLCurrentInstance.cluster,
801+
this.XMLCurrentInstance.enum,
802+
this.XMLCurrentInstance.struct,
803+
this.XMLCurrentInstance.deviceType !== undefined,
804+
!!(currentExt || originalExt)
805+
);
806+
721807
return serializeClusterXML(xmlFile);
722808
}
723809
}

src/app/Components/Component.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import * as React from 'react';
1010
import { ReactNode, useCallback, useEffect, useState } from 'react';
11+
import { telemetry } from '@nordicsemiconductor/pc-nrfconnect-shared';
1112

1213
import eventEmitter from './EventEmitter';
1314
import ClusterTable from './Table';
@@ -262,6 +263,11 @@ const Component = <T,>({
262263
};
263264
}, [saveAllRows]);
264265

266+
useEffect(() => {
267+
// Send telemetry event when Component is rendered
268+
telemetry.sendEvent(`Component opened: ${name}`);
269+
}, [name]);
270+
265271
/// Render a table according to the current name, headers, rows and addRow callback function
266272
return ClusterTable(name, headers, rows, addRow, description);
267273
};

src/app/SidePanel/Buttons.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
InfoDialog,
1414
logger,
1515
Overlay,
16+
telemetry,
1617
useHotKey,
1718
} from '@nordicsemiconductor/pc-nrfconnect-shared';
1819

@@ -133,6 +134,7 @@ const OpenSavePanelButtons = () => {
133134
a.download = ClusterFile.fileName;
134135
a.click();
135136
URL.revokeObjectURL(url);
137+
telemetry.sendEvent('Saved the cluster to XML file');
136138
logger.info('Saved the cluster');
137139
};
138140

src/app/SidePanel/ExtensionButtons.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
InfoDialog,
1111
logger,
1212
Overlay,
13+
telemetry,
1314
useHotKey,
1415
} from '@nordicsemiconductor/pc-nrfconnect-shared';
1516

@@ -84,6 +85,7 @@ const ExtensionButtons = () => {
8485
a.download = ClusterFile.fileName.replace('.xml', '_extension.xml');
8586
a.click();
8687
URL.revokeObjectURL(url);
88+
telemetry.sendEvent('Saved cluster extension to XML file');
8789
logger.info('Extension saved to file');
8890
};
8991

src/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77
import 'regenerator-runtime/runtime';
88

99
import React from 'react';
10-
import { App, render } from '@nordicsemiconductor/pc-nrfconnect-shared';
10+
import {
11+
App,
12+
render,
13+
telemetry,
14+
} from '@nordicsemiconductor/pc-nrfconnect-shared';
1115

1216
import AttributesTable from './app/Attributes/Attributes';
1317
import ClusterPage from './app/Cluster/Cluster';
@@ -23,6 +27,8 @@ import '../resources/css/index.scss';
2327

2428
const reducer = undefined;
2529

30+
telemetry.enableTelemetry();
31+
2632
render(
2733
<App
2834
appReducer={reducer}

tests/__mocks__/nordic-shared.mock.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,4 +185,9 @@ jest.mock('@nordicsemiconductor/pc-nrfconnect-shared', () => ({
185185
error: jest.fn(),
186186
info: jest.fn(),
187187
},
188+
telemetry: {
189+
sendEvent: jest.fn(),
190+
sendErrorReport: jest.fn(),
191+
enableTelemetry: jest.fn(),
192+
},
188193
}));

0 commit comments

Comments
 (0)