Skip to content

Commit c0fff5e

Browse files
authored
Initial support for try functions mixpanel (#635)
* Add initial support for try functions mixpanel. Fix localhost behavior for /try * populate CorrelationId in outbound links * PR changes: move mixpanel check to @AiDefined safe check mixpanel using typeof mixpanel !== 'undefined' * init mixpanel with {cross_subdomain_cookie: true}
1 parent ac460f4 commit c0fff5e

14 files changed

+109
-56
lines changed

AzureFunctions.Client/AzureFunctions.Client.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
<Content Include="styles\pop-over.style.css" />
5858
<Content Include="styles\try-now.styles.css" />
5959
<Content Include="styles\trial-expired.styles.css" />
60+
<Content Include="templates\local-development-instructions.component.html" />
6061
<Content Include="templates\secrets-box-container.html" />
6162
<Content Include="templates\tooltip-content-component.html" />
6263
<Content Include="templates\aggregate-block.component.html" />
@@ -158,6 +159,7 @@
158159
<TypeScriptCompile Include="app\decorators\cache.decorator.ts" />
159160
<TypeScriptCompile Include="app\directives\monaco-editor.directive.ts" />
160161
<TypeScriptCompile Include="app\handlers\functions.exception-handler.ts" />
162+
<TypeScriptCompile Include="app\models\app-insights.ts" />
161163
<TypeScriptCompile Include="app\models\monaco-model.ts" />
162164
<TypeScriptCompile Include="app\models\function-monitor.ts" />
163165
<TypeScriptCompile Include="app\models\portal-resources.ts" />
@@ -206,6 +208,7 @@
206208
<TypeScriptCompile Include="app\pipes\aggregate-block.pipe.ts" />
207209
<TypeScriptCompile Include="app\pipes\sidebar.pipe.ts" />
208210
<TypeScriptCompile Include="app\pipes\table-function-monitor.pipe.ts" />
211+
<TypeScriptCompile Include="app\services\ai.service.ts" />
209212
<TypeScriptCompile Include="app\services\app-monitoring.service.ts" />
210213
<TypeScriptCompile Include="app\services\arm.service.ts" />
211214
<TypeScriptCompile Include="app\services\background-tasks.service.ts" />

AzureFunctions.Client/app/components/function-new.component.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {GlobalStateService} from '../services/global-state.service';
1818
import {PopOverComponent} from './pop-over.component';
1919
import {TranslateService, TranslatePipe} from 'ng2-translate/ng2-translate';
2020
import {PortalResources} from '../models/portal-resources';
21+
import {AiService} from '../services/ai.service';
2122

2223
declare var jQuery: any;
2324

@@ -82,7 +83,8 @@ export class FunctionNewComponent {
8283
private _broadcastService: BroadcastService,
8384
private _portalService : PortalService,
8485
private _globalStateService: GlobalStateService,
85-
private _translateService: TranslateService)
86+
private _translateService: TranslateService,
87+
private _aiService: AiService)
8688
{
8789
this.elementRef = elementRef;
8890
this.disabled = _broadcastService.getDirtyState("function_disabled");
@@ -230,11 +232,13 @@ export class FunctionNewComponent {
230232
this._functionsService.createFunctionV2(this.functionName, this.selectedTemplate.files, this.bc.UIToFunctionConfig(this.model.config))
231233
.subscribe(res => {
232234
this._portalService.logAction("new-function", "success", { template: this.selectedTemplate.id, name: this.functionName });
235+
this._aiService.trackEvent("new-function", { template: this.selectedTemplate.id, result: "success", first: "false" });
233236
this._broadcastService.broadcast(BroadcastEvent.FunctionAdded, res);
234237
this._globalStateService.clearBusyState();
235238
},
236239
e => {
237240
this._portalService.logAction("new-function", "failed", { template: this.selectedTemplate.id, name: this.functionName });
241+
this._aiService.trackEvent("new-function", { template: this.selectedTemplate.id, result: "failed", first: "false" });
238242
this._globalStateService.clearBusyState();
239243
this._broadcastService.broadcast<ErrorEvent>(BroadcastEvent.Error, {
240244
message: this._translateService.instant(PortalResources.functionCreateErrorMessage),

AzureFunctions.Client/app/components/trial-expired.component.ts

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import {Component, OnInit} from '@angular/core';
22
import {TranslateService, TranslatePipe} from 'ng2-translate/ng2-translate';
33
import {PortalResources} from '../models/portal-resources';
4+
import {AiService} from '../services/ai.service';
5+
6+
declare var mixpanel: any;
47

58
@Component({
69
selector: 'trial-expired',
@@ -12,10 +15,20 @@ export class TrialExpiredComponent implements OnInit {
1215

1316
public freeTrialUri: string;
1417

15-
constructor() {
16-
var freeTrialExpireCachedQuery = `try_functionsexpiredpage`;
17-
this.freeTrialUri = `${window.location.protocol}//azure.microsoft.com/${window.navigator.language}/free?WT.mc_id=${freeTrialExpireCachedQuery}`;
18+
constructor(private _aiService: AiService) {
19+
this.freeTrialUri = `${window.location.protocol}//azure.microsoft.com/${window.navigator.language}/free` + ((typeof mixpanel !== 'undefined') ? "?correlationId=" + mixpanel.get_distinct_id() : "");;
1820
}
1921

2022
ngOnInit() { }
23+
24+
trackClick(buttonName: string) {
25+
if (buttonName) {
26+
try {
27+
this._aiService.trackEvent(buttonName, { expired: "true" });
28+
} catch (error) {
29+
this._aiService.trackException(error, 'trackClick');
30+
}
31+
}
32+
}
33+
2134
}

AzureFunctions.Client/app/components/try-landing.component.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {FunctionContainer} from '../models/function-container';
1515
import {BusyStateComponent} from './busy-state.component';
1616
import {TranslateService, TranslatePipe} from 'ng2-translate/ng2-translate';
1717
import {PortalResources} from '../models/portal-resources';
18+
import {AiService} from '../services/ai.service';
1819

1920
@Component({
2021
selector: 'try-landing',
@@ -36,7 +37,8 @@ export class TryLandingComponent implements OnInit {
3637
private _broadcastService: BroadcastService,
3738
private _globalStateService: GlobalStateService,
3839
private _userService: UserService,
39-
private _translateService: TranslateService
40+
private _translateService: TranslateService,
41+
private _aiService: AiService
4042

4143
) {
4244
this.tryFunctionsContainer = new EventEmitter<FunctionContainer>();
@@ -174,17 +176,20 @@ export class TryLandingComponent implements OnInit {
174176
tryScmCred: encryptedCreds
175177
};
176178
this._functionsService.setScmParams(tryfunctionContainer);
179+
this._userService.setTryUserName(resource.userName);
177180
this.setBusyState();
178181
this._functionsService.getFunctionContainerAppSettings(tryfunctionContainer)
179182
.subscribe(a => this._globalStateService.AppSettings = a);
180183
this._functionsService.createFunctionV2(functionName, selectedTemplate.files, selectedTemplate.function)
181184
.subscribe(res => {
182185
this.clearBusyState();
186+
this._aiService.trackEvent("new-function", { template: selectedTemplate.id, result: "success", first:"true" });
183187
this._broadcastService.broadcast(BroadcastEvent.FunctionAdded, res);
184188
this.tryFunctionsContainer.emit(tryfunctionContainer);
185189
},
186190
e => {
187191
this.clearBusyState();
192+
this._aiService.trackEvent("new-function", { template: selectedTemplate.id, result: "failed", first: "true" });
188193
this._broadcastService.broadcast<ErrorEvent>(BroadcastEvent.Error, { message: `${this._translateService.instant(PortalResources.tryLanding_functionError)}`, details: `${this._translateService.instant(PortalResources.tryLanding_functionErrorDetails)}: ${JSON.stringify(e)}` });
189194
});
190195
}

AzureFunctions.Client/app/components/try-now.component.ts

+17-5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import {PortalResources} from '../models/portal-resources';
88
import {GlobalStateService} from '../services/global-state.service';
99
import {TooltipContentComponent} from './tooltip-content.component';
1010
import {TooltipComponent} from './tooltip.component';
11+
import {AiService} from '../services/ai.service';
12+
13+
declare var mixpanel: any;
1114

1215
@Component({
1316
selector: 'try-now',
@@ -27,13 +30,12 @@ export class TryNowComponent implements OnInit {
2730
constructor(private _functionsService: FunctionsService,
2831
private _broadcastService: BroadcastService,
2932
private _globalStateService: GlobalStateService,
30-
private _translateService: TranslateService) {
33+
private _translateService: TranslateService,
34+
private _aiService: AiService) {
3135
this.trialExpired = false;
3236
//TODO: Add cookie referer details like in try
33-
var freeTrialExpireCachedQuery = `try_functionstimer`;
34-
var discoverMoreButton = `try_functionsdiscovermore`;
35-
this.freeTrialUri = `${window.location.protocol}//azure.microsoft.com/${window.navigator.language}/free?WT.mc_id=${freeTrialExpireCachedQuery}`;
36-
this.discoverMoreUri = `${window.location.protocol}//azure.microsoft.com/${window.navigator.language}/services/functions/?WT.mc_id=${discoverMoreButton}`;
37+
this.freeTrialUri = `${window.location.protocol}//azure.microsoft.com/${window.navigator.language}/free` + ((typeof mixpanel !== 'undefined') ? "?correlationId=" + mixpanel.get_distinct_id() : "");
38+
this.discoverMoreUri = `${window.location.protocol}//azure.microsoft.com/${window.navigator.language}/services/functions/` + ((typeof mixpanel !== 'undefined') ? "?correlationId=" + mixpanel.get_distinct_id() : "");
3739

3840
var callBack = () => {
3941
window.setTimeout(() => {
@@ -74,4 +76,14 @@ export class TryNowComponent implements OnInit {
7476
n = n + '';
7577
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
7678
}
79+
80+
trackClick(buttonName: string) {
81+
if (buttonName) {
82+
try {
83+
this._aiService.trackEvent(buttonName, {expired:this.trialExpired.toString()});
84+
} catch (error) {
85+
this._aiService.trackException(error, 'trackClick');
86+
}
87+
}
88+
}
7789
}

AzureFunctions.Client/app/models/portal-resources.ts

-2
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,5 @@ export class PortalResources
542542
public static twilioSms_to_label: string = "twilioSms_to_label";
543543
public static eventHubTrigger_consumerGroup_errorText: string = "eventHubTrigger_consumerGroup_errorText";
544544
public static documentDB_displayName: string = "documentDB_displayName";
545-
public static sendGrid_apiKey_help: string = "sendGrid_apiKey_help";
546-
public static sendGrid_apiKey_label: string = "sendGrid_apiKey_label";
547545
}
548546

AzureFunctions.Client/app/models/ui-resource.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface UIResource {
1212
templateName: string;
1313
isExtended: boolean;
1414
csmId: string;
15+
userName: string;
1516
}
1617

1718
export enum AppService {

AzureFunctions.Client/app/services/ai.service.ts

+23-3
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import {Injectable} from '@angular/core';
22
import {IAppInsights, IConfig, SeverityLevel} from '../models/app-insights';
33

44
declare var appInsights: IAppInsights;
5+
declare var mixpanel: any;
56

67
function AiDefined() {
78
return (target: Object, functionName: string, descriptor: TypedPropertyDescriptor<any>) => {
89
let originalMethod = descriptor.value;
910
descriptor.value = function(...args: any[]) {
10-
if (typeof(appInsights) !== 'undefined' &&
11+
if (typeof (appInsights) !== 'undefined' && typeof (mixpanel) !== 'undefined' &&
1112
typeof(appInsights[functionName]) !== 'undefined') {
1213
return originalMethod.apply(this, args);
1314
} else {
@@ -61,9 +62,16 @@ export class AiService implements IAppInsights {
6162
*/
6263
@AiDefined()
6364
startTrackPage(name?: string) {
65+
mixpanel.track('Functions Start Page View', { page: name, properties: this.addMixPanelProperties(null)});
6466
return appInsights.startTrackPage(name);
6567
}
6668

69+
addMixPanelProperties(properties?) {
70+
properties = properties || {};
71+
properties['sitename'] = 'functions';
72+
properties["correlationId"] = mixpanel.get_distinct_id();
73+
}
74+
6775
/**
6876
* Logs how long a page or other item was visible, after {@link startTrackPage}. Call this when the page closes.
6977
* @param name The string you used as the name in startTrackPage. Defaults to the document title.
@@ -73,6 +81,7 @@ export class AiService implements IAppInsights {
7381
*/
7482
@AiDefined()
7583
stopTrackPage(name?: string, url?: string, properties?: { [name: string]: string; }, measurements?: { [name: string]: number; }) {
84+
mixpanel.track('Functions Stop Page View', { page: name, url: url, properties: this.addMixPanelProperties(properties), measurements : measurements });
7685
return appInsights.stopTrackPage(name, url, properties, measurements);
7786
}
7887

@@ -86,6 +95,7 @@ export class AiService implements IAppInsights {
8695
*/
8796
@AiDefined()
8897
trackPageView(name?: string, url?: string, properties?: { [name: string]: string; }, measurements?: { [name: string]: number; }, duration?: number) {
98+
mixpanel.track('Functions Page Viewed', { page: name, url: url, properties: this.addMixPanelProperties(properties), measurements: measurements });
8999
return appInsights.trackPageView(name, url, properties, measurements);
90100
}
91101

@@ -95,6 +105,7 @@ export class AiService implements IAppInsights {
95105
*/
96106
@AiDefined()
97107
startTrackEvent(name: string) {
108+
mixpanel.track(name);
98109
return appInsights.startTrackEvent(name);
99110
}
100111

@@ -106,7 +117,8 @@ export class AiService implements IAppInsights {
106117
* @param measurements map[string, number] - metrics associated with this event, displayed in Metrics Explorer on the portal. Defaults to empty.
107118
*/
108119
@AiDefined()
109-
stopTrackEvent(name: string, properties?: { [name: string]: string; }, measurements?: { [name: string]: number; }){
120+
stopTrackEvent(name: string, properties?: { [name: string]: string; }, measurements?: { [name: string]: number; }) {
121+
mixpanel.track(name, { properties: this.addMixPanelProperties(properties), measurements: measurements });
110122
return appInsights.stopTrackEvent(name, properties, measurements);
111123
}
112124

@@ -118,6 +130,7 @@ export class AiService implements IAppInsights {
118130
*/
119131
@AiDefined()
120132
trackEvent(name: string, properties?: { [name: string]: string; }, measurements?: { [name: string]: number; }){
133+
mixpanel.track(name, { properties: this.addMixPanelProperties(properties), measurements: measurements });
121134
return appInsights.trackEvent(name, properties, measurements);
122135
}
123136

@@ -159,7 +172,8 @@ export class AiService implements IAppInsights {
159172
* @param max The largest measurement in the sample. Defaults to the average.
160173
*/
161174
@AiDefined()
162-
trackMetric(name: string, average: number, sampleCount?: number, min?: number, max?: number, properties?: { [name: string]: string; }){
175+
trackMetric(name: string, average: number, sampleCount?: number, min?: number, max?: number, properties?: { [name: string]: string; }) {
176+
mixpanel.track(name, { average: average, sampleCount: sampleCount, min: min, max: max, properties: this.addMixPanelProperties(properties) });
163177
return appInsights.trackMetric(name, average, sampleCount, min, max, properties);
164178
}
165179

@@ -192,6 +206,12 @@ export class AiService implements IAppInsights {
192206
*/
193207
@AiDefined()
194208
setAuthenticatedUserContext(authenticatedUserId: string, accountId?: string) {
209+
var userDetails = authenticatedUserId.split("#");
210+
if (userDetails.length === 2) {
211+
mixpanel.alias(userDetails[1]);
212+
} else {
213+
mixpanel.alias(authenticatedUserId);
214+
}
195215
return appInsights.setAuthenticatedUserContext(authenticatedUserId, accountId);
196216
}
197217

AzureFunctions.Client/app/services/functions.service.ts

+6-25
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {ArmService} from './arm.service';
3131
import {BroadcastEvent} from '../models/broadcast-event';
3232
import {ErrorEvent} from '../models/error-event';
3333

34+
declare var mixpanel: any;
3435

3536
@Injectable()
3637
export class FunctionsService {
@@ -482,8 +483,7 @@ export class FunctionsService {
482483
}
483484

484485
getTrialResource(provider?: string): Observable<UIResource> {
485-
var url = this.tryAppServiceUrl + "/api/resource"
486-
+ "?appServiceName=" + encodeURIComponent("Function")
486+
var url = this.tryAppServiceUrl + "/api/resource?appServiceName=Function"
487487
+ (provider ? "&provider=" + provider : "");
488488

489489
return this._http.get(url, { headers: this.getTryAppServiceHeaders() })
@@ -492,15 +492,15 @@ export class FunctionsService {
492492
}
493493

494494
createTrialResource(selectedTemplate: FunctionTemplate, provider: string, functionName: string): Observable<UIResource> {
495-
var url = this.tryAppServiceUrl + "/api/resource"
496-
+ "?appServiceName=" + encodeURIComponent("Function")
495+
var url = this.tryAppServiceUrl + "/api/resource?appServiceName=Function"
497496
+ (provider ? "&provider=" + provider : "")
498497
+ "&templateId=" + encodeURIComponent(selectedTemplate.id)
499-
+ "&functionName=" + encodeURIComponent(functionName);
498+
+ "&functionName=" + encodeURIComponent(functionName)
499+
+ ((typeof mixpanel !== 'undefined') ? "&correlationId="+ mixpanel.get_distinct_id():"") ;
500500

501501
var template = <ITryAppServiceTemplate>{
502502
name: selectedTemplate.id,
503-
appService: "FunctionsContainer",
503+
appService: "Function",
504504
language: selectedTemplate.metadata.language,
505505
githubRepo: ""
506506
};
@@ -510,25 +510,6 @@ export class FunctionsService {
510510
.map<UIResource>(r => r.json());
511511
}
512512

513-
redirectToCreateResource(selectedTemplate: FunctionTemplate, provider: string) {
514-
var url = this.tryAppServiceUrl + "/api/resource"
515-
+ "?appServiceName=" + encodeURIComponent("Functions")
516-
+ (provider ? "&provider=" + provider : "")
517-
+ "&templateId=" + encodeURIComponent(selectedTemplate.id);
518-
window.location.href = url;
519-
520-
}
521-
522-
extendTrialResource() {
523-
var url = this.tryAppServiceUrl + "/api/resource/extend"
524-
+ "?appServiceName=" + encodeURIComponent("Function")
525-
+ (this.selectedProvider ? "&provider=" + this.selectedProvider : "");
526-
527-
return this._http.post(url, '', { headers: this.getTryAppServiceHeaders() })
528-
.retryWhen(this.retryAntares)
529-
.map<UIResource>(r => r.json());
530-
}
531-
532513
updateFunction(fi: FunctionInfo) {
533514
ClearAllFunctionCache(fi);
534515
return this._http.put(fi.href, JSON.stringify(fi), { headers: this.getScmSiteHeaders() })

AzureFunctions.Client/app/services/user.service.ts

+9
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@ export class UserService {
5252
}
5353
}
5454

55+
setTryUserName(userName: string) {
56+
if (userName) {
57+
try {
58+
this._aiService.setAuthenticatedUserContext(userName);
59+
} catch (error) {
60+
this._aiService.trackException(error, 'setToken');
61+
}
62+
}
63+
}
5564
setLanguage(lang: string) {
5665
this.languageSubject.next(lang);
5766
}

AzureFunctions.Client/templates/trial-expired.component.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ <h1>{{ 'trialExpired_enjoyedHostingFunctions' | translate }}</h1>
1414
<h3>{{ 'trialExpired_signupForAzure' | translate }}</h3>
1515
<div style="height:50px;"></div>
1616
<div>
17-
<a [attr.href]="freeTrialUri" target="_blank" class="trial-expired-signup-button">
17+
<a [attr.href]="freeTrialUri" (click)="trackClick('expiredFreeTrialClick')" target="_blank" class="trial-expired-signup-button">
1818
{{ 'tryNow_createFreeAzureAccount' | translate }}
1919
</a>
2020
</div>

AzureFunctions.Client/templates/try-now.component.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
</div>
66
<div>{{ 'tryNow_trialTimeRemaining' | translate }} <span id="timer-text">{{timerText}}</span></div>
77
<div class="free-trial-wrapper">
8-
<a [attr.href]="freeTrialUri" target="_blank" class="signup-button ">
8+
<a [attr.href]="freeTrialUri" (click)="trackClick('freeTrialTopClick')" target="_blank" class="signup-button ">
99
{{ 'tryNow_createFreeAzureAccount' | translate }}
1010
</a>
1111
<i [tooltip]="freeAccountTooltip" class="fa fa-question-circle"></i>
@@ -16,7 +16,7 @@
1616
</tooltip-content>
1717
</div>
1818
<span class="discover-more-wrapper">
19-
<a [attr.href]="discoverMoreUri" target="_blank" class="discover-more-button">
19+
<a [attr.href]="discoverMoreUri" (click)="trackClick('discoverMoreClick')" target="_blank" class="discover-more-button">
2020
{{ 'tryNow_discoverMore' | translate }}
2121
</a>
2222
</span>

0 commit comments

Comments
 (0)