@elementor/editor-audits 4.4.0-1149 → 4.4.0-beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elementor/editor-audits",
3
- "version": "4.4.0-1149",
3
+ "version": "4.4.0-beta2",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -39,21 +39,22 @@
39
39
  "dev": "tsup --config=../../tsup.dev.ts"
40
40
  },
41
41
  "dependencies": {
42
- "@elementor/editor": "4.4.0-1149",
43
- "@elementor/editor-mcp": "4.4.0-1149",
44
- "@elementor/editor-app-bar": "4.4.0-1149",
45
- "@elementor/editor-elements": "4.4.0-1149",
46
- "@elementor/editor-floating-panels": "4.4.0-1149",
47
- "@elementor/editor-notifications": "4.4.0-1149",
48
- "@elementor/editor-props": "4.4.0-1149",
49
- "@elementor/editor-styles": "4.4.0-1149",
50
- "@elementor/editor-v1-adapters": "4.4.0-1149",
51
- "@elementor/events": "4.4.0-1149",
52
- "@elementor/http-client": "4.4.0-1149",
42
+ "@elementor/editor": "4.4.0-beta2",
43
+ "@elementor/editor-mcp": "4.4.0-beta2",
44
+ "@elementor/editor-app-bar": "4.4.0-beta2",
45
+ "@elementor/editor-current-user": "4.4.0-beta2",
46
+ "@elementor/editor-elements": "4.4.0-beta2",
47
+ "@elementor/editor-floating-panels": "4.4.0-beta2",
48
+ "@elementor/editor-notifications": "4.4.0-beta2",
49
+ "@elementor/editor-props": "4.4.0-beta2",
50
+ "@elementor/editor-styles": "4.4.0-beta2",
51
+ "@elementor/editor-v1-adapters": "4.4.0-beta2",
52
+ "@elementor/events": "4.4.0-beta2",
53
+ "@elementor/http-client": "4.4.0-beta2",
53
54
  "@elementor/icons": "~1.78.1",
54
- "@elementor/locations": "4.4.0-1149",
55
- "@elementor/session": "4.4.0-1149",
56
- "@elementor/store": "4.4.0-1149",
55
+ "@elementor/locations": "4.4.0-beta2",
56
+ "@elementor/session": "4.4.0-beta2",
57
+ "@elementor/store": "4.4.0-beta2",
57
58
  "@elementor/ui": "1.41.4"
58
59
  },
59
60
  "peerDependencies": {
@@ -32,6 +32,7 @@ export const audit: Audit = {
32
32
  ctaLabel: __( 'Create', 'elementor' ),
33
33
  secondaryCtaLabel: __( 'Learn more', 'elementor' ),
34
34
  secondaryCtaUrl: 'https://elementor.com/help/start-using-ally/#statement',
35
+ appInstalled: isReady,
35
36
  },
36
37
  ],
37
38
  };
@@ -34,6 +34,7 @@ export const audit: Audit = {
34
34
  label: __( 'No accessibility widget is active on this site.', 'elementor' ),
35
35
  externalUrl: isReady ? ctx.pageContext.ally_widget_settings_url : ctx.pageContext.ally_plugin_url,
36
36
  ctaLabel: __( 'Activate', 'elementor' ),
37
+ appInstalled: isReady,
37
38
  },
38
39
  ],
39
40
  };
@@ -26,6 +26,7 @@ export const audit: Audit = {
26
26
  label: __( 'Cookiez plugin is not installed or active.', 'elementor' ),
27
27
  externalUrl: ctx.pageContext.cookiez_plugin_url,
28
28
  ctaLabel: __( 'Set up cookie consent', 'elementor' ),
29
+ appInstalled: ctx.pageContext.cookiez_plugin_active,
29
30
  },
30
31
  ],
31
32
  };
@@ -45,6 +45,7 @@ export const audit: Audit = {
45
45
  auditId: audit.id,
46
46
  label,
47
47
  ctaLabel: __( 'Enable', 'elementor' ),
48
+ appInstalled: true,
48
49
  externalUrl: ctx.pageContext.cookiez_consent_mode_settings_url,
49
50
  },
50
51
  ],
@@ -38,6 +38,7 @@ export const audit: Audit = {
38
38
  label: __( 'Image is missing alt text.', 'elementor' ),
39
39
  externalUrl: isReady ? ctx.pageContext.ally_scan_url : ctx.pageContext.ally_plugin_url,
40
40
  ctaLabel: __( 'Fix with Ally', 'elementor' ),
41
+ appInstalled: isReady,
41
42
  } );
