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" > 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/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()); }; 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); } }; diff --git a/selector/src/shared/analytics/adobeAnalytics.d.ts b/selector/src/shared/analytics/adobeAnalytics.d.ts index 422fe0009ea..c750a5929c5 100644 --- a/selector/src/shared/analytics/adobeAnalytics.d.ts +++ b/selector/src/shared/analytics/adobeAnalytics.d.ts @@ -2,9 +2,11 @@ declare global { interface Window { wap_tms?: { custom?: { - trackComponentClick?: (componentName: string, value: string) => void; + trackComponentClick?: (componentName: string, value: string, detail?: string) => void; }; }; + wapLocalCode?: string; + wapSection?: string; } } diff --git a/selector/src/shared/analytics/analytics.ts b/selector/src/shared/analytics/analytics.ts index f18b0c6606e..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; @@ -26,16 +29,25 @@ 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; +export type AdobeTrackFn = (componentName: COMPONENT, label: string, detail?: string) => void; function getAdobeAnalyticsFunction(window: Window): AdobeTrackFn | null { + if (isEmbedded) { + return sendAnalyticsMessage; + } + if (typeof window.wap_tms?.custom?.trackComponentClick !== 'function') { return null; } @@ -87,6 +99,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(); 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);