|
| 1 | +# Tenant Telemetry Application Customizer |
| 2 | + |
| 3 | +A lightweight tenant-wide Application Customizer that automatically collects telemetry events from any SPFx Web Part or extension across the entire tenant. |
| 4 | +This solution enables organizations to track: |
| 5 | + |
| 6 | +* Which sites use which custom SPFx components |
| 7 | +* Component versions |
| 8 | +* Page usage |
| 9 | +* Custom events from web parts |
| 10 | +* User interactions |
| 11 | +* Performance / feature adoption |
| 12 | + |
| 13 | +The telemetry is sent either directly to **Azure Application Insights** or to a custom **Azure Function** endpoint depending on your configuration. |
| 14 | + |
| 15 | + |
| 16 | + |
| 17 | +## Summary |
| 18 | + |
| 19 | +This SPFx Application Customizer listens for telemetry events via the browser’s `CustomEvent` API: |
| 20 | + |
| 21 | +```ts |
| 22 | +window.dispatchEvent(new CustomEvent("spfx-telemetry", { detail: { ... } })); |
| 23 | +``` |
| 24 | + |
| 25 | +The extension enriches the payload with site and page context and then forwards it through one of two configurable channels: |
| 26 | + |
| 27 | +### **Telemetry modes** |
| 28 | + |
| 29 | +| Mode | Description | |
| 30 | +| ------------- | ----------------------------------------------------------------------------------------------------------------------- | |
| 31 | +| `appinsights` | Sends events directly to Azure Application Insights using a Connection String | |
| 32 | +| `function` | Sends events to a custom Azure Function (HTTP POST) for further processing (Event Grid, Log Analytics, Cosmos DB, etc.) | |
| 33 | + |
| 34 | +### **This sample includes** |
| 35 | + |
| 36 | +✔ Application Customizer |
| 37 | +✔ TelemetryService (AppInsights + Function modes) |
| 38 | +✔ WebPart integration example |
| 39 | +✔ Console test commands |
| 40 | +✔ Deployment instructions (tenant-wide) |
| 41 | +✔ App Insights Log queries |
| 42 | + |
| 43 | +## Compatibility |
| 44 | + |
| 45 | +| :warning: Important | |
| 46 | +|:---------------------------| |
| 47 | +| Every SPFx version is optimally compatible with specific versions of Node.js. In order to be able to Toolchain this sample, you need to ensure that the version of Node on your workstation matches one of the versions listed in this section. This sample will not work on a different version of Node.| |
| 48 | +|Refer to <https://aka.ms/spfx-matrix> for more information on SPFx compatibility. | |
| 49 | + |
| 50 | +This sample is optimally compatible with the following environment configuration: |
| 51 | + |
| 52 | + |
| 53 | + |
| 54 | + |
| 55 | + |
| 56 | + |
| 57 | +-Incompatible-red.svg "SharePoint Server 2016 Feature Pack 2 requires SPFx 1.1") |
| 58 | + |
| 59 | + |
| 60 | + |
| 61 | + |
| 62 | +## Applies to |
| 63 | + |
| 64 | +* [SharePoint Framework](https://learn.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview) |
| 65 | +* [Microsoft 365 tenant](https://learn.microsoft.com/sharepoint/dev/spfx/set-up-your-developer-tenant) |
| 66 | + |
| 67 | +## Contributors |
| 68 | + |
| 69 | +* [Valeras Narbutas](https://github.com/ValerasNarbutas) |
| 70 | + |
| 71 | +## Version history |
| 72 | + |
| 73 | +| Version | Date | Comments | |
| 74 | +| ------- | ---------- | --------------- | |
| 75 | +| 1.0 | November 18, 2025 | Initial release | |
| 76 | + |
| 77 | +## Prerequisites |
| 78 | + |
| 79 | +* Azure Application Insights **or** Azure Function endpoint |
| 80 | +* Tenant App Catalog |
| 81 | +* Modern SharePoint pages |
| 82 | + |
| 83 | +## Minimal Path to Awesome |
| 84 | + |
| 85 | +### 1. Clone the repo |
| 86 | + |
| 87 | +```bash |
| 88 | +npm install |
| 89 | +gulp build |
| 90 | +gulp bundle --ship |
| 91 | +gulp package-solution --ship |
| 92 | +``` |
| 93 | + |
| 94 | +### 2. Upload the `.sppkg` to the Tenant App Catalog |
| 95 | + |
| 96 | +``` |
| 97 | +/sites/appcatalog/Apps for SharePoint |
| 98 | +``` |
| 99 | + |
| 100 | +Check: |
| 101 | + |
| 102 | +✔ **Make this solution available to all sites** |
| 103 | +✔ Deploy |
| 104 | + |
| 105 | +### 3. Register the Application Customizer in Tenant Wide Extensions |
| 106 | + |
| 107 | +Open: |
| 108 | + |
| 109 | +``` |
| 110 | +/sites/appcatalog/Lists/TenantWideExtensions |
| 111 | +``` |
| 112 | + |
| 113 | +Create a **new item** with: |
| 114 | + |
| 115 | +| Field | Value | |
| 116 | +| ----------- | ------------------------------------------- | |
| 117 | +| Title | TenantTelemetry | |
| 118 | +| ComponentId | `11352c34-6ebe-4679-82bb-734061f2cae0` | |
| 119 | +| Location | `ClientSideExtension.ApplicationCustomizer` | |
| 120 | +| Sequence | `1` | |
| 121 | + |
| 122 | +### 4. Add configuration (App Insights mode) |
| 123 | + |
| 124 | +```json |
| 125 | +{ |
| 126 | + "mode": "appinsights", |
| 127 | + "endpoint": "InstrumentationKey=XXXX;IngestionEndpoint=YYYY" |
| 128 | +} |
| 129 | +``` |
| 130 | + |
| 131 | +### (Optional) Azure Function mode |
| 132 | + |
| 133 | +```json |
| 134 | +{ |
| 135 | + "mode": "function", |
| 136 | + "endpoint": "https://yourfunction.azurewebsites.net/api/telemetry" |
| 137 | +} |
| 138 | +``` |
| 139 | + |
| 140 | +Save → telemetry starts flowing across the entire tenant. |
| 141 | + |
| 142 | + |
| 143 | + |
| 144 | +## How to Use in Your Web Parts |
| 145 | + |
| 146 | +Your web part (or another extension) simply dispatches an event: |
| 147 | + |
| 148 | +```ts |
| 149 | +window.dispatchEvent( |
| 150 | + new CustomEvent("spfx-telemetry", { |
| 151 | + detail: { |
| 152 | + eventName: "MyWebPart_Rendered", |
| 153 | + componentId: this.context.manifest.id, |
| 154 | + componentName: this.context.manifest.alias, |
| 155 | + componentVersion: this.context.manifest.version, |
| 156 | + customData: { |
| 157 | + action: "render", |
| 158 | + filterValue: this.properties.category |
| 159 | + } |
| 160 | + } |
| 161 | + }) |
| 162 | +); |
| 163 | +``` |
| 164 | + |
| 165 | +Example webpart can be found in samples: |
| 166 | +[https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-telemetry-event-sender-sample](https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-telemetry-event-sender-sample) |
| 167 | + |
| 168 | +That’s all. |
| 169 | +No dependency imports. No service initialization. |
| 170 | +The Application Customizer handles all communication. |
| 171 | + |
| 172 | +## How to Test Via Console |
| 173 | + |
| 174 | +Open any modern SharePoint page, then run: |
| 175 | + |
| 176 | +```js |
| 177 | +window.dispatchEvent( |
| 178 | + new CustomEvent("spfx-telemetry", { |
| 179 | + detail: { |
| 180 | + eventName: "Console_Test_Event", |
| 181 | + componentId: "console", |
| 182 | + componentName: "Console", |
| 183 | + componentVersion: "0.0.1", |
| 184 | + customData: { message: "Hello from console!" } |
| 185 | + } |
| 186 | + }) |
| 187 | +); |
| 188 | +``` |
| 189 | + |
| 190 | + |
| 191 | + |
| 192 | +The Application Customizer logs the event and pushes telemetry. |
| 193 | + |
| 194 | +## How to Verify in Application Insights |
| 195 | + |
| 196 | +Go to **Logs** and run: |
| 197 | + |
| 198 | +```kusto |
| 199 | +customEvents |
| 200 | +| where name == "Console_Test_Event" |
| 201 | +| order by timestamp desc |
| 202 | +``` |
| 203 | + |
| 204 | +Or view *all* events: |
| 205 | + |
| 206 | +```kusto |
| 207 | +customEvents |
| 208 | +| extend siteUrl = tostring(customDimensions.siteUrl) |
| 209 | +| extend componentName = tostring(customDimensions.componentName) |
| 210 | +| project timestamp, name, siteUrl, componentName, customDimensions |
| 211 | +| top 50 by timestamp desc |
| 212 | +``` |
| 213 | + |
| 214 | +## How to Test Azure Function Mode |
| 215 | + |
| 216 | +If using Azure Function mode, telemetry is sent via: |
| 217 | + |
| 218 | +```http |
| 219 | +POST https://yourfunction.azurewebsites.net/api/telemetry |
| 220 | +Content-Type: application/json |
| 221 | +Body: { ...payload } |
| 222 | +``` |
| 223 | + |
| 224 | +### Check your Function |
| 225 | + |
| 226 | +#### 1. Azure Portal → Function App → Function → **Monitor** |
| 227 | + |
| 228 | +You should see requests. |
| 229 | + |
| 230 | +#### 2. Azure Portal → Function → **Logs** |
| 231 | + |
| 232 | +You should see the JSON payload: |
| 233 | + |
| 234 | +```json |
| 235 | +{ |
| 236 | + "eventName": "Console_Test_Event", |
| 237 | + "siteUrl": "https://tenant.sharepoint.com/sites/...", |
| 238 | + "pageUrl": "https://tenant.sharepoint.com/sites/.../SitePages/Page.aspx", |
| 239 | + "componentId": "...", |
| 240 | + "componentVersion": "...", |
| 241 | + "customData": { |
| 242 | + "message": "Hello from console!" |
| 243 | + } |
| 244 | +} |
| 245 | +``` |
| 246 | + |
| 247 | +#### 3. Your function can forward to |
| 248 | + |
| 249 | +* Event Grid |
| 250 | +* Log Analytics |
| 251 | +* Cosmos DB |
| 252 | +* Azure Storage |
| 253 | + |
| 254 | +--- |
| 255 | + |
| 256 | +## Debug URL |
| 257 | + |
| 258 | +To test the extension locally: |
| 259 | + |
| 260 | +``` |
| 261 | +?loadSPFX=true |
| 262 | +&debugManifestsFile=https://localhost:4321/temp/manifests.js |
| 263 | +&customActions={"11352c34-6ebe-4679-82bb-734061f2cae0":{"location":"ClientSideExtension.ApplicationCustomizer","properties":{"mode":"appinsights","endpoint":"InstrumentationKey=X;IngestionEndpoint=Y"}}} |
| 264 | +``` |
| 265 | + |
| 266 | +(Update `ComponentId` and endpoint as needed.) |
| 267 | + |
| 268 | +## Features |
| 269 | + |
| 270 | +This sample demonstrates: |
| 271 | + |
| 272 | +* A **tenant-wide telemetry** system for SPFx |
| 273 | +* A **runtime-configurable backend** (App Insights or Azure Function) |
| 274 | +* A simple, framework-agnostic **telemetry event protocol** using CustomEvent |
| 275 | +* A lightweight **Application Customizer listener** |
| 276 | +* Web part instrumentation without dependencies |
| 277 | +* Support for: |
| 278 | + ✔ Site-level context |
| 279 | + ✔ Page URL |
| 280 | + ✔ Component identity and version |
| 281 | + ✔ Custom event data |
| 282 | + |
| 283 | +## Disclaimer |
| 284 | + |
| 285 | +**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.** |
0 commit comments