From 683dd6ce72bf2ef905a1dc08fdba2bba00382a46 Mon Sep 17 00:00:00 2001 From: yatarkan Date: Tue, 30 Jul 2024 17:43:18 +0400 Subject: [PATCH 1/7] Add analytics vars --- selector/src/shared/analytics/adobeAnalytics.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selector/src/shared/analytics/adobeAnalytics.d.ts b/selector/src/shared/analytics/adobeAnalytics.d.ts index 422fe0009ea..454addbab15 100644 --- a/selector/src/shared/analytics/adobeAnalytics.d.ts +++ b/selector/src/shared/analytics/adobeAnalytics.d.ts @@ -5,6 +5,8 @@ declare global { trackComponentClick?: (componentName: string, value: string) => void; }; }; + wapLocalCode?: string; + wapSection?: string; } } From e59deb9cea5c723dba83a38507d0fea3bac70623 Mon Sep 17 00:00:00 2001 From: yatarkan Date: Tue, 30 Jul 2024 17:43:31 +0400 Subject: [PATCH 2/7] Add filter and search events to analytics --- selector/src/shared/analytics/analytics.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/selector/src/shared/analytics/analytics.ts b/selector/src/shared/analytics/analytics.ts index f18b0c6606e..4f446fc54ac 100644 --- a/selector/src/shared/analytics/analytics.ts +++ b/selector/src/shared/analytics/analytics.ts @@ -26,16 +26,22 @@ export function addAnalyticsScript(): void { scriptElement.src = url; const headElement = document.getElementsByTagName('head')[0]; headElement.appendChild(scriptElement); + // Set analytics vars + window.wapLocalCode = 'us-en'; + window.wapSection = 'openvinotoolkit'; } enum COMPONENT { NAVIGATE = 'ov-notebooks:navigate', COPY_LINK = 'ov-notebooks:copy-link', + FILTER = 'ov-notebooks:filter', + SEARCH = 'ov-notebooks:search', } type AdobeTrackFn = (componentName: COMPONENT, label: string, detail?: string) => void; function getAdobeAnalyticsFunction(window: Window): AdobeTrackFn | null { + // TODO Fix Uncaught DOMException: Failed to read a named property 'wap_tms' from 'Window': Blocked a frame with origin "https://openvinotoolkit.github.io" from accessing a cross-origin frame. if (typeof window.wap_tms?.custom?.trackComponentClick !== 'function') { return null; } @@ -87,6 +93,14 @@ class Analytics { sendCopyLinkEvent(notebookPath: string): void { this._send(COMPONENT.COPY_LINK, notebookPath); } + + sendFilterEvent(filterOption: string) { + this._send(COMPONENT.FILTER, filterOption); + } + + sendSearchEvent(searchValue: string) { + this._send(COMPONENT.SEARCH, searchValue); + } } export const analytics = new Analytics(); From 19b80f15e21ee8dc49d4d033ec35abc641ae66bb Mon Sep 17 00:00:00 2001 From: yatarkan Date: Tue, 30 Jul 2024 17:45:23 +0400 Subject: [PATCH 3/7] Use filter and search events in components --- .../ContentSectionHeader/ContentSectionHeader.tsx | 14 +++++++++++++- .../src/components/FiltersPanel/FiltersPanel.tsx | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/selector/src/components/ContentSection/ContentSectionHeader/ContentSectionHeader.tsx b/selector/src/components/ContentSection/ContentSectionHeader/ContentSectionHeader.tsx index f0b25badf70..8157269a4bf 100644 --- a/selector/src/components/ContentSection/ContentSectionHeader/ContentSectionHeader.tsx +++ b/selector/src/components/ContentSection/ContentSectionHeader/ContentSectionHeader.tsx @@ -1,11 +1,12 @@ import './ContentSectionHeader.scss'; -import { useContext } from 'react'; +import { useContext, useEffect } from 'react'; import { openFiltersPanel } from '@/components/FiltersPanel/filters-panel-handlers'; import { Button } from '@/components/shared/Button/Button'; import { Dropdown } from '@/components/shared/Dropdown/Dropdown'; import { Search } from '@/components/shared/Search/Search'; +import { analytics } from '@/shared/analytics/analytics'; import { SORT_OPTIONS, SortValues } from '@/shared/notebooks.service'; import { NotebooksContext } from '@/shared/notebooks-context'; @@ -23,6 +24,17 @@ export const ContentSectionHeader = ({ totalCount, filteredCount }: ContentSecti const isFiltered = filteredCount !== totalCount; + // Send search event to analytics with debouncing + useEffect(() => { + const sendSearchEventTimeout = setTimeout(() => { + if (searchValue) { + analytics.sendSearchEvent(searchValue); + } + }, 2000); + + return () => clearTimeout(sendSearchEventTimeout); + }, [searchValue]); + return (
diff --git a/selector/src/components/FiltersPanel/FiltersPanel.tsx b/selector/src/components/FiltersPanel/FiltersPanel.tsx index 589dddae9cc..67804a8c88a 100644 --- a/selector/src/components/FiltersPanel/FiltersPanel.tsx +++ b/selector/src/components/FiltersPanel/FiltersPanel.tsx @@ -6,6 +6,7 @@ import CrossIcon from '@/assets/images/cross.svg?react'; import { FilterSection } from '@/components/shared/FilterSection/FilterSection'; import { Search } from '@/components/shared/Search/Search'; import { ITabItem, Tabs } from '@/components/shared/Tabs/Tabs'; +import { analytics } from '@/shared/analytics/analytics'; import { INotebookMetadata } from '@/shared/notebook-metadata'; import { CATEGORIES, LIBRARIES, LIBRARIES_VALUES, TASKS, TASKS_VALUES } from '@/shared/notebook-tags'; import { notebooksService } from '@/shared/notebooks.service'; @@ -101,6 +102,7 @@ export const FiltersPanel = (): JSX.Element => { ...selectedTags, [group]: [tag], }); + analytics.sendFilterEvent(tag); } }; From 108171497ca9daca9532a7739b8e0e283e2fae12 Mon Sep 17 00:00:00 2001 From: yatarkan Date: Tue, 30 Jul 2024 17:49:21 +0400 Subject: [PATCH 4/7] Fix getting window location for cross-origin frame --- .../ContentSection/NotebooksList/NotebookCard/NotebookCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selector/src/components/ContentSection/NotebooksList/NotebookCard/NotebookCard.tsx b/selector/src/components/ContentSection/NotebooksList/NotebookCard/NotebookCard.tsx index 16ae38753d7..a27477952ae 100644 --- a/selector/src/components/ContentSection/NotebooksList/NotebookCard/NotebookCard.tsx +++ b/selector/src/components/ContentSection/NotebooksList/NotebookCard/NotebookCard.tsx @@ -35,7 +35,7 @@ const openNotebookInDocs = ({ links, path }: INotebookMetadata) => { }; const copyNotebookShareUrl = ({ title }: INotebookMetadata): void => { - const shareUrl = new URL(window.parent.location.toString()); + const shareUrl = new URL(window.location.toString()); shareUrl.search = getUrlParamsWithSearch(title).toString(); void copyToClipboard(shareUrl.toString()); }; From a30493d0e79113fecfc279062b55f8da8fa81df3 Mon Sep 17 00:00:00 2001 From: yatarkan Date: Tue, 30 Jul 2024 19:41:37 +0400 Subject: [PATCH 5/7] Add optional detail argument to track component function --- selector/src/shared/analytics/adobeAnalytics.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selector/src/shared/analytics/adobeAnalytics.d.ts b/selector/src/shared/analytics/adobeAnalytics.d.ts index 454addbab15..c750a5929c5 100644 --- a/selector/src/shared/analytics/adobeAnalytics.d.ts +++ b/selector/src/shared/analytics/adobeAnalytics.d.ts @@ -2,7 +2,7 @@ declare global { interface Window { wap_tms?: { custom?: { - trackComponentClick?: (componentName: string, value: string) => void; + trackComponentClick?: (componentName: string, value: string, detail?: string) => void; }; }; wapLocalCode?: string; From cb86168fc143de8c180be8f67161233a5098f7f7 Mon Sep 17 00:00:00 2001 From: yatarkan Date: Tue, 30 Jul 2024 19:42:21 +0400 Subject: [PATCH 6/7] Propagate analytics events to host page for embedded mode --- selector/src/shared/analytics/analytics.ts | 10 ++++++++-- selector/src/shared/iframe-message-emitter.ts | 14 ++++++++++++++ selector/src/shared/iframe-message-handler.ts | 13 +++++++++++-- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/selector/src/shared/analytics/analytics.ts b/selector/src/shared/analytics/analytics.ts index 4f446fc54ac..cd0c3fc796a 100644 --- a/selector/src/shared/analytics/analytics.ts +++ b/selector/src/shared/analytics/analytics.ts @@ -1,3 +1,6 @@ +import { isEmbedded } from '../iframe-detector'; +import { sendAnalyticsMessage } from '../iframe-message-emitter'; + // eslint-disable-next-line @typescript-eslint/no-explicit-any const once = function any>(fn: T) { let result: ReturnType; @@ -38,10 +41,13 @@ enum COMPONENT { SEARCH = 'ov-notebooks:search', } -type AdobeTrackFn = (componentName: COMPONENT, label: string, detail?: string) => void; +export type AdobeTrackFn = (componentName: COMPONENT, label: string, detail?: string) => void; function getAdobeAnalyticsFunction(window: Window): AdobeTrackFn | null { - // TODO Fix Uncaught DOMException: Failed to read a named property 'wap_tms' from 'Window': Blocked a frame with origin "https://openvinotoolkit.github.io" from accessing a cross-origin frame. + if (isEmbedded) { + return sendAnalyticsMessage; + } + if (typeof window.wap_tms?.custom?.trackComponentClick !== 'function') { return null; } diff --git a/selector/src/shared/iframe-message-emitter.ts b/selector/src/shared/iframe-message-emitter.ts index 2424656e3e3..8e4a8ffbcb2 100644 --- a/selector/src/shared/iframe-message-emitter.ts +++ b/selector/src/shared/iframe-message-emitter.ts @@ -1,3 +1,4 @@ +import { type AdobeTrackFn } from './analytics/analytics'; import { isEmbedded } from './iframe-detector'; export interface IResizeMessage { @@ -9,6 +10,19 @@ export interface IScrollMessage { type: 'scroll'; } +export interface IAnalyticsMessage { + type: 'analytics'; + args: Parameters; +} + +export const sendAnalyticsMessage = (...args: IAnalyticsMessage['args']): void => { + const message: IAnalyticsMessage = { + type: 'analytics', + args, + }; + window.parent.postMessage(message, '*'); +}; + export const sendScrollMessage = (): void => { const message: IScrollMessage = { type: 'scroll', diff --git a/selector/src/shared/iframe-message-handler.ts b/selector/src/shared/iframe-message-handler.ts index a8496a27a13..bcaf26d9b75 100644 --- a/selector/src/shared/iframe-message-handler.ts +++ b/selector/src/shared/iframe-message-handler.ts @@ -1,4 +1,4 @@ -import type { IResizeMessage, IScrollMessage } from './iframe-message-emitter'; +import type { IAnalyticsMessage, IResizeMessage, IScrollMessage } from './iframe-message-emitter'; const notebooksSelectorElement = document.getElementById('notebooks-selector') as HTMLIFrameElement; @@ -17,7 +17,7 @@ function setInitialIframeHeight(iframeElement: HTMLIFrameElement): void { } } -window.onmessage = (message: MessageEvent) => { +window.onmessage = (message: MessageEvent) => { const { origin: allowedOrigin } = new URL( import.meta.env.PROD ? (import.meta.env.VITE_APP_LOCATION as string) : import.meta.url ); @@ -35,6 +35,15 @@ window.onmessage = (message: MessageEvent) => { notebooksSelectorElement.scrollIntoView({ behavior: 'smooth' }); return; } + + if (message.data.type === 'analytics') { + if (typeof window.wap_tms?.custom?.trackComponentClick === 'function') { + window.wap_tms.custom.trackComponentClick(...message.data.args); + } else { + console.log('Analytics is not found on the host page.'); + } + return; + } }; setInitialIframeHeight(notebooksSelectorElement); From aa4ee09c5eecad7928a33446bc22aef0fda61308 Mon Sep 17 00:00:00 2001 From: yatarkan Date: Tue, 30 Jul 2024 20:44:48 +0400 Subject: [PATCH 7/7] Allow clipboard write policy for iframe --- selector/embedded.html | 1 + 1 file changed, 1 insertion(+) diff --git a/selector/embedded.html b/selector/embedded.html index ffe49dfa0dc..1aa34c716da 100644 --- a/selector/embedded.html +++ b/selector/embedded.html @@ -5,4 +5,5 @@ src="%VITE_APP_LOCATION%" style="width: 100%; border: none" title="OpenVINO™ Notebooks - Jupyter notebook tutorials for OpenVINO™" + allow="clipboard-write" >