Skip to content

Commit a026de4

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 c6721aa commit a026de4

5 files changed

Lines changed: 141 additions & 12 deletions

File tree

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
@@ -1368,9 +1368,13 @@ interface WalkthroughCompletionEvent {
13681368
'context.key': WalkthroughContextKeys;
13691369
}
13701370

1371-
type WelcomeActionNames = 'plus/sign-up';
1371+
type WelcomeActionNames = 'plus/sign-up' | 'dismiss' | 'shown';
13721372

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

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

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import { consume } from '@lit/context';
22
import { css, html, LitElement, nothing } from 'lit';
3-
import { customElement, property, state } from 'lit/decorators.js';
3+
import { customElement, property, query, state } from 'lit/decorators.js';
44
import { isSubscriptionTrialOrPaidFromState } from '../../../../plus/gk/utils/subscription.utils.js';
55
import type { State } from '../../../home/protocol.js';
66
import { CollapseSectionCommand } from '../../../home/protocol.js';
77
import { ipcContext } from '../../shared/contexts/ipc.js';
8+
import type { TelemetryContext } from '../../shared/contexts/telemetry.js';
9+
import { telemetryContext } from '../../shared/contexts/telemetry.js';
810
import type { HostIpc } from '../../shared/ipc.js';
911
import '../../shared/components/button.js';
1012
import { stateContext } from '../context.js';
13+
import type { GlWelcomePage } from './welcome-page.js';
1114
import './welcome-page.js';
1215

1316
declare global {
@@ -74,6 +77,12 @@ export class GlWelcomeOverlay extends LitElement {
7477
@state()
7578
private _ipc!: HostIpc;
7679

80+
@consume({ context: telemetryContext as { __context__: TelemetryContext } })
81+
_telemetry!: TelemetryContext;
82+
83+
@query('gl-welcome-page')
84+
private welcomePage?: GlWelcomePage;
85+
7786
@state()
7887
private closed = false;
7988

@@ -108,6 +117,16 @@ export class GlWelcomeOverlay extends LitElement {
108117

109118
private onClose() {
110119
this.closed = true;
120+
const telemetryData = this.welcomePage?.getTelemetryData();
121+
this._telemetry.sendEvent({
122+
name: 'welcome/action',
123+
data: {
124+
name: 'dismiss',
125+
viewedCarouselPages: telemetryData?.viewedCarouselPages,
126+
proButtonClicked: telemetryData?.proButtonClicked,
127+
},
128+
source: { source: 'welcome', detail: 'close-on-overlay' },
129+
});
111130

112131
this._ipc.sendCommand(CollapseSectionCommand, {
113132
section: 'welcomeOverlay',

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

Lines changed: 41 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,52 @@ 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', detail: 'dismiss-in-body' },
96+
});
6897
this.dispatchEvent(new CustomEvent('close'));
6998
}
7099

100+
private onFeatureAppeared() {
101+
this.telemetryData.viewedCarouselPages++;
102+
}
103+
104+
getTelemetryData(): TelemetryData {
105+
return { ...this.telemetryData };
106+
}
107+
71108
override render(): unknown {
72109
const themeSuffix = this.isLightTheme ? 'light' : 'dark';
73110
return html`
@@ -85,7 +122,7 @@ export class GlWelcomePage extends LitElement {
85122
</div>
86123
87124
<div class="section">
88-
<gl-feature-carousel>
125+
<gl-feature-carousel @gl-feature-appeared=${this.onFeatureAppeared}>
89126
<gl-feature-card class="card">
90127
<img
91128
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.js';
44
import '../../shared/components/code-icon.js';
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)