Skip to content

Commit d5c6739

Browse files
committed
Tracks welcome page carousel views and other events
Adds telemetry to track the number of carousel pages viewed on the welcome page. This data will help us understand user engagement with the welcome page features. (#4769, #4773, PLG-138)
1 parent cb09aa6 commit d5c6739

File tree

4 files changed

+117
-11
lines changed

4 files changed

+117
-11
lines changed

docs/telemetry-events.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4130,10 +4130,9 @@ or
41304130
41314131
```typescript
41324132
{
4133-
'command': string,
4134-
'detail': string,
4135-
'name': 'plus/sign-up',
4136-
'type': 'command'
4133+
'name': 'plus/sign-up' | 'dismiss' | 'shown',
4134+
'proButtonClicked': boolean,
4135+
'viewedCarouselPages': number
41374136
}
41384137
```
41394138

src/constants.telemetry.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1369,9 +1369,13 @@ interface WalkthroughCompletionEvent {
13691369
'context.key': WalkthroughContextKeys;
13701370
}
13711371

1372-
type WelcomeActionNames = 'plus/sign-up';
1372+
type WelcomeActionNames = 'plus/sign-up' | 'dismiss' | 'shown';
13731373

1374-
type WelcomeActionEvent = { type: 'command'; name: WelcomeActionNames; command: string; detail?: string };
1374+
type WelcomeActionEvent = {
1375+
name: WelcomeActionNames;
1376+
viewedCarouselPages?: number;
1377+
proButtonClicked?: boolean;
1378+
};
13751379

13761380
type WebviewContextEventData = {
13771381
'context.webview.id': string;

src/webviews/apps/home/components/welcome-page.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,19 @@ const helpBlameUrl =
2626
const helpRevisionNavigationUrl =
2727
'https://help.gitkraken.com/gitlens/gitlens-features/?utm_source=gitlens-extension&utm_medium=in-app-links#revision-navigation';
2828

29+
type TelemetryData = {
30+
viewedCarouselPages: number;
31+
proButtonClicked: boolean;
32+
};
33+
2934
@customElement('gl-welcome-page')
3035
export class GlWelcomePage extends LitElement {
3136
static override styles = [scrollableBase, welcomeStyles];
32-
//static override styles = welcomeStyles;
37+
38+
private telemetryData: TelemetryData = {
39+
viewedCarouselPages: 0,
40+
proButtonClicked: false,
41+
};
3342

3443
@property({ type: Boolean })
3544
closeable = false;
@@ -50,24 +59,48 @@ export class GlWelcomePage extends LitElement {
5059
@consume({ context: telemetryContext as { __context__: TelemetryContext } })
5160
_telemetry!: TelemetryContext;
5261

62+
override connectedCallback(): void {
63+
super.connectedCallback?.();
64+
this._telemetry.sendEvent({
65+
name: 'welcome/action',
66+
data: {
67+
name: 'shown',
68+
},
69+
source: { source: 'welcome' },
70+
});
71+
}
72+
5373
private onStartTrial() {
74+
this.telemetryData.proButtonClicked = true;
5475
const command: GlCommands = 'gitlens.plus.signUp';
5576
this._telemetry.sendEvent({
5677
name: 'welcome/action',
5778
data: {
58-
type: 'command',
5979
name: 'plus/sign-up',
60-
command: command,
80+
viewedCarouselPages: this.telemetryData.viewedCarouselPages,
6181
},
6282
source: { source: 'welcome' },
6383
});
6484
this._ipc.sendCommand(ExecuteCommand, { command: command, args: [{ source: 'welcome' }] });
6585
}
6686

6787
private onClose() {
88+
this._telemetry.sendEvent({
89+
name: 'welcome/action',
90+
data: {
91+
name: 'dismiss',
92+
viewedCarouselPages: this.telemetryData.viewedCarouselPages,
93+
proButtonClicked: this.telemetryData.proButtonClicked,
94+
},
95+
source: { source: 'welcome' },
96+
});
6897
this.dispatchEvent(new CustomEvent('close'));
6998
}
7099

100+
private onFeatureAppeared() {
101+
this.telemetryData.viewedCarouselPages++;
102+
}
103+
71104
override render(): unknown {
72105
const themeSuffix = this.isLightTheme ? 'light' : 'dark';
73106
return html`
@@ -85,7 +118,7 @@ export class GlWelcomePage extends LitElement {
85118
</div>
86119
87120
<div class="section">
88-
<gl-feature-carousel>
121+
<gl-feature-carousel @gl-feature-appeared=${this.onFeatureAppeared}>
89122
<gl-feature-card class="card">
90123
<img
91124
slot="image"

src/webviews/apps/home/components/welcome-parts.ts

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { css, html, LitElement } from 'lit';
2-
import { customElement, queryAssignedElements, state } from 'lit/decorators.js';
2+
import { customElement, property, queryAssignedElements, state } from 'lit/decorators.js';
33
import '../../shared/components/button';
44
import '../../shared/components/code-icon';
55

@@ -250,6 +250,42 @@ export class GlFeatureCard extends LitElement {
250250
`,
251251
];
252252

253+
private hasBeenVisible: boolean = false;
254+
255+
override updated(changedProperties: Map<PropertyKey, unknown>): void {
256+
super.updated(changedProperties);
257+
258+
const isVisible = isElementVisible(this);
259+
if (!isVisible || this.hasBeenVisible) return;
260+
261+
const isInViewport = isElementInViewport(this);
262+
const isPartiallyInViewport = isElementPartiallyInViewport(this);
263+
const visible = isVisible && (isInViewport || isPartiallyInViewport);
264+
if (visible && !this.hasBeenVisible) {
265+
this.hasBeenVisible = true;
266+
267+
// Dispatch a custom event when any property changes
268+
this.dispatchEvent(
269+
new CustomEvent('gl-feature-appeared', {
270+
detail: {
271+
changedProperties: Array.from(changedProperties.keys()),
272+
visibility: {
273+
isVisible: isVisible,
274+
isInViewport: isInViewport,
275+
isPartiallyInViewport: isPartiallyInViewport,
276+
},
277+
},
278+
bubbles: true,
279+
composed: true,
280+
}),
281+
);
282+
}
283+
}
284+
285+
/** is used to make the component reactive to 'data-active' attribute changes */
286+
@property({ type: Boolean, reflect: true, attribute: 'data-active' })
287+
private _dataActive: boolean = false;
288+
253289
override render(): unknown {
254290
return html`
255291
<div class="image">
@@ -358,3 +394,37 @@ export class GlScrollableFeatures extends LitElement {
358394
return html`<div class="content"><slot></slot></div>`;
359395
}
360396
}
397+
398+
function isElementVisible(element: HTMLElement): boolean {
399+
// Check if element is hidden by display: none or visibility: hidden
400+
const style = window.getComputedStyle(element);
401+
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
402+
return false;
403+
}
404+
405+
// Check if element has zero dimensions
406+
const rect = element.getBoundingClientRect();
407+
if (rect.width === 0 || rect.height === 0) {
408+
return false;
409+
}
410+
411+
return true;
412+
}
413+
414+
function isElementInViewport(element: HTMLElement): boolean {
415+
const rect = element.getBoundingClientRect();
416+
return (
417+
rect.top >= 0 &&
418+
rect.left >= 0 &&
419+
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
420+
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
421+
);
422+
}
423+
424+
function isElementPartiallyInViewport(element: HTMLElement): boolean {
425+
const rect = element.getBoundingClientRect();
426+
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
427+
const windowWidth = window.innerWidth || document.documentElement.clientWidth;
428+
429+
return rect.bottom > 0 && rect.right > 0 && rect.top < windowHeight && rect.left < windowWidth;
430+
}

0 commit comments

Comments
 (0)