42
43
  }
43
44
  } );
@@ -57,6 +57,7 @@ export const audit: Audit = {
57
57
  ? ctx.pageContext.image_optimization_settings_url
58
58
  : ctx.pageContext.image_optimization_plugin_url,
59
59
  ctaLabel: __( 'Optimize all', 'elementor' ),
60
+ appInstalled: isReady,
60
61
  } ) );
61
62
 
62
63
  return violations.length === 0
@@ -65,6 +65,7 @@ export const audit: Audit = {
65
65
  ? ctx.pageContext.image_optimization_settings_url
66
66
  : ctx.pageContext.image_optimization_plugin_url,
67
67
  ctaLabel: __( 'Optimize all', 'elementor' ),
68
+ appInstalled: isReady,
68
69
  } ) );
69
70
 
70
71
  return violations.length === 0
@@ -23,6 +23,7 @@ export const audit: Audit = {
23
23
  auditId: audit.id,
24
24
  label: __( 'This page has not been scanned for cookies yet.', 'elementor' ),
25
25
  externalUrl: isReady ? ctx.pageContext.cookiez_scan_url : ctx.pageContext.cookiez_plugin_action_url,
26
+ appInstalled: isReady,
26
27
  },
27
28
  ],
28
29
  };
@@ -6,6 +6,8 @@ import { __ } from '@wordpress/i18n';
6
6
 
7
7
  import { AUDIT_PANEL_ID } from '../constants';
8
8
  import { useAuditReport } from '../hooks/use-audit-report';
9
+ import { useTrackPanelViewed } from '../hooks/use-track-panel-viewed';
10
+ import { trackPanelClosed } from '../utils/tracking';
9
11
  import AuditFeedback from './audit-feedback';
10
12
  import ErrorPage from './pages/error-page';
11
13
  import LoadingPage from './pages/loading-page';
