@redocly/theme 0.67.0-next.3 → 0.67.0-next.5
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/lib/components/Breadcrumbs/BreadcrumbDropdown.d.ts +2 -1
- package/lib/components/Breadcrumbs/BreadcrumbDropdown.js +9 -10
- package/lib/components/Breadcrumbs/Breadcrumbs.js +5 -22
- package/lib/components/Buttons/AIAssistantButton.js +2 -6
- package/lib/components/Buttons/EditPageButton.js +2 -1
- package/lib/components/Catalog/CatalogCardView/CatalogCard.js +1 -1
- package/lib/components/CatalogClassic/CatalogClassicActions.js +3 -1
- package/lib/components/CatalogClassic/CatalogClassicCard.js +1 -1
- package/lib/components/CatalogClassic/CatalogClassicInfoBlock.js +2 -1
- package/lib/components/Dropdown/Dropdown.d.ts +2 -0
- package/lib/components/Dropdown/Dropdown.js +8 -2
- package/lib/components/Feedback/ReportDialog.js +4 -1
- package/lib/components/Filter/FilterCheckboxes.js +3 -1
- package/lib/components/Footer/FooterItem.js +1 -1
- package/lib/components/LanguagePicker/LanguagePicker.js +3 -1
- package/lib/components/Logo/Logo.js +2 -1
- package/lib/components/Menu/MenuContainer.js +1 -0
- package/lib/components/Menu/MenuItem.js +21 -7
- package/lib/components/Navbar/Navbar.js +6 -2
- package/lib/components/Navbar/NavbarItem.js +3 -1
- package/lib/components/Search/SearchAiMessage.js +23 -5
- package/lib/components/Search/SearchDialog.js +5 -33
- package/lib/components/Search/SearchInput.js +4 -1
- package/lib/components/Search/SearchRecent.js +3 -1
- package/lib/components/SidebarActions/SidebarActions.js +10 -3
- package/lib/components/TableOfContent/TableOfContent.js +1 -1
- package/lib/components/UserMenu/LoginButton.js +2 -1
- package/lib/components/UserMenu/LogoutMenuItem.js +2 -1
- package/lib/core/hooks/search/use-search-dialog.js +7 -2
- package/lib/core/hooks/use-banner-telemetry.js +2 -11
- package/lib/core/hooks/use-color-switcher.js +2 -1
- package/lib/core/hooks/use-page-actions.js +12 -21
- package/lib/core/hooks/use-product-picker.js +14 -4
- package/lib/core/hooks/use-telemetry-fallback.d.ts +45 -37
- package/lib/core/hooks/use-telemetry-fallback.js +47 -39
- package/lib/core/types/search.d.ts +2 -1
- package/lib/core/types/search.js +1 -0
- package/lib/core/utils/index.d.ts +2 -0
- package/lib/core/utils/index.js +2 -0
- package/lib/core/utils/telemetry/generate-before-after-context.d.ts +37 -0
- package/lib/core/utils/telemetry/generate-before-after-context.js +32 -0
- package/lib/core/utils/telemetry/generate-resource-urn.d.ts +7 -0
- package/lib/core/utils/telemetry/generate-resource-urn.js +13 -0
- package/lib/core/utils/telemetry/get-base-data-attributes.d.ts +14 -0
- package/lib/core/utils/telemetry/get-base-data-attributes.js +17 -0
- package/package.json +3 -3
- package/src/components/Breadcrumbs/BreadcrumbDropdown.tsx +18 -4
- package/src/components/Breadcrumbs/Breadcrumbs.tsx +5 -6
- package/src/components/Buttons/AIAssistantButton.tsx +2 -3
- package/src/components/Buttons/EditPageButton.tsx +4 -1
- package/src/components/Catalog/CatalogCardView/CatalogCard.tsx +2 -2
- package/src/components/CatalogClassic/CatalogClassicActions.tsx +4 -2
- package/src/components/CatalogClassic/CatalogClassicCard.tsx +9 -2
- package/src/components/CatalogClassic/CatalogClassicInfoBlock.tsx +2 -1
- package/src/components/Dropdown/Dropdown.tsx +8 -1
- package/src/components/Feedback/ReportDialog.tsx +4 -1
- package/src/components/Filter/FilterCheckboxes.tsx +4 -2
- package/src/components/Footer/FooterItem.tsx +4 -2
- package/src/components/LanguagePicker/LanguagePicker.tsx +9 -2
- package/src/components/Logo/Logo.tsx +7 -1
- package/src/components/Menu/MenuContainer.tsx +1 -0
- package/src/components/Menu/MenuItem.tsx +35 -4
- package/src/components/Navbar/Navbar.tsx +7 -3
- package/src/components/Navbar/NavbarItem.tsx +7 -1
- package/src/components/Search/SearchAiMessage.tsx +25 -5
- package/src/components/Search/SearchDialog.tsx +6 -13
- package/src/components/Search/SearchInput.tsx +4 -1
- package/src/components/Search/SearchRecent.tsx +4 -2
- package/src/components/SidebarActions/SidebarActions.tsx +10 -3
- package/src/components/TableOfContent/TableOfContent.tsx +7 -2
- package/src/components/UserMenu/LoginButton.tsx +4 -1
- package/src/components/UserMenu/LogoutMenuItem.tsx +2 -1
- package/src/core/hooks/search/use-search-dialog.ts +13 -2
- package/src/core/hooks/use-banner-telemetry.ts +5 -4
- package/src/core/hooks/use-color-switcher.ts +7 -1
- package/src/core/hooks/use-page-actions.ts +17 -28
- package/src/core/hooks/use-product-picker.ts +19 -5
- package/src/core/hooks/use-telemetry-fallback.ts +47 -39
- package/src/core/types/search.ts +1 -0
- package/src/core/utils/index.ts +2 -0
- package/src/core/utils/telemetry/generate-before-after-context.ts +59 -0
- package/src/core/utils/telemetry/generate-resource-urn.ts +9 -0
- package/src/core/utils/telemetry/get-base-data-attributes.ts +27 -0
|
@@ -7,7 +7,7 @@ import type { SearchFacetCount, SearchItemData } from '@redocly/theme/core/types
|
|
|
7
7
|
import { SearchInput } from '@redocly/theme/components/Search/SearchInput';
|
|
8
8
|
import { SearchShortcut } from '@redocly/theme/components/Search/SearchShortcut';
|
|
9
9
|
import { Button } from '@redocly/theme/components/Button/Button';
|
|
10
|
-
import { breakpoints, concatClassNames } from '@redocly/theme/core/utils';
|
|
10
|
+
import { breakpoints, concatClassNames, getBaseDataAttributes } from '@redocly/theme/core/utils';
|
|
11
11
|
import { Portal } from '@redocly/theme/components/Portal/Portal';
|
|
12
12
|
import { SearchItem } from '@redocly/theme/components/Search/SearchItem';
|
|
13
13
|
import { SearchRecent } from '@redocly/theme/components/Search/SearchRecent';
|
|
@@ -172,9 +172,8 @@ export function SearchDialog({
|
|
|
172
172
|
addSearchHistoryItem(query);
|
|
173
173
|
telemetry.sendSearchResultClickedMessage([
|
|
174
174
|
{
|
|
175
|
-
|
|
175
|
+
...getBaseDataAttributes('searchResultItem', 'search', item.document.url),
|
|
176
176
|
query: query,
|
|
177
|
-
url: item.document.url,
|
|
178
177
|
totalResults: results.length.toString(),
|
|
179
178
|
index: index.toString(),
|
|
180
179
|
searchEngine: mode,
|
|
@@ -259,9 +258,7 @@ export function SearchDialog({
|
|
|
259
258
|
}
|
|
260
259
|
telemetry.sendSearchAiOpenedMessage([
|
|
261
260
|
{
|
|
262
|
-
|
|
263
|
-
object: 'search',
|
|
264
|
-
uri: 'urn:redocly:realm:ui:search:searchAiButton',
|
|
261
|
+
...getBaseDataAttributes('searchAiButton', 'search'),
|
|
265
262
|
method: 'ai_search_button',
|
|
266
263
|
},
|
|
267
264
|
]);
|
|
@@ -355,9 +352,7 @@ export function SearchDialog({
|
|
|
355
352
|
}
|
|
356
353
|
telemetry.sendSearchAiOpenedMessage([
|
|
357
354
|
{
|
|
358
|
-
|
|
359
|
-
object: 'search',
|
|
360
|
-
uri: 'urn:redocly:realm:ui:search:searchAiInput',
|
|
355
|
+
...getBaseDataAttributes('searchAiInput', 'search'),
|
|
361
356
|
method: 'ai_search_input',
|
|
362
357
|
},
|
|
363
358
|
]);
|
|
@@ -370,9 +365,7 @@ export function SearchDialog({
|
|
|
370
365
|
}
|
|
371
366
|
telemetry.sendSearchAiOpenedMessage([
|
|
372
367
|
{
|
|
373
|
-
|
|
374
|
-
object: 'search',
|
|
375
|
-
uri: 'urn:redocly:realm:ui:search:searchAiInput',
|
|
368
|
+
...getBaseDataAttributes('searchAiInput', 'search'),
|
|
376
369
|
method: 'ai_search_input',
|
|
377
370
|
},
|
|
378
371
|
]);
|
|
@@ -474,7 +467,7 @@ export function SearchDialog({
|
|
|
474
467
|
onSelect={(query, index) => {
|
|
475
468
|
telemetry.sendSearchRecentClickedMessage([
|
|
476
469
|
{
|
|
477
|
-
|
|
470
|
+
...getBaseDataAttributes('searchRecentItem', 'search'),
|
|
478
471
|
query,
|
|
479
472
|
index: index.toString(),
|
|
480
473
|
searchSessionId,
|
|
@@ -9,6 +9,7 @@ import { Button } from '@redocly/theme/components/Button/Button';
|
|
|
9
9
|
import { useInputKeyCommands, useRecentSearches, useThemeHooks } from '@redocly/theme/core/hooks';
|
|
10
10
|
import { CloseFilledIcon } from '@redocly/theme/icons/CloseFilledIcon/CloseFilledIcon';
|
|
11
11
|
import { ChevronLeftIcon } from '@redocly/theme/icons/ChevronLeftIcon/ChevronLeftIcon';
|
|
12
|
+
import { getBaseDataAttributes } from '@redocly/theme/core/utils';
|
|
12
13
|
|
|
13
14
|
export type SearchInputProps = {
|
|
14
15
|
placeholder?: string;
|
|
@@ -51,7 +52,9 @@ export function SearchInput({
|
|
|
51
52
|
const handleOnReset = () => {
|
|
52
53
|
onChange('');
|
|
53
54
|
addSearchHistoryItem(value);
|
|
54
|
-
telemetry.
|
|
55
|
+
telemetry.sendSearchInputResetClickedMessage([
|
|
56
|
+
getBaseDataAttributes('searchInputReset', 'search'),
|
|
57
|
+
]);
|
|
55
58
|
};
|
|
56
59
|
|
|
57
60
|
return (
|
|
@@ -3,7 +3,7 @@ import styled from 'styled-components';
|
|
|
3
3
|
|
|
4
4
|
import type { JSX } from 'react';
|
|
5
5
|
|
|
6
|
-
import { breakpoints } from '@redocly/theme/core/utils';
|
|
6
|
+
import { breakpoints, getBaseDataAttributes } from '@redocly/theme/core/utils';
|
|
7
7
|
import { useThemeHooks, useRecentSearches } from '@redocly/theme/core/hooks';
|
|
8
8
|
import { CloseIcon } from '@redocly/theme/icons/CloseIcon/CloseIcon';
|
|
9
9
|
import { RecentlyViewedIcon } from '@redocly/theme/icons/RecentlyViewedIcon/RecentlyViewedIcon';
|
|
@@ -25,7 +25,9 @@ export function SearchRecent({ onSelect, className }: SearchRecentProps): JSX.El
|
|
|
25
25
|
const handleOnRemove = (e: React.MouseEvent<SVGSVGElement, MouseEvent>, item: string) => {
|
|
26
26
|
e.stopPropagation();
|
|
27
27
|
removeSearchHistoryItem(item);
|
|
28
|
-
telemetry.
|
|
28
|
+
telemetry.sendSearchRecentRemoveClickedMessage([
|
|
29
|
+
getBaseDataAttributes('searchRecentRemove', 'search'),
|
|
30
|
+
]);
|
|
29
31
|
};
|
|
30
32
|
|
|
31
33
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>, item: string, index: number) => {
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
ControlsWrapChangeLayoutButtons,
|
|
12
12
|
} from '@redocly/theme/components/SidebarActions/styled';
|
|
13
13
|
import { Tooltip } from '@redocly/theme/components/Tooltip/Tooltip';
|
|
14
|
+
import { getBaseDataAttributes } from '@redocly/theme/core/utils';
|
|
14
15
|
|
|
15
16
|
export { LayoutVariant };
|
|
16
17
|
|
|
@@ -57,9 +58,13 @@ export const SidebarActions = ({
|
|
|
57
58
|
onClick={() => {
|
|
58
59
|
onChangeCollapseSidebarClick();
|
|
59
60
|
if (collapsedSidebar) {
|
|
60
|
-
telemetry.
|
|
61
|
+
telemetry.sendSidebarExpandedMessage([
|
|
62
|
+
getBaseDataAttributes('sidebarExpand', 'button'),
|
|
63
|
+
]);
|
|
61
64
|
} else {
|
|
62
|
-
telemetry.
|
|
65
|
+
telemetry.sendSidebarCollapsedMessage([
|
|
66
|
+
getBaseDataAttributes('sidebarCollapse', 'button'),
|
|
67
|
+
]);
|
|
63
68
|
}
|
|
64
69
|
}}
|
|
65
70
|
size="small"
|
|
@@ -76,7 +81,9 @@ export const SidebarActions = ({
|
|
|
76
81
|
layout={layout}
|
|
77
82
|
onClick={() => {
|
|
78
83
|
onChangeViewClick();
|
|
79
|
-
telemetry.
|
|
84
|
+
telemetry.sendChangeLayoutClickedMessage([
|
|
85
|
+
getBaseDataAttributes('changeLayout', 'button'),
|
|
86
|
+
]);
|
|
80
87
|
}}
|
|
81
88
|
/>
|
|
82
89
|
</ControlsWrapChangeLayoutButtons>
|
|
@@ -10,7 +10,12 @@ import {
|
|
|
10
10
|
useThemeConfig,
|
|
11
11
|
useFullHeight,
|
|
12
12
|
} from '@redocly/theme/core/hooks';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
breakpoints,
|
|
15
|
+
getDisplayedHeadings,
|
|
16
|
+
getLeastDepth,
|
|
17
|
+
getBaseDataAttributes,
|
|
18
|
+
} from '@redocly/theme/core/utils';
|
|
14
19
|
|
|
15
20
|
export type TableOfContentProps = {
|
|
16
21
|
headings?: Array<MdHeading | null> | null | undefined;
|
|
@@ -62,7 +67,7 @@ export function TableOfContent(props: TableOfContentProps): JSX.Element | null {
|
|
|
62
67
|
data-testid={`toc-${heading.value}`}
|
|
63
68
|
onClick={(e) => {
|
|
64
69
|
e.preventDefault();
|
|
65
|
-
telemetry.sendTocItemClickedMessage();
|
|
70
|
+
telemetry.sendTocItemClickedMessage([getBaseDataAttributes('tocItem', 'toc')]);
|
|
66
71
|
handleHeadingClick(heading.id);
|
|
67
72
|
}}
|
|
68
73
|
/>
|
|
@@ -4,6 +4,7 @@ import type { ButtonVariant, ButtonSize } from '@redocly/theme/components/Button
|
|
|
4
4
|
|
|
5
5
|
import { useThemeHooks } from '@redocly/theme/core/hooks';
|
|
6
6
|
import { Button } from '@redocly/theme/components/Button/Button';
|
|
7
|
+
import { getBaseDataAttributes } from '@redocly/theme/core/utils';
|
|
7
8
|
|
|
8
9
|
export type LoginButtonProps = {
|
|
9
10
|
href: string;
|
|
@@ -36,7 +37,9 @@ export function LoginButton({
|
|
|
36
37
|
data-translation-key={label ? undefined : labelTranslationKey}
|
|
37
38
|
to={href}
|
|
38
39
|
languageInsensitive
|
|
39
|
-
onClick={() =>
|
|
40
|
+
onClick={() =>
|
|
41
|
+
telemetry.sendLoginClickedMessage([getBaseDataAttributes('login', 'button')])
|
|
42
|
+
}
|
|
40
43
|
data-testid="login-btn"
|
|
41
44
|
extraClass={className}
|
|
42
45
|
variant={variant}
|
|
@@ -5,6 +5,7 @@ import type { JSX } from 'react';
|
|
|
5
5
|
import { useThemeHooks } from '@redocly/theme/core/hooks';
|
|
6
6
|
import { LogoutIcon } from '@redocly/theme/icons/LogoutIcon/LogoutIcon';
|
|
7
7
|
import { DropdownMenuItem } from '@redocly/theme/components/Dropdown/DropdownMenuItem';
|
|
8
|
+
import { getBaseDataAttributes } from '@redocly/theme/core/utils';
|
|
8
9
|
|
|
9
10
|
export type LogoutMenuItemProps = {
|
|
10
11
|
iconOnly?: boolean;
|
|
@@ -18,7 +19,7 @@ export function LogoutMenuItem({ iconOnly, className }: LogoutMenuItemProps): JS
|
|
|
18
19
|
const { translate } = useTranslate();
|
|
19
20
|
|
|
20
21
|
const handleClick = () => {
|
|
21
|
-
telemetry.
|
|
22
|
+
telemetry.sendLogoutClickedMessage([getBaseDataAttributes('logoutItem', 'userMenu')]);
|
|
22
23
|
handleLogout();
|
|
23
24
|
};
|
|
24
25
|
|
|
@@ -5,6 +5,7 @@ import hotkeys from 'hotkeys-js';
|
|
|
5
5
|
import { useThemeHooks } from '../use-theme-hooks';
|
|
6
6
|
import { useThemeConfig } from '../use-theme-config';
|
|
7
7
|
import { useSearchSession } from '../../contexts';
|
|
8
|
+
import { getBaseDataAttributes } from '../../utils';
|
|
8
9
|
|
|
9
10
|
export function useSearchDialog() {
|
|
10
11
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -20,7 +21,12 @@ export function useSearchDialog() {
|
|
|
20
21
|
if (hotKeys) {
|
|
21
22
|
hotkeys(hotKeys, (ev) => {
|
|
22
23
|
setIsOpen(true);
|
|
23
|
-
telemetry.sendSearchOpenedMessage([
|
|
24
|
+
telemetry.sendSearchOpenedMessage([
|
|
25
|
+
{
|
|
26
|
+
...getBaseDataAttributes('searchDialogId', 'search'),
|
|
27
|
+
method: 'shortcut',
|
|
28
|
+
},
|
|
29
|
+
]);
|
|
24
30
|
ev.preventDefault();
|
|
25
31
|
});
|
|
26
32
|
|
|
@@ -30,7 +36,12 @@ export function useSearchDialog() {
|
|
|
30
36
|
}, [hotKeys]);
|
|
31
37
|
|
|
32
38
|
const onOpen = useCallback(function () {
|
|
33
|
-
telemetry.sendSearchOpenedMessage([
|
|
39
|
+
telemetry.sendSearchOpenedMessage([
|
|
40
|
+
{
|
|
41
|
+
...getBaseDataAttributes('searchDialogId', 'search'),
|
|
42
|
+
method: 'click',
|
|
43
|
+
},
|
|
44
|
+
]);
|
|
34
45
|
setIsOpen(true);
|
|
35
46
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
36
47
|
}, []);
|
|
@@ -3,6 +3,7 @@ import { useMemo } from 'react';
|
|
|
3
3
|
import type { DisplayBanner } from '@redocly/theme/components/Banner/Banner';
|
|
4
4
|
|
|
5
5
|
import { useThemeHooks } from './use-theme-hooks';
|
|
6
|
+
import { getBaseDataAttributes } from '../utils/telemetry/get-base-data-attributes';
|
|
6
7
|
|
|
7
8
|
const noop = (): void => {};
|
|
8
9
|
const noopLink = (_href: string): void => {};
|
|
@@ -28,11 +29,11 @@ export function useBannerTelemetry(
|
|
|
28
29
|
};
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
const bannerUri = 'urn:redocly:realm:ui:banner:banner-id';
|
|
32
32
|
const payload = {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
...getBaseDataAttributes<'bannerId', 'banner', 'urn:redocly:realm:ui:banner:bannerId'>(
|
|
34
|
+
'bannerId',
|
|
35
|
+
'banner',
|
|
36
|
+
),
|
|
36
37
|
trackingId: displayBanner.trackingId,
|
|
37
38
|
hash: displayBanner.hash,
|
|
38
39
|
color: displayBanner.color,
|
|
@@ -6,6 +6,7 @@ import { useThemeConfig } from './use-theme-config';
|
|
|
6
6
|
import { useThemeHooks } from './use-theme-hooks';
|
|
7
7
|
import { createStore, useStore } from './use-store';
|
|
8
8
|
import { DEFAULT_COLOR_MODES } from '../constants';
|
|
9
|
+
import { generateBeforeAfterContext } from '../utils';
|
|
9
10
|
|
|
10
11
|
const COLOR_MODE_KEY = 'colorSchema';
|
|
11
12
|
const colorModeStore = createStore<string>({
|
|
@@ -51,7 +52,12 @@ export const useColorSwitcher = () => {
|
|
|
51
52
|
root.classList.remove('notransition');
|
|
52
53
|
});
|
|
53
54
|
telemetry.sendColorModeSwitchedMessage([
|
|
54
|
-
|
|
55
|
+
...generateBeforeAfterContext<'sendColorModeSwitchedMessage'>(
|
|
56
|
+
'colorMode',
|
|
57
|
+
'button',
|
|
58
|
+
{ mode: activeColorMode },
|
|
59
|
+
{ mode: newMode },
|
|
60
|
+
),
|
|
55
61
|
]);
|
|
56
62
|
setActiveColorMode(newMode);
|
|
57
63
|
};
|
|
@@ -22,14 +22,7 @@ import {
|
|
|
22
22
|
removeTrailingSlash,
|
|
23
23
|
withoutPathPrefix,
|
|
24
24
|
} from '../utils/urls';
|
|
25
|
-
|
|
26
|
-
function createPageActionResource(pageSlug: string, pageUrl: string) {
|
|
27
|
-
return {
|
|
28
|
-
id: pageSlug,
|
|
29
|
-
object: 'page' as const,
|
|
30
|
-
uri: pageUrl,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
25
|
+
import { getBaseDataAttributes } from '../utils/telemetry/get-base-data-attributes';
|
|
33
26
|
|
|
34
27
|
const DEFAULT_ENABLED_ACTIONS = [
|
|
35
28
|
'copy',
|
|
@@ -78,25 +71,22 @@ export function usePageActions(
|
|
|
78
71
|
|
|
79
72
|
const isDocsMcp = !requiresMcpUrl;
|
|
80
73
|
|
|
81
|
-
const origin = IS_BROWSER ? window.location.origin : '';
|
|
82
|
-
const pageUrl = `${origin}${pageSlug}`;
|
|
83
|
-
|
|
84
74
|
return createMCPAction({
|
|
85
75
|
clientType,
|
|
86
76
|
mcpConfig: config,
|
|
87
77
|
translate,
|
|
88
78
|
onClickCallback: isDocsMcp
|
|
89
79
|
? () =>
|
|
90
|
-
telemetry.
|
|
80
|
+
telemetry.sendPageActionsClickedMessage([
|
|
91
81
|
{
|
|
92
|
-
...
|
|
93
|
-
|
|
82
|
+
...getBaseDataAttributes('pageActions', 'button'),
|
|
83
|
+
actionType: `docs-mcp-${clientType}` as const,
|
|
94
84
|
},
|
|
95
85
|
])
|
|
96
86
|
: undefined,
|
|
97
87
|
});
|
|
98
88
|
},
|
|
99
|
-
[mcpUrl, mcpConfig, translate, telemetry
|
|
89
|
+
[mcpUrl, mcpConfig, translate, telemetry],
|
|
100
90
|
);
|
|
101
91
|
|
|
102
92
|
const result: PageAction[] = useMemo(() => {
|
|
@@ -113,7 +103,6 @@ export function usePageActions(
|
|
|
113
103
|
? window.location.origin
|
|
114
104
|
: ((globalThis as { SSR_HOSTNAME?: string })['SSR_HOSTNAME'] ?? '');
|
|
115
105
|
const pathname = addTrailingSlash(pageSlug);
|
|
116
|
-
const pageUrl = combineUrls(origin, pathname);
|
|
117
106
|
const isRoot = withoutPathPrefix(pathname) === '/';
|
|
118
107
|
const mdPageUrl = isRoot
|
|
119
108
|
? combineUrls(origin, pathname, 'index.html.md')
|
|
@@ -138,10 +127,10 @@ export function usePageActions(
|
|
|
138
127
|
}
|
|
139
128
|
const text = await result.text();
|
|
140
129
|
ClipboardService.copyCustom(text);
|
|
141
|
-
telemetry.
|
|
130
|
+
telemetry.sendPageActionsClickedMessage([
|
|
142
131
|
{
|
|
143
|
-
...
|
|
144
|
-
|
|
132
|
+
...getBaseDataAttributes('pageActions', 'button'),
|
|
133
|
+
actionType: 'copy',
|
|
145
134
|
},
|
|
146
135
|
]);
|
|
147
136
|
} catch (error) {
|
|
@@ -157,10 +146,10 @@ export function usePageActions(
|
|
|
157
146
|
iconComponent: MarkdownFullIcon,
|
|
158
147
|
link: mdPageUrl,
|
|
159
148
|
onClick: () => {
|
|
160
|
-
telemetry.
|
|
149
|
+
telemetry.sendPageActionsClickedMessage([
|
|
161
150
|
{
|
|
162
|
-
...
|
|
163
|
-
|
|
151
|
+
...getBaseDataAttributes('pageActions', 'button'),
|
|
152
|
+
actionType: 'view',
|
|
164
153
|
},
|
|
165
154
|
]);
|
|
166
155
|
},
|
|
@@ -175,10 +164,10 @@ export function usePageActions(
|
|
|
175
164
|
iconComponent: ChatGptIcon,
|
|
176
165
|
link,
|
|
177
166
|
onClick: () => {
|
|
178
|
-
telemetry.
|
|
167
|
+
telemetry.sendPageActionsClickedMessage([
|
|
179
168
|
{
|
|
180
|
-
...
|
|
181
|
-
|
|
169
|
+
...getBaseDataAttributes('pageActions', 'button'),
|
|
170
|
+
actionType: 'chatgpt',
|
|
182
171
|
},
|
|
183
172
|
]);
|
|
184
173
|
window.location.href = link;
|
|
@@ -195,10 +184,10 @@ export function usePageActions(
|
|
|
195
184
|
iconComponent: ClaudeIcon,
|
|
196
185
|
link,
|
|
197
186
|
onClick: () => {
|
|
198
|
-
telemetry.
|
|
187
|
+
telemetry.sendPageActionsClickedMessage([
|
|
199
188
|
{
|
|
200
|
-
...
|
|
201
|
-
|
|
189
|
+
...getBaseDataAttributes('pageActions', 'button'),
|
|
190
|
+
actionType: 'claude',
|
|
202
191
|
},
|
|
203
192
|
]);
|
|
204
193
|
window.location.href = link;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { useRef } from 'react';
|
|
1
2
|
import { useNavigate } from 'react-router-dom';
|
|
2
3
|
|
|
3
4
|
import { getProductClassName } from '@redocly/theme/components/Product/utils';
|
|
4
5
|
|
|
5
6
|
import { useThemeHooks } from './use-theme-hooks';
|
|
6
|
-
import { withPathPrefix } from '../utils';
|
|
7
|
+
import { withPathPrefix, generateBeforeAfterContext } from '../utils';
|
|
7
8
|
|
|
8
9
|
export function useProductPicker() {
|
|
9
10
|
const { useCurrentProduct, useProducts, useTelemetry, useLoadAndNavigate } = useThemeHooks();
|
|
@@ -12,20 +13,33 @@ export function useProductPicker() {
|
|
|
12
13
|
const telemetry = useTelemetry();
|
|
13
14
|
const navigate = useNavigate();
|
|
14
15
|
const loadAndNavigate = useLoadAndNavigate();
|
|
16
|
+
// An open dropdown can fire a menu item's onAction from an earlier render, so `product.link`
|
|
17
|
+
// may be stale (e.g. after a locale switch); resolve the live product via a ref.
|
|
18
|
+
const productsRef = useRef(products);
|
|
19
|
+
productsRef.current = products;
|
|
15
20
|
function setProduct(product: typeof currentProduct) {
|
|
16
21
|
if (!product) return;
|
|
17
|
-
|
|
22
|
+
// match on `name` (locale-stable; link/slug are re-localized)
|
|
23
|
+
const freshProduct = productsRef.current.find((p) => p.name === product.name) ?? product;
|
|
24
|
+
telemetry.sendProductPickerChangedMessage([
|
|
25
|
+
...generateBeforeAfterContext<'sendProductPickerChangedMessage'>(
|
|
26
|
+
'productPicker',
|
|
27
|
+
'dropdown',
|
|
28
|
+
{ product: currentProduct?.slug || '' },
|
|
29
|
+
{ product: freshProduct.slug },
|
|
30
|
+
),
|
|
31
|
+
]);
|
|
18
32
|
if (typeof document === 'undefined') return;
|
|
19
33
|
|
|
20
|
-
if (
|
|
34
|
+
if (freshProduct.name) {
|
|
21
35
|
const root = document.documentElement;
|
|
22
36
|
Array.from(root.classList)
|
|
23
37
|
.filter((c) => c.startsWith('product-'))
|
|
24
38
|
.forEach((c) => root.classList.remove(c));
|
|
25
|
-
root.classList.add(getProductClassName(
|
|
39
|
+
root.classList.add(getProductClassName(freshProduct.name));
|
|
26
40
|
}
|
|
27
41
|
|
|
28
|
-
loadAndNavigate({ navigate, to: withPathPrefix(
|
|
42
|
+
loadAndNavigate({ navigate, to: withPathPrefix(freshProduct.link) });
|
|
29
43
|
}
|
|
30
44
|
return {
|
|
31
45
|
currentProduct,
|
|
@@ -1,50 +1,61 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
// Fallback no-op telemetry used by `useThemeHooks` when no ThemeDataContext
|
|
2
|
+
// provider supplies real hooks.
|
|
3
3
|
export const useTelemetryFallback = () => ({
|
|
4
4
|
send: () => {},
|
|
5
|
-
sendPageViewedMessage: () => {},
|
|
6
|
-
sendPageActionsButtonClickedMessage: () => {},
|
|
7
5
|
sendErrorMessage: () => {},
|
|
8
6
|
sendClientErrorMessage: () => {},
|
|
7
|
+
sendCatalogEntitiesFilterCheckboxToggledMessage: () => {},
|
|
8
|
+
sendCatalogEntitiesCopyDataSchemaClickedMessage: () => {},
|
|
9
|
+
sendCatalogEntitiesViewModeChangedMessage: () => {},
|
|
10
|
+
sendCatalogEntitiesRelatedEntitiesListSearchQueryMessage: () => {},
|
|
11
|
+
sendCatalogEntitiesRelatedEntitiesListSearchResultClickMessage: () => {},
|
|
12
|
+
sendCatalogEntitiesListSearchQueryMessage: () => {},
|
|
13
|
+
sendCatalogEntitiesListSearchResultClickedMessage: () => {},
|
|
14
|
+
sendRedirectMessage: () => {},
|
|
15
|
+
sendCustomMessage: () => {},
|
|
16
|
+
sendSearchAIQueryMessage: () => {},
|
|
17
|
+
sendSearchAiOpenedMessage: () => {},
|
|
18
|
+
sendSearchAIFeedbackMessage: () => {},
|
|
19
|
+
sendLoginClickedMessage: () => {},
|
|
20
|
+
sendLoginProviderClickedMessage: () => {},
|
|
21
|
+
sendLogoutClickedMessage: () => {},
|
|
22
|
+
sendBannerViewedMessage: () => {},
|
|
23
|
+
sendBannerLinkClickedMessage: () => {},
|
|
24
|
+
sendBannerDismissedMessage: () => {},
|
|
9
25
|
sendBreadcrumbClickedMessage: () => {},
|
|
10
|
-
|
|
11
|
-
sendSidebarItemClickedMessage: () => {},
|
|
12
|
-
sendSidebarItemExpandedMessage: () => {},
|
|
13
|
-
sendSidebarItemCollapsedMessage: () => {},
|
|
14
|
-
sendChangeLayoutButtonClickedMessage: () => {},
|
|
15
|
-
sendEditPageLinkClickedMessage: () => {},
|
|
16
|
-
sendCodeSnippetReportedMessage: () => {},
|
|
17
|
-
sendNavbarMenuItemClickedMessage: () => {},
|
|
18
|
-
sendLoginButtonClickedMessage: () => {},
|
|
19
|
-
sendLoginProviderButtonClickedMessage: () => {},
|
|
20
|
-
sendLogoutMenuItemClickedMessage: () => {},
|
|
21
|
-
sendLogoClickedMessage: () => {},
|
|
22
|
-
sendTocItemClickedMessage: () => {},
|
|
26
|
+
sendCatalogActionsClickedMessage: () => {},
|
|
23
27
|
sendCatalogFilterChangedMessage: () => {},
|
|
24
28
|
sendCatalogItemClickedMessage: () => {},
|
|
25
29
|
sendScorecardLinkClickedMessage: () => {},
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
sendFilterCheckboxToggledMessage: () => {},
|
|
31
|
+
sendFeedbackMessage: () => {},
|
|
28
32
|
sendFooterItemClickedMessage: () => {},
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
sendNavbarMenuItemClickedMessage: () => {},
|
|
34
|
+
sendColorModeSwitchedMessage: () => {},
|
|
35
|
+
sendLanguagePickerChangedMessage: () => {},
|
|
36
|
+
sendProductPickerChangedMessage: () => {},
|
|
37
|
+
sendLogoClickedMessage: () => {},
|
|
38
|
+
sendCodeSnippetReportedMessage: () => {},
|
|
39
|
+
sendMobileMenuClosedMessage: () => {},
|
|
40
|
+
sendMobileMenuOpenedMessage: () => {},
|
|
41
|
+
sendPageViewedMessage: () => {},
|
|
42
|
+
sendPageTimeMessage: () => {},
|
|
43
|
+
sendPageActionsClickedMessage: () => {},
|
|
44
|
+
sendPageEditClickedMessage: () => {},
|
|
45
|
+
sendSidebarDrilldownBackClickedMessage: () => {},
|
|
46
|
+
sendSidebarItemClickedMessage: () => {},
|
|
47
|
+
sendSidebarExpandedMessage: () => {},
|
|
48
|
+
sendSidebarCollapsedMessage: () => {},
|
|
49
|
+
sendChangeLayoutClickedMessage: () => {},
|
|
50
|
+
sendRequestApiAccessClickedMessage: () => {},
|
|
51
|
+
sendVersionPickerChangedMessage: () => {},
|
|
52
|
+
sendSearchInputResetClickedMessage: () => {},
|
|
53
|
+
sendSearchResultClickedMessage: () => {},
|
|
54
|
+
sendSearchRecentRemoveClickedMessage: () => {},
|
|
34
55
|
sendSearchRecentClickedMessage: () => {},
|
|
35
|
-
sendRequestApiAccessButtonClickedMessage: () => {},
|
|
36
|
-
sendVersionPickerSelectionChangeMessage: () => {},
|
|
37
|
-
sendProductPickedMessage: () => {},
|
|
38
|
-
sendFilterCheckboxToggledMessage: () => {},
|
|
39
|
-
sendLanguagePickerLocaleChangedMessage: () => {},
|
|
40
56
|
sendSearchOpenedMessage: () => {},
|
|
41
57
|
sendSearchQueryMessage: () => {},
|
|
42
|
-
|
|
43
|
-
sendSearchAIQueryMessage: () => {},
|
|
44
|
-
sendSearchAIFeedbackMessage: () => {},
|
|
45
|
-
sendFeedbackMessage: () => {},
|
|
46
|
-
sendSearchResultClickedMessage: () => {},
|
|
47
|
-
sendRedirectMessage: () => {},
|
|
58
|
+
sendTocItemClickedMessage: () => {},
|
|
48
59
|
sendOpenapiDocsMessage: () => {},
|
|
49
60
|
sendCopyCodeSnippetClickedMessage: () => {},
|
|
50
61
|
sendViewedMessage: () => {},
|
|
@@ -67,10 +78,7 @@ export const useTelemetryFallback = () => ({
|
|
|
67
78
|
sendAsyncapiDocsReferencedInClickedMessage: () => {},
|
|
68
79
|
sendGraphqlDocsViewedMessage: () => {},
|
|
69
80
|
sendGraphqlDocsPerformanceMetricsMessage: () => {},
|
|
81
|
+
sendGraphqlDocsDownloadDefinitionClickedMessage: () => {},
|
|
70
82
|
sendGraphqlDocsReferencedInLinkClickedMessage: () => {},
|
|
71
83
|
sendGraphqlDocsRequiredScopesModalOpenedMessage: () => {},
|
|
72
|
-
sendGraphqlDocsDownloadDefinitionClickedMessage: () => {},
|
|
73
|
-
sendBannerViewedMessage: () => {},
|
|
74
|
-
sendBannerLinkClickedMessage: () => {},
|
|
75
|
-
sendBannerDismissedMessage: () => {},
|
|
76
84
|
});
|
package/src/core/types/search.ts
CHANGED
package/src/core/utils/index.ts
CHANGED
|
@@ -46,3 +46,5 @@ export * from './content-segments';
|
|
|
46
46
|
export * from './custom-catalog-options-casing';
|
|
47
47
|
export * from './get-auto-dismiss-duration';
|
|
48
48
|
export * from './tooltip-placement';
|
|
49
|
+
export * from './telemetry/get-base-data-attributes';
|
|
50
|
+
export * from './telemetry/generate-before-after-context';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { AsyncApiRealmUI } from '@redocly/realm-asyncapi-sdk';
|
|
2
|
+
|
|
3
|
+
import { getBaseDataAttributes } from './get-base-data-attributes';
|
|
4
|
+
|
|
5
|
+
type UITelemetryClient = AsyncApiRealmUI.Telemetry;
|
|
6
|
+
|
|
7
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
8
|
+
type UITelemetryClientMethods = {
|
|
9
|
+
[K in keyof UITelemetryClient]: UITelemetryClient[K] extends (...args: any) => any
|
|
10
|
+
? UITelemetryClient[K]
|
|
11
|
+
: never;
|
|
12
|
+
};
|
|
13
|
+
type UITelemetryClientMethodKeys = {
|
|
14
|
+
[K in keyof UITelemetryClientMethods]: Parameters<
|
|
15
|
+
UITelemetryClientMethods[K]
|
|
16
|
+
>[0] extends readonly [any, any, ...any[]]
|
|
17
|
+
? K
|
|
18
|
+
: never;
|
|
19
|
+
}[keyof UITelemetryClientMethods];
|
|
20
|
+
|
|
21
|
+
type EventSchemaProps<T extends UITelemetryClientMethodKeys> = Parameters<
|
|
22
|
+
UITelemetryClientMethods[T]
|
|
23
|
+
>[0];
|
|
24
|
+
|
|
25
|
+
type EventAfterContext<T extends UITelemetryClientMethodKeys> = EventSchemaProps<T>[0];
|
|
26
|
+
type EventBeforeContext<T extends UITelemetryClientMethodKeys> = EventSchemaProps<T>[1];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Builds a typed `[after, before]` tuple for Realm UI telemetry events whose AsyncAPI
|
|
30
|
+
* contract lists the updated state first, then the prior state (per CloudEvents styleguide).
|
|
31
|
+
*
|
|
32
|
+
* Each slice merges {@link getBaseDataAttributes} (`id`, `object`, `uri`) with the
|
|
33
|
+
* corresponding partial payload and sets `context` to `'after'` or `'before'`.
|
|
34
|
+
*
|
|
35
|
+
* @template T - Telemetry client method key whose `data` tuple is
|
|
36
|
+
* `[after, before, ...]` (for example `'sendLanguagePickerChangedMessage'`).
|
|
37
|
+
* @param id - Stable resource identifier passed to {@link getBaseDataAttributes}
|
|
38
|
+
* and included on both payloads.
|
|
39
|
+
* @param object - Resource kind passed to {@link getBaseDataAttributes}
|
|
40
|
+
* and included on both payloads (for example `'dropdown'` or `'button'`).
|
|
41
|
+
* @param before - Event-specific fields for the prior state, excluding `id`, `object`,
|
|
42
|
+
* `uri`, and `context` (filled by this helper).
|
|
43
|
+
* @param after - Event-specific fields for the updated state, excluding `id`, `object`,
|
|
44
|
+
* `uri`, and `context` (filled by this helper).
|
|
45
|
+
* @returns A two-tuple in send order: the fully merged after payload, then the fully
|
|
46
|
+
* merged before payload. Spread into the matching `send*Message` call.
|
|
47
|
+
*/
|
|
48
|
+
export function generateBeforeAfterContext<T extends UITelemetryClientMethodKeys>(
|
|
49
|
+
id: EventAfterContext<T>['id'],
|
|
50
|
+
object: EventAfterContext<T>['object'],
|
|
51
|
+
before: Omit<EventBeforeContext<T>, 'id' | 'object' | 'uri' | 'context'>,
|
|
52
|
+
after: Omit<EventAfterContext<T>, 'id' | 'object' | 'uri' | 'context'>,
|
|
53
|
+
): [EventAfterContext<T> & { context: 'after' }, EventBeforeContext<T> & { context: 'before' }] {
|
|
54
|
+
const baseAttributes = getBaseDataAttributes(id, object);
|
|
55
|
+
return [
|
|
56
|
+
{ ...baseAttributes, context: 'after' as const, ...after },
|
|
57
|
+
{ ...baseAttributes, context: 'before' as const, ...before },
|
|
58
|
+
];
|
|
59
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a resource URN for Realm UI telemetry.
|
|
3
|
+
* @param object - The object type.
|
|
4
|
+
* @param id - The resource ID.
|
|
5
|
+
* @returns The resource URN.
|
|
6
|
+
*/
|
|
7
|
+
export function generateResourceUrn<K, T, U>(object: K, id: T): U {
|
|
8
|
+
return `urn:redocly:realm:ui:${object}:${id}` as U;
|
|
9
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { generateResourceUrn } from './generate-resource-urn';
|
|
2
|
+
|
|
3
|
+
export type BaseDataAttributes<T, K, U> = {
|
|
4
|
+
/** Resource identifier (e.g. UI key, or page URL when tracking navigation). */
|
|
5
|
+
id: T;
|
|
6
|
+
/** Object kind used for telemetry categorization (e.g. `'button'`, `'page'`). */
|
|
7
|
+
object: K;
|
|
8
|
+
/** Resolved URI: the optional `url` when provided, otherwise a Realm UI resource URN. */
|
|
9
|
+
uri: U;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Builds the base fields for Realm UI telemetry payloads: `id`, `object`, and `uri`.
|
|
14
|
+
* When `url` is omitted, {@link generateResourceUrn} derives `uri` as
|
|
15
|
+
* `urn:redocly:realm:ui:{object}:{id}`.
|
|
16
|
+
*/
|
|
17
|
+
export function getBaseDataAttributes<T, K, U>(
|
|
18
|
+
id: T,
|
|
19
|
+
object: K,
|
|
20
|
+
url?: U,
|
|
21
|
+
): BaseDataAttributes<T, K, U> {
|
|
22
|
+
return {
|
|
23
|
+
id,
|
|
24
|
+
object,
|
|
25
|
+
uri: url ?? generateResourceUrn(object, id),
|
|
26
|
+
};
|
|
27
|
+
}
|