@@ -16,8 +18,10 @@ import RescanButton from './rescan-button';
16
18
  export default function AuditPanel() {
17
19
  const { status, report, error, isStale, run } = useAuditReport();
18
20
 
21
+ useTrackPanelViewed();
22
+
19
23
  const hostDocumentId = getHostDocumentId() ?? 0;
20
- const onRun = () => run( hostDocumentId );
24
+ const onRun = () => run( hostDocumentId, report ? 'rescan' : 'run_audit' );
21
25
  const lastScanLabel = report ? new Date( report.runAt ).toLocaleTimeString() : null;
22
26
 
23
27
  return (
@@ -27,6 +31,7 @@ export default function AuditPanel() {
27
31
  title={ __( 'Page Audit', 'elementor' ) }
28
32
  badge={ __( 'Beta', 'elementor' ) }
29
33
  titleVariant="subtitle2"
34
+ onClose={ trackPanelClosed }
30
35
  />
31
36
  <FloatingPanelBody
32
37
  sx={
@@ -5,14 +5,17 @@ import { Button, IconButton, Tooltip } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
6
6
 
7
7
  import { ANGIE_FIX_ENTRY_POINT, CREATE_WIDGET_EVENT } from '../constants';
8
+ import { type AuditMeta } from '../types';
9
+ import { trackFindingClicked } from '../utils/tracking';
8
10
 
9
11
  type Props = {
12
+ audit: AuditMeta;
10
13
  prompt: string;
11
14
  panelZIndex: number;
12
15
  variant?: 'icon' | 'button';
13
16
  };
14
17
 
15
- export default function FixViolationWithAngie( { prompt, panelZIndex, variant = 'icon' }: Props ) {
18
+ export default function FixViolationWithAngie( { audit, prompt, panelZIndex, variant = 'icon' }: Props ) {
16
19
  const label = __( 'Fix with Angie', 'elementor' );
17
20
  const href = `#angie-prompt=${ encodeURIComponent( prompt ) }`;
18
21
 
@@ -22,6 +25,12 @@ export default function FixViolationWithAngie( { prompt, panelZIndex, variant =
22
25
 
23
26
  if ( isAngieAvailable() ) {
24
27
  sendPromptToAngie( prompt );
28
+ trackFindingClicked( {
29
+ audit,
30
+ locationL2: 'issues',
31
+ actionType: 'fix_with_angie',
32
+ interactionResult: 'angie_prompt_sent',
33
+ } );
25
34
  return;
26
35
  }
27
36
 
@@ -5,6 +5,7 @@ import { __ } from '@wordpress/i18n';
5
5
  import { PROMOTIONS } from '../register-promotions';
6
6
  import { type PageAuditReport } from '../types';
7
7
  import { findAuditRun } from '../utils/find-audit-run';
8
+ import { trackFixWithApp } from '../utils/tracking';
8
9
  import PromotionCard from './promotion-card';
9
10
 
10
11
  type Props = {
@@ -58,8 +59,14 @@ export default function Promotions( { report }: Props ) {
58
59
  ctaLabel={ config.ctaLabel }
59
60
  ctaDisabled={ ! ctaUrl }
60
61
  onCtaClick={ () => {
61
- if ( ctaUrl ) {
62
- window.open( ctaUrl, '_blank' );
62
+ if ( ! ctaUrl ) {
63
+ return;
64
+ }
65
+
66
+ window.open( ctaUrl, '_blank', 'noopener' );
67
+
68
+ if ( run.result.status === 'fail' ) {
69
+ trackFixWithApp( run.audit, run.result.violations[ 0 ], 'overview' );
63
70
  }
64
71
  } }
65
72
  />
@@ -7,14 +7,22 @@ type Props = {
7
7
  externalUrl: string;
8
8
  variant?: 'outlined' | 'text';
9
9
  withIcon?: boolean;
10
+ onOpen?: () => void;
10
11
  };
11
12
 
12
- export default function ViolationCtaButton( { ctaLabel, externalUrl, variant = 'outlined', withIcon = false }: Props ) {
13
+ export default function ViolationCtaButton( {
14
+ ctaLabel,
15
+ externalUrl,
16
+ variant = 'outlined',
17
+ withIcon = false,
18
+ onOpen,
19
+ }: Props ) {
13
20
  const handleClick = ( event: React.MouseEvent< HTMLButtonElement > ) => {
14
21
  event.stopPropagation();
15
22
  event.preventDefault();
16
23
 
17
24
  window.open( externalUrl, '_blank', 'noopener' );
25
+ onOpen?.();
18
26
  };
19
27
 
20
28
  return (
@@ -11,6 +11,7 @@ import { type AuditMeta, type AuditViolation } from '../types';
11
11
  import { buildAngiePrompt } from '../utils/build-angie-prompt';
12
12
  import { isScoredAudit } from '../utils/is-scored-audit';
13
13
  import { onKeyboardClick } from '../utils/keyboard-click';
14
+ import { trackFindingClicked, trackFixWithApp } from '../utils/tracking';
14
15
  import FixViolationWithAngie from './fix-violation-with-angie';
15
16
  import SeverityIcon from './severity-icons';
16
17
  import ViolationCtaButton from './violation-cta-button';
@@ -52,10 +53,12 @@ function StatusIndicator( { audit, violations }: Pick< Props, 'audit' | 'violati
52
53
  }
53
54
 
54
55
  function GuidanceAction( {
56
+ audit,
55
57
  violation,
56
58
  sharedAngiePrompt,
57
59
  panelZIndex,
58
60
  }: {
61
+ audit: AuditMeta;
59
62
  violation: AuditViolation;
60
63
  sharedAngiePrompt?: string;
61
64
  panelZIndex: number;
@@ -73,6 +76,7 @@ function GuidanceAction( {
73
76
  ctaLabel={ violation.ctaLabel as string }
74
77
  externalUrl={ violation.externalUrl as string }
75
78
  withIcon
79
+ onOpen={ () => trackFixWithApp( audit, violation, 'issues' ) }
76
80
  />
77
81
  ) }
78
82
  { hasPrimaryCta && violation.secondaryCtaLabel && violation.secondaryCtaUrl && (
@@ -83,7 +87,12 @@ function GuidanceAction( {
83
87
  />
84
88
  ) }
85
89
  { sharedAngiePrompt && (
86
- <FixViolationWithAngie prompt={ sharedAngiePrompt } panelZIndex={ panelZIndex } variant="button" />
90
+ <FixViolationWithAngie
91
+ audit={ audit }
92
+ prompt={ sharedAngiePrompt }
93
+ panelZIndex={ panelZIndex }
94
+ variant="button"
95
+ />
87
96
  ) }
88
97
  </Box>
89
98
  );
@@ -113,9 +122,22 @@ export default function ViolationRow( { audit, expanded, onToggleExpand, skipRea
113
122
  const panelZIndex = useFloatingPanelZIndex( AUDIT_PANEL_ID );
114
123
  const sharedAngiePrompt = getSharedAngiePrompt( violations );
115
124
 
125
+ const viewIssue = ( violation: AuditViolation ) => {
126
+ const interactionResult = focusViolation( violation );
127
+
128
+ if ( interactionResult ) {
129
+ trackFindingClicked( {
130
+ audit,
131
+ locationL2: 'issues',
132
+ actionType: 'view_issue',
133
+ interactionResult,
134
+ } );
135
+ }
136
+ };
137
+
116
138
  const handleGuidanceClick = () => {
117
139
  if ( isGuidanceFocusable && primaryViolation ) {
118
- focusViolation( primaryViolation );
140
+ viewIssue( primaryViolation );
119
141
  }
120
142
  };
121
143
 
@@ -170,6 +192,7 @@ export default function ViolationRow( { audit, expanded, onToggleExpand, skipRea
170
192
  </Typography>
171
193
  { primaryViolation && (
172
194
  <GuidanceAction
195
+ audit={ audit }
173
196
  violation={ primaryViolation }
174
197
  sharedAngiePrompt={ sharedAngiePrompt }
175
198
  panelZIndex={ panelZIndex }
@@ -194,8 +217,8 @@ export default function ViolationRow( { audit, expanded, onToggleExpand, skipRea
194
217
  key={ idx }
195
218
  role="button"
196
219
  tabIndex={ 0 }
197
- onClick={ () => focusViolation( violation ) }
198
- onKeyDown={ onKeyboardClick( () => focusViolation( violation ) ) }
220
+ onClick={ () => viewIssue( violation ) }
221
+ onKeyDown={ onKeyboardClick( () => viewIssue( violation ) ) }
199
222
  sx={ {
200
223
  display: 'flex',
201
224
  alignItems: 'center',
@@ -226,6 +249,7 @@ export default function ViolationRow( { audit, expanded, onToggleExpand, skipRea
226
249
  sx={ { opacity: 0, transition: 'opacity .15s' } }
227
250
  >
228
251
  <FixViolationWithAngie
252
+ audit={ audit }
229
253
  prompt={
230
254
  violation.angiePrompt ?? buildAngiePrompt( angiePromptContext )
231
255
  }
package/src/constants.ts CHANGED
@@ -18,6 +18,19 @@ export const FEEDBACK_SENT_EVENT = 'audit_feedback_sent';
18
18
  export const FEEDBACK_CANCELLED_EVENT = 'audit_feedback_cancelled';
19
19
  export const FEEDBACK_CLOSED_EVENT = 'audit_feedback_closed';
20
20
 
21
+ export const PANEL_VIEWED_EVENT = 'page_audit_panel_viewed';
22
+ export const PANEL_CLOSED_EVENT = 'page_audit_panel_closed';
23
+ export const RUN_STARTED_EVENT = 'page_audit_run_started';
24
+ export const RUN_COMPLETED_EVENT = 'page_audit_run_completed';
25
+ export const FINDING_CLICKED_EVENT = 'page_audit_finding_clicked';
26
+
27
+ export const TRACKING_WINDOW_NAME = 'page_audit';
28
+ export const TRACKING_TARGET_LOCATION = 'panel';
29
+ export const TRACKING_LOCATION_L1 = 'audit_panel';
30
+
31
+ export const FIRST_OPEN_MESSAGE_KEY = 'page-audit-panel-opened';
32
+ export const AUDIT_ID_PREFIX = 'audits/';
33
+
21
34
  export const ALL_CATEGORIES: AuditCategory[] = [
22
35
  'best-practices',
23
36
  'seo',
@@ -2,10 +2,11 @@ import { getContainer, selectElement } from '@elementor/editor-elements';
2
2
  import { __privateOpenRoute as openRoute, __privateRunCommand as runCommand } from '@elementor/editor-v1-adapters';
3
3
 
4
4
  import { type AuditViolation } from '../types';
5
+ import { type ViewIssueResult } from '../utils/tracking';
5
6
 
6
7
  const SCROLL_TO_VIEW_DELAY_MS = 200;
7
8
 
8
- export function focusViolation( violation: AuditViolation ): void {
9
+ export function focusViolation( violation: AuditViolation ): ViewIssueResult | null {
9
10
  if ( violation.elementId ) {
10
11
  const container = getContainer( violation.elementId );
11
12
 
@@ -27,27 +28,30 @@ export function focusViolation( violation: AuditViolation ): void {
27
28
  runCommand( 'panel/editor/open' );
28
29
  }
29
30
 
30
- return;
31
+ return container ? 'element_focused' : null;
31
32
  }
32
33
 
33
34
  if ( violation.externalUrl ) {
34
35
  window.open( violation.externalUrl, '_blank' );
35
- return;
36
+ return 'external_url_opened';
36
37
  }
37
38
 
38
39
  if ( violation.targetHint === 'page-settings' ) {
39
40
  openRoute( 'panel/page-settings/settings' );
40
- return;
41
+ return 'page_settings_opened';
41
42
  }
42
43
 
43
44
  if ( violation.targetHint === 'site-settings' ) {
44
45
  runCommand( 'panel/global/open' );
45
- return;
46
+ return 'site_settings_opened';
46
47
  }
47
48
 
48
49
  if ( violation.targetHint === 'site-identity-settings' ) {
49
50
  void runCommand( 'panel/global/open' ).then( () => {
50
51
  openRoute( 'panel/global/settings-site-identity' );
51
52
  } );
53
+ return 'site_settings_opened';
52
54
  }
55
+
56
+ return null;
53
57
  }
@@ -6,6 +6,7 @@ import { runPageAudit } from '../runner';
6
6
  import { type GlobalState, selectError, selectIsStale, selectReport, selectStatus, slice } from '../store';
7
7
  import { getPersistedReport, persistReport } from '../utils/report-storage';
8
8
  import { SessionExpiredError } from '../utils/session-expiration';
9
+ import { type RunTrigger, trackRunCompleted, trackRunFailed, trackRunStarted } from '../utils/tracking';
9
10
 
10
11
  export function useAuditReport() {
11
12
  const status = useSelector( ( state: GlobalState ) => selectStatus( state ) );
@@ -29,20 +30,24 @@ export function useAuditReport() {
29
30
  }
30
31
  }, [ documentId, report, dispatch ] );
31
32
 
32
- const run = async ( documentIdToRun: number ) => {
33
+ const run = async ( documentIdToRun: number, trigger: RunTrigger ) => {
33
34
  dispatch( slice.actions.runStarted() );
35
+ trackRunStarted( trigger );
34
36
 
35
37
  try {
36
38
  const nextReport = await runPageAudit( documentIdToRun );
37
39
  dispatch( slice.actions.runSucceeded( nextReport ) );
38
40
  persistReport( documentIdToRun, nextReport );
41
+ trackRunCompleted( nextReport );
39
42
  } catch ( e ) {
40
43
  if ( e instanceof SessionExpiredError ) {
41
44
  dispatch( slice.actions.runAborted() );
45
+ trackRunFailed();
42
46
  return;
43
47
  }
44
48
 
45
49
  dispatch( slice.actions.runFailed( e instanceof Error ? e.message : 'Unknown error' ) );
50
+ trackRunFailed();
46
51
  }
47
52
  };
48
53
 
@@ -0,0 +1,31 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { useCurrentUser, useSuppressedMessage } from '@elementor/editor-current-user';
3
+ import { getHostDocumentId } from '@elementor/editor-elements';
4
+ import { __useSelector as useSelector } from '@elementor/store';
5
+
6
+ import { FIRST_OPEN_MESSAGE_KEY } from '../constants';
7
+ import { type GlobalState, selectReport } from '../store';
8
+ import { getPersistedReport } from '../utils/report-storage';
9
+ import { trackPanelViewed } from '../utils/tracking';
10
+
11
+ export function useTrackPanelViewed() {
12
+ const { data: currentUser } = useCurrentUser();
13
+ const [ isFirstOpenMessageSuppressed, suppressFirstOpenMessage ] = useSuppressedMessage( FIRST_OPEN_MESSAGE_KEY );
14
+ const report = useSelector( ( state: GlobalState ) => selectReport( state ) );
15
+ const [ hasPriorReport ] = useState( () => {
16
+ const documentId = getHostDocumentId() ?? 0;
17
+
18
+ return Boolean( report ) || Boolean( documentId && getPersistedReport( documentId ) );
19
+ } );
20
+ const hasTracked = useRef( false );
21
+
22
+ useEffect( () => {
23
+ if ( ! currentUser || hasTracked.current ) {
24
+ return;
25
+ }
26
+
27
+ hasTracked.current = true;
28
+ trackPanelViewed( { hasPriorReport, isFirstOpen: ! isFirstOpenMessageSuppressed } );
29
+ suppressFirstOpenMessage();
30
+ }, [ currentUser, hasPriorReport, isFirstOpenMessageSuppressed, suppressFirstOpenMessage ] );
31
+ }
package/src/types.ts CHANGED
@@ -43,6 +43,7 @@ export type AuditViolation = {
43
43
  ctaLabel?: string;
44
44
  secondaryCtaLabel?: string;
45
45
  secondaryCtaUrl?: string;
46
+ appInstalled?: boolean;
46
47
  };
47
48
 
48
49
  export type PageContextResponse = {
@@ -0,0 +1,45 @@
1
+ import { type PageAuditReport } from '../types';
2
+ import { isScoredAudit } from './is-scored-audit';
3
+ import { toCheckName, toTrackingSeverity, type TrackingSeverity } from './tracking-names';
4
+
5
+ type RequiredFix = {
6
+ check: string;
7
+ severity: TrackingSeverity;
8
+ };
9
+
10
+ export type RunSummary = {
11
+ state: TrackingSeverity | 'success';
12
+ metadata: {
13
+ score: number;
14
+ fix_count: number;
15
+ error_count: number;
16
+ warning_count: number;
17
+ suggestion_count: number;
18
+ required_fixes: RequiredFix[];
19
+ };
20
+ };
21
+
22
+ const SEVERITY_PRIORITY: TrackingSeverity[] = [ 'error', 'warning', 'suggestion' ];
23
+
24
+ export function buildRunSummary( report: PageAuditReport ): RunSummary {
25
+ const requiredFixes: RequiredFix[] = report.auditResults
26
+ .filter( ( { audit, result } ) => result.status === 'fail' && isScoredAudit( audit ) )
27
+ .map( ( { audit } ) => ( { check: toCheckName( audit.id ), severity: toTrackingSeverity( audit.severity ) } ) );
28
+
29
+ const countBySeverity = ( severity: TrackingSeverity ) =>
30
+ requiredFixes.filter( ( fix ) => fix.severity === severity ).length;
31
+
32
+ const highestSeverity = SEVERITY_PRIORITY.find( ( severity ) => countBySeverity( severity ) > 0 );
33
+
34
+ return {
35
+ state: highestSeverity ?? 'success',
36
+ metadata: {
37
+ score: report.overall,
38
+ fix_count: requiredFixes.length,
39
+ error_count: countBySeverity( 'error' ),
40
+ warning_count: countBySeverity( 'warning' ),
41
+ suggestion_count: countBySeverity( 'suggestion' ),
42
+ required_fixes: requiredFixes,
43
+ },
44
+ };
45
+ }
@@ -0,0 +1,12 @@
1
+ import { AUDIT_ID_PREFIX } from '../constants';
2
+ import { type AuditSeverity } from '../types';
3
+
4
+ export type TrackingSeverity = 'error' | 'warning' | 'suggestion';
5
+
6
+ export function toTrackingSeverity( severity: AuditSeverity ): TrackingSeverity {
7
+ return severity === 'info' ? 'suggestion' : severity;
8
+ }
9
+
10
+ export function toCheckName( auditId: string ): string {
11
+ return auditId.replace( AUDIT_ID_PREFIX, '' ).replace( /-/g, '_' );
12
+ }
@@ -0,0 +1,130 @@
1
+ import { getMixpanel } from '@elementor/events';
2
+
3
+ import {
4
+ FINDING_CLICKED_EVENT,
5
+ PANEL_CLOSED_EVENT,
6
+ PANEL_VIEWED_EVENT,
7
+ RUN_COMPLETED_EVENT,
8
+ RUN_STARTED_EVENT,
9
+ TRACKING_LOCATION_L1,
10
+ TRACKING_TARGET_LOCATION,
11
+ TRACKING_WINDOW_NAME,
12
+ } from '../constants';
13
+ import { type AuditMeta, type AuditViolation, type PageAuditReport } from '../types';
14
+ import { buildRunSummary } from './build-run-summary';
15
+ import { toCheckName, toTrackingSeverity } from './tracking-names';
16
+
17
+ export type RunTrigger = 'run_audit' | 'rescan';
18
+
19
+ export type ViewIssueResult =
20
+ | 'element_focused'
21
+ | 'page_settings_opened'
22
+ | 'site_settings_opened'
23
+ | 'external_url_opened';
24
+
25
+ export type FindingLocation = 'overview' | 'issues';
26
+
27
+ type FindingAction =
28
+ | { actionType: 'view_issue'; interactionResult: ViewIssueResult }
29
+ | { actionType: 'fix_with_angie'; interactionResult: 'angie_prompt_sent' }
30
+ | { actionType: 'fix_with_app'; interactionResult: 'app_opened'; appInstalled?: boolean };
31
+
32
+ type FindingClick = FindingAction & {
33
+ audit: AuditMeta;
34
+ locationL2: FindingLocation;
35
+ };
36
+
37
+ const dispatch = ( eventName: string, payload: Record< string, unknown > ) => {
38
+ getMixpanel().dispatchEvent?.( eventName, {
39
+ window_name: TRACKING_WINDOW_NAME,
40
+ target_location: TRACKING_TARGET_LOCATION,
41
+ location_l1: TRACKING_LOCATION_L1,
42
+ ...payload,
43
+ } );
44
+ };
45
+
46
+ export const trackPanelViewed = ( {
47
+ hasPriorReport,
48
+ isFirstOpen,
49
+ }: {
50
+ hasPriorReport: boolean;
51
+ isFirstOpen: boolean;
52
+ } ) => {
53
+ dispatch( PANEL_VIEWED_EVENT, {
54
+ interaction_type: 'view',
55
+ target_type: 'panel',
56
+ target_name: 'page_audit_panel',
57
+ interaction_result: 'panel_viewed',
58
+ metadata: { has_prior_report: hasPriorReport, is_first_open: isFirstOpen },
59
+ } );
60
+ };
61
+
62
+ export const trackPanelClosed = () => {
63
+ dispatch( PANEL_CLOSED_EVENT, {
64
+ interaction_type: 'click',
65
+ target_type: 'button',
66
+ target_name: 'close',
67
+ interaction_result: 'panel_closed',
68
+ } );
69
+ };
70
+
71
+ export const trackRunStarted = ( trigger: RunTrigger ) => {
72
+ dispatch( RUN_STARTED_EVENT, {
73
+ interaction_type: 'click',
74
+ target_type: 'button',
75
+ target_name: trigger,
76
+ interaction_result: 'run_initiated',
77
+ } );
78
+ };
79
+
80
+ const trackRunOutcome = ( interactionResult: 'run_completed' | 'run_failed', details: Record< string, unknown > ) => {
81
+ dispatch( RUN_COMPLETED_EVENT, {
82
+ interaction_type: 'view',
83
+ target_type: 'report',
84
+ target_name: 'audit_report',
85
+ interaction_result: interactionResult,
86
+ ...details,
87
+ } );
88
+ };
89
+
90
+ export const trackRunCompleted = ( report: PageAuditReport ) => {
91
+ const { state, metadata } = buildRunSummary( report );
92
+
93
+ trackRunOutcome( 'run_completed', { state, metadata } );
94
+ };
95
+
96
+ export const trackRunFailed = () => {
97
+ trackRunOutcome( 'run_failed', {} );
98
+ };
99
+
100
+ export const trackFixWithApp = ( audit: AuditMeta, violation: AuditViolation, locationL2: FindingLocation ) => {
101
+ if ( violation.appInstalled === undefined ) {
102
+ return;
103
+ }
104
+
105
+ trackFindingClicked( {
106
+ audit,
107
+ locationL2,
108
+ actionType: 'fix_with_app',
109
+ interactionResult: 'app_opened',
110
+ appInstalled: violation.appInstalled,
111
+ } );
112
+ };
113
+
114
+ export const trackFindingClicked = ( click: FindingClick ) => {
115
+ const { audit, locationL2, actionType, interactionResult } = click;
116
+
117
+ dispatch( FINDING_CLICKED_EVENT, {
118
+ interaction_type: 'click',
119
+ target_type: 'row',
120
+ target_name: 'finding_row',
121
+ action_type: actionType,
122
+ target_value: toCheckName( audit.id ),
123
+ interaction_result: interactionResult,
124
+ state: toTrackingSeverity( audit.severity ),
125
+ location_l2: locationL2,
126
+ ...( click.actionType === 'fix_with_app' && {
127
+ app_status: click.appInstalled ? 'installed' : 'not_installed',
128
+ } ),
129
+ } );
130
+ };