@redocly/theme 0.18.2 → 0.18.3

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.
Files changed (45) hide show
  1. package/lib/components/Breadcrumbs/Breadcrumb.d.ts +1 -0
  2. package/lib/components/Breadcrumbs/Breadcrumb.js +2 -2
  3. package/lib/components/Breadcrumbs/Breadcrumbs.js +8 -1
  4. package/lib/components/Catalog/CatalogCard.js +2 -1
  5. package/lib/components/Catalog/useCatalog.js +12 -1
  6. package/lib/components/CodeBlock/CodeBlockControls.js +6 -2
  7. package/lib/components/ColorModeSwitcher/ColorModeSwitcher.js +2 -0
  8. package/lib/components/EditPageButton/EditPageButton.js +2 -1
  9. package/lib/components/Feedback/Feedback.js +13 -3
  10. package/lib/components/Feedback/Sentiment.js +6 -2
  11. package/lib/components/Footer/FooterColumn.js +2 -1
  12. package/lib/components/Menu/MenuLinkItem.js +2 -1
  13. package/lib/components/Navbar/MobileUserProfile.js +6 -2
  14. package/lib/components/Navbar/NavbarItem.js +3 -2
  15. package/lib/components/NavbarLogo/NavbarLogo.js +2 -1
  16. package/lib/components/Profile/LoginLink.js +4 -1
  17. package/lib/components/Profile/UserProfile.js +5 -1
  18. package/lib/components/Search/SearchItem.js +10 -13
  19. package/lib/components/Sidebar/SidebarLayout.js +2 -1
  20. package/lib/components/SidebarActions/SidebarActions.js +15 -3
  21. package/lib/components/TableOfContent/TableOfContent.js +2 -1
  22. package/lib/config.d.ts +2 -2
  23. package/lib/config.js +1 -1
  24. package/package.json +2 -2
  25. package/src/components/Breadcrumbs/Breadcrumb.tsx +8 -2
  26. package/src/components/Breadcrumbs/Breadcrumbs.tsx +13 -1
  27. package/src/components/Catalog/CatalogCard.tsx +2 -1
  28. package/src/components/Catalog/useCatalog.ts +13 -1
  29. package/src/components/CodeBlock/CodeBlockControls.tsx +6 -1
  30. package/src/components/ColorModeSwitcher/ColorModeSwitcher.tsx +3 -0
  31. package/src/components/EditPageButton/EditPageButton.tsx +2 -1
  32. package/src/components/Feedback/Feedback.tsx +13 -3
  33. package/src/components/Feedback/Sentiment.tsx +10 -2
  34. package/src/components/Footer/FooterColumn.tsx +2 -0
  35. package/src/components/Menu/MenuLinkItem.tsx +6 -1
  36. package/src/components/Navbar/MobileUserProfile.tsx +12 -2
  37. package/src/components/Navbar/NavbarItem.tsx +3 -0
  38. package/src/components/NavbarLogo/NavbarLogo.tsx +2 -0
  39. package/src/components/Profile/LoginLink.tsx +8 -1
  40. package/src/components/Profile/UserProfile.tsx +5 -1
  41. package/src/components/Search/SearchItem.tsx +22 -27
  42. package/src/components/Sidebar/SidebarLayout.tsx +6 -1
  43. package/src/components/SidebarActions/SidebarActions.tsx +18 -3
  44. package/src/components/TableOfContent/TableOfContent.tsx +2 -0
  45. package/src/config.ts +1 -1
@@ -3,4 +3,5 @@ export declare const Breadcrumb: (props: {
3
3
  label: string;
4
4
  link?: string;
5
5
  isActive: boolean;
6
+ onClick?: () => void;
6
7
  }) => JSX.Element;
@@ -8,8 +8,8 @@ const react_1 = __importDefault(require("react"));
8
8
  const styled_components_1 = __importDefault(require("styled-components"));
9
9
  const Link_1 = require("../../mocks/Link");
10
10
  const Breadcrumb = (props) => {
11
- const { label, link, isActive } = props;
12
- return (react_1.default.createElement(BreadcrumbWrapper, { "data-component-name": "Breadcrumbs/Breadcrumb", isLink: link != null, isActive: isActive }, link ? (react_1.default.createElement(BreadcrumbLink, { to: link }, label)) : (react_1.default.createElement(BreadcrumbText, null, label))));
11
+ const { label, link, isActive, onClick } = props;
12
+ return (react_1.default.createElement(BreadcrumbWrapper, { "data-component-name": "Breadcrumbs/Breadcrumb", isLink: link != null, isActive: isActive, onClick: onClick }, link ? (react_1.default.createElement(BreadcrumbLink, { to: link }, label)) : (react_1.default.createElement(BreadcrumbText, null, label))));
13
13
  };
14
14
  exports.Breadcrumb = Breadcrumb;
15
15
  const BreadcrumbText = styled_components_1.default.div `
@@ -7,6 +7,7 @@ exports.Breadcrumbs = void 0;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const styled_components_1 = __importDefault(require("styled-components"));
9
9
  const useBreadcrumbs_1 = require("../../mocks/Sidebar/useBreadcrumbs");
10
+ const telemetry_1 = require("../../mocks/telemetry");
10
11
  const Breadcrumb_1 = require("./Breadcrumb");
11
12
  const Breadcrumbs = (props) => {
12
13
  const breadcrumbs = (0, useBreadcrumbs_1.useBreadcrumbs)();
@@ -16,7 +17,13 @@ const Breadcrumbs = (props) => {
16
17
  return (react_1.default.createElement(Container, { "data-component-name": "Breadcrumbs/Breadcrumbs", className: props.className }, breadcrumbs.map((breadcrumb, idx) => {
17
18
  const isLast = idx === breadcrumbs.length - 1;
18
19
  return (react_1.default.createElement(react_1.default.Fragment, { key: idx },
19
- react_1.default.createElement(Breadcrumb_1.Breadcrumb, { link: breadcrumb.link, label: breadcrumb.label, isActive: isLast }),
20
+ react_1.default.createElement(Breadcrumb_1.Breadcrumb, { link: breadcrumb.link, label: breadcrumb.label, isActive: isLast, onClick: () => {
21
+ telemetry_1.telemetry.send('breadcrumb_clicked', {
22
+ link: breadcrumb.link,
23
+ position: idx + 1,
24
+ total_breadcrumbs: breadcrumbs.length,
25
+ });
26
+ } }),
20
27
  isLast ? null : react_1.default.createElement("div", null, "/")));
21
28
  })));
22
29
  };
@@ -33,6 +33,7 @@ const Link_1 = require("../../mocks/Link");
33
33
  const Highlight_1 = require("../../ui/Highlight");
34
34
  const Tags_1 = require("../../components/Tags");
35
35
  const hooks_1 = require("../../mocks/hooks");
36
+ const telemetry_1 = require("../../mocks/telemetry");
36
37
  function CatalogCard({ item }) {
37
38
  var _a;
38
39
  const { translate } = (0, hooks_1.useTranslate)();
@@ -40,7 +41,7 @@ function CatalogCard({ item }) {
40
41
  footer: 'theme.catalog.card.footer',
41
42
  };
42
43
  return (React.createElement(Link_1.Link, { key: item.docsLink || item.link, to: item.docsLink || item.link },
43
- React.createElement(StyledCard, null,
44
+ React.createElement(StyledCard, { onClick: () => telemetry_1.telemetry.send('catalog_item_clicked', {}) },
44
45
  React.createElement(CardTitle, null,
45
46
  React.createElement(Highlight_1.Highlight, null, item.title)),
46
47
  React.createElement(CardDescription, null,
@@ -26,6 +26,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.useCatalog = void 0;
27
27
  const React = __importStar(require("react"));
28
28
  const react_router_dom_1 = require("react-router-dom");
29
+ const telemetry_1 = require("../../mocks/telemetry");
29
30
  const utils_1 = require("../../utils");
30
31
  function useCatalog(items, config) {
31
32
  const location = (0, react_router_dom_1.useLocation)();
@@ -60,6 +61,7 @@ function useCatalog(items, config) {
60
61
  }
61
62
  return [...prev.slice(0, filterIdx), newFilterOptions, ...prev.slice(filterIdx + 1)];
62
63
  });
64
+ telemetry_1.telemetry.send('catalog_filter_changed', { type: 'toggle' });
63
65
  window.scrollTo(0, 0);
64
66
  }, []);
65
67
  const selectOption = React.useCallback((filterIdx, option) => {
@@ -77,6 +79,7 @@ function useCatalog(items, config) {
77
79
  ? new Set()
78
80
  : f);
79
81
  });
82
+ telemetry_1.telemetry.send('catalog_filter_changed', { type: 'select' });
80
83
  window.scrollTo(0, 0);
81
84
  }, [filtersWithOptions]);
82
85
  React.useEffect(() => {
@@ -136,7 +139,15 @@ function useCatalog(items, config) {
136
139
  const groups = config.groupByFirstFilter && ((_a = filters[0].selectedOptions) === null || _a === void 0 ? void 0 : _a.size) > 0
137
140
  ? groupByFirstFilter(resolvedFilters, filteredItems)
138
141
  : [{ title: 'APIs', items: filteredItems }];
139
- return { groups, filters: resolvedFilters, setFilterTerm, filterTerm };
142
+ return {
143
+ groups,
144
+ filters: resolvedFilters,
145
+ setFilterTerm: (newTerm) => {
146
+ setFilterTerm(newTerm);
147
+ telemetry_1.telemetry.send('catalog_filter_changed', { type: 'term' });
148
+ },
149
+ filterTerm,
150
+ };
140
151
  }, [
141
152
  filtersWithOptions,
142
153
  normalizedItems,
@@ -9,6 +9,7 @@ const styled_components_1 = __importDefault(require("styled-components"));
9
9
  const CodeBlock_1 = require("../../components/CodeBlock");
10
10
  const icons_1 = require("../../icons");
11
11
  const hooks_1 = require("../../hooks");
12
+ const telemetry_1 = require("../../mocks/telemetry");
12
13
  const CopyButton_1 = require("../CopyButton");
13
14
  function CodeBlockControls({ children, className, title, controls, }) {
14
15
  var _a, _b, _c, _d, _e;
@@ -20,12 +21,15 @@ function CodeBlockControls({ children, className, title, controls, }) {
20
21
  const defaultControls = controls ? (react_1.default.createElement(react_1.default.Fragment, null,
21
22
  react_1.default.createElement(Title, null, title),
22
23
  react_1.default.createElement(ControlsWrapper, null,
23
- copy && !((_a = codeSnippet === null || codeSnippet === void 0 ? void 0 : codeSnippet.copy) === null || _a === void 0 ? void 0 : _a.hide) ? (react_1.default.createElement(CopyButton_1.CopyButton, { data: copy.data, "data-source": copy.dataSource, "data-hash": copy.dataHash, type: controlsType, toasterPlacement: copy.toasterPlacement, toasterDuration: copy.toasterDuration, buttonText: copy.label, tooltipText: copy.tooltipText, onCopyClick: copy === null || copy === void 0 ? void 0 : copy.onClick })) : null,
24
+ copy && !((_a = codeSnippet === null || codeSnippet === void 0 ? void 0 : codeSnippet.copy) === null || _a === void 0 ? void 0 : _a.hide) ? (react_1.default.createElement(CopyButton_1.CopyButton, { data: copy.data, "data-source": copy.dataSource, "data-hash": copy.dataHash, type: controlsType, toasterPlacement: copy.toasterPlacement, toasterDuration: copy.toasterDuration, buttonText: copy.label, tooltipText: copy.tooltipText, onCopyClick: () => {
25
+ copy === null || copy === void 0 ? void 0 : copy.onClick;
26
+ telemetry_1.telemetry.send('code_snippet_copied', {});
27
+ } })) : null,
24
28
  expand && !((_b = codeSnippet === null || codeSnippet === void 0 ? void 0 : codeSnippet.expand) === null || _b === void 0 ? void 0 : _b.hide) ? (react_1.default.createElement(CodeBlock_1.CodeBlockControlButton, { "data-cy": "expand-all", "data-testid": "expand-all", asIcon: controlsType === 'icon', onClick: expand === null || expand === void 0 ? void 0 : expand.onClick, title: (expand === null || expand === void 0 ? void 0 : expand.tooltipText) || 'Expand all' }, controlsType === 'icon' ? react_1.default.createElement(icons_1.ExpandIcon, null) : (expand === null || expand === void 0 ? void 0 : expand.label) ? expand.label : 'Expand all')) : null,
25
29
  collapse && !((_c = codeSnippet === null || codeSnippet === void 0 ? void 0 : codeSnippet.collapse) === null || _c === void 0 ? void 0 : _c.hide) ? (react_1.default.createElement(CodeBlock_1.CodeBlockControlButton, { "data-cy": "collapse-all", "data-testid": "collapse-all", asIcon: controlsType === 'icon', onClick: collapse === null || collapse === void 0 ? void 0 : collapse.onClick, title: (collapse === null || collapse === void 0 ? void 0 : collapse.tooltipText) || 'Collapse all' }, controlsType === 'icon' ? (react_1.default.createElement(icons_1.CollapseIcon, null)) : (collapse === null || collapse === void 0 ? void 0 : collapse.label) ? (collapse.label) : ('Collapse all'))) : null,
26
30
  select ? (react_1.default.createElement(CodeBlock_1.CodeBlockControlButton, { "data-cy": "select-all", "data-testid": "select-all", asIcon: controlsType === 'icon', onClick: select === null || select === void 0 ? void 0 : select.onClick, title: select === null || select === void 0 ? void 0 : select.tooltipText }, controlsType === 'icon' ? react_1.default.createElement(icons_1.SelectIcon, null) : (select === null || select === void 0 ? void 0 : select.label) ? select.label : 'Select all')) : null,
27
31
  deselect ? (react_1.default.createElement(CodeBlock_1.CodeBlockControlButton, { "data-cy": "clear-all", "data-testid": "clear-all", asIcon: controlsType === 'icon', onClick: deselect === null || deselect === void 0 ? void 0 : deselect.onClick, title: deselect === null || deselect === void 0 ? void 0 : deselect.tooltipText }, controlsType === 'icon' ? (react_1.default.createElement(icons_1.DeselectIcon, null)) : (deselect === null || deselect === void 0 ? void 0 : deselect.label) ? (deselect.label) : ('Clear all'))) : null,
28
- report && ((_d = report === null || report === void 0 ? void 0 : report.props) === null || _d === void 0 ? void 0 : _d.visible) ? (react_1.default.createElement(CodeBlock_1.CodeBlockControlButton, Object.assign({ "data-cy": "report-button", "data-testid": "report-button", asIcon: controlsType === 'icon', title: report.tooltipText }, report.props), controlsType === 'icon' ? react_1.default.createElement(icons_1.ReportIcon, null) : ((_e = report.props) === null || _e === void 0 ? void 0 : _e.buttonText) || 'Report')) : null))) : null;
32
+ report && ((_d = report === null || report === void 0 ? void 0 : report.props) === null || _d === void 0 ? void 0 : _d.visible) ? (react_1.default.createElement(CodeBlock_1.CodeBlockControlButton, Object.assign({ "data-cy": "report-button", "data-testid": "report-button", asIcon: controlsType === 'icon', title: report.tooltipText }, report.props, { onClick: () => telemetry_1.telemetry.send('code_snippet_reported', {}) }), controlsType === 'icon' ? react_1.default.createElement(icons_1.ReportIcon, null) : ((_e = report.props) === null || _e === void 0 ? void 0 : _e.buttonText) || 'Report')) : null))) : null;
29
33
  return children || controls ? (react_1.default.createElement(ContainerWraper, { "data-component-name": "CodeBlock/CodeBlockControls", className: className }, children ? children : defaultControls)) : null;
30
34
  }
31
35
  exports.CodeBlockControls = CodeBlockControls;
@@ -31,6 +31,7 @@ const react_1 = __importStar(require("react"));
31
31
  const styled_components_1 = __importDefault(require("styled-components"));
32
32
  const ColorModeIcon_1 = require("../../icons/ColorModeIcon");
33
33
  const hooks_1 = require("../../hooks");
34
+ const telemetry_1 = require("../../mocks/telemetry");
34
35
  function ColorModeSwitcher(props) {
35
36
  const { className } = props;
36
37
  const themeSettings = (0, hooks_1.useThemeConfig)();
@@ -53,6 +54,7 @@ function ColorModeSwitcher(props) {
53
54
  window.requestAnimationFrame(() => {
54
55
  document.documentElement.classList.remove('notransition');
55
56
  });
57
+ telemetry_1.telemetry.send('color_mode_switched', { from: activeColorMode, to: mode });
56
58
  };
57
59
  return (react_1.default.createElement(Wrapper, { "data-component-name": "ColorModeSwitcher/ColorModeSwitcher", onClick: handelChangeColorMode, modes: modes, role: "link", className: className }, modes.map((mode) => (react_1.default.createElement(ColorModeIcon_1.ColorModeIcon, { mode: mode, key: mode })))));
58
60
  }
@@ -7,8 +7,9 @@ exports.EditPageButton = void 0;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const styled_components_1 = __importDefault(require("styled-components"));
9
9
  const Link_1 = require("../../mocks/Link");
10
+ const telemetry_1 = require("../../mocks/telemetry");
10
11
  const EditPageButton = ({ text, to, icon }) => {
11
- return (react_1.default.createElement(EditButton, { to: to },
12
+ return (react_1.default.createElement(EditButton, { to: to, onClick: () => telemetry_1.telemetry.send('edit_page_link_clicked', {}) },
12
13
  icon ? react_1.default.createElement(ButtonIcon, { src: icon }) : null,
13
14
  react_1.default.createElement(ButtonText, null, text)));
14
15
  };
@@ -33,6 +33,7 @@ const styled_components_1 = __importDefault(require("styled-components"));
33
33
  const Feedback_1 = require("../../components/Feedback");
34
34
  const hooks_1 = require("../../hooks");
35
35
  const useSubmitFeedback_1 = require("../../mocks/Feedback/useSubmitFeedback");
36
+ const telemetry_1 = require("../../mocks/telemetry");
36
37
  const Feedback = (props) => {
37
38
  const { submitFeedback } = (0, useSubmitFeedback_1.useSubmitFeedback)();
38
39
  const { pathname } = (0, react_router_dom_1.useLocation)();
@@ -42,13 +43,22 @@ const Feedback = (props) => {
42
43
  switch (type) {
43
44
  case 'rating':
44
45
  return (React.createElement(Wrapper, null,
45
- React.createElement(Feedback_1.Rating, { settings: settings, onSubmit: (values) => submitFeedback({ type: 'rating', values, path }) })));
46
+ React.createElement(Feedback_1.Rating, { settings: settings, onSubmit: (values) => {
47
+ submitFeedback({ type: 'rating', values, path });
48
+ telemetry_1.telemetry.send('feedback_sent', { type: 'rating' });
49
+ } })));
46
50
  case 'sentiment':
47
51
  return (React.createElement(Wrapper, null,
48
- React.createElement(Feedback_1.Sentiment, { settings: settings, onSubmit: (values) => submitFeedback({ type: 'sentiment', values, path }) })));
52
+ React.createElement(Feedback_1.Sentiment, { settings: settings, onSubmit: (values) => {
53
+ submitFeedback({ type: 'sentiment', values, path });
54
+ telemetry_1.telemetry.send('feedback_sent', { type: 'sentiment' });
55
+ } })));
49
56
  case 'comment':
50
57
  return (React.createElement(Wrapper, null,
51
- React.createElement(Feedback_1.Comment, { settings: settings, onSubmit: (values) => submitFeedback({ type: 'comment', values, path }) })));
58
+ React.createElement(Feedback_1.Comment, { settings: settings, onSubmit: (values) => {
59
+ submitFeedback({ type: 'comment', values, path });
60
+ telemetry_1.telemetry.send('feedback_sent', { type: 'comment' });
61
+ } })));
52
62
  default:
53
63
  console.log(`No feedback with type ${type}!`);
54
64
  break;
@@ -66,9 +66,13 @@ const Sentiment = ({ settings, onSubmit, className }) => {
66
66
  }
67
67
  return (React.createElement(Wrapper, { "data-component-name": "Feedback/Sentiment", className: className },
68
68
  React.createElement(Label, { "data-translation-key": translationKeys.label }, translate(translationKeys.label, label || 'Was this page helpful?')),
69
- React.createElement(Vote, { onClick: () => setScore(1) },
69
+ React.createElement(Vote, { onClick: () => {
70
+ setScore(1);
71
+ } },
70
72
  React.createElement(Thumbs_1.ThumbUp, { text: "Yes" })),
71
- React.createElement(Vote, { onClick: () => setScore(-1) },
73
+ React.createElement(Vote, { onClick: () => {
74
+ setScore(-1);
75
+ } },
72
76
  React.createElement(Thumbs_1.ThumbDown, null))));
73
77
  };
74
78
  exports.Sentiment = Sentiment;
@@ -31,6 +31,7 @@ const react_1 = __importDefault(require("react"));
31
31
  const styled_components_1 = __importStar(require("styled-components"));
32
32
  const Link_1 = require("../../mocks/Link");
33
33
  const hooks_1 = require("../../mocks/hooks");
34
+ const telemetry_1 = require("../../mocks/telemetry");
34
35
  function FooterColumn({ column, className }) {
35
36
  var _a;
36
37
  const { translate } = (0, hooks_1.useTranslate)();
@@ -41,7 +42,7 @@ function FooterColumn({ column, className }) {
41
42
  if (columnItem.type === 'error') {
42
43
  return null;
43
44
  }
44
- return columnItem.type === 'separator' ? (react_1.default.createElement(FooterSeparator, { key: columnItem.label + '_' + columnItemIndex }, translate(columnItem.labelTranslationKey, columnItem.label))) : (react_1.default.createElement(FooterLink, { key: columnItemIndex, to: columnItem.link, external: columnItem.external, target: columnItem.target, "data-cy": columnItem.label },
45
+ return columnItem.type === 'separator' ? (react_1.default.createElement(FooterSeparator, { key: columnItem.label + '_' + columnItemIndex }, translate(columnItem.labelTranslationKey, columnItem.label))) : (react_1.default.createElement(FooterLink, { key: columnItemIndex, to: columnItem.link, external: columnItem.external, target: columnItem.target, "data-cy": columnItem.label, onClick: () => telemetry_1.telemetry.send('footer_item_clicked', {}) },
45
46
  react_1.default.createElement(exports.FooterLinkIcon, { url: columnItem.icon, withIconPadding: hasIcon }),
46
47
  translate(columnItem.labelTranslationKey, columnItem.label)));
47
48
  })));
@@ -7,8 +7,9 @@ exports.MenuLinkItem = void 0;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const styled_components_1 = __importDefault(require("styled-components"));
9
9
  const MenuLink_1 = require("../../components/Menu/MenuLink");
10
+ const telemetry_1 = require("../../mocks/telemetry");
10
11
  function MenuLinkItem({ item, children, className, }) {
11
- return (react_1.default.createElement(Wrapper, { "data-component-name": "Sidebar/MenuLinkItem", className: className }, item.link ? (react_1.default.createElement(MenuLink_1.MenuLink, Object.assign({ to: item.link }, item), children)) : (children)));
12
+ return (react_1.default.createElement(Wrapper, { "data-component-name": "Sidebar/MenuLinkItem", className: className, onClick: () => telemetry_1.telemetry.send('sidebar_item_clicked', { label: item.label, type: item.type }) }, item.link ? (react_1.default.createElement(MenuLink_1.MenuLink, Object.assign({ to: item.link }, item), children)) : (children)));
12
13
  }
13
14
  exports.MenuLinkItem = MenuLinkItem;
14
15
  const Wrapper = styled_components_1.default.span ``;
@@ -11,6 +11,7 @@ const LogoutIcon_1 = require("../../icons/LogoutIcon");
11
11
  const Profile_1 = require("../../components/Profile/Profile");
12
12
  const useThemeConfig_1 = require("../../hooks/useThemeConfig");
13
13
  const hooks_1 = require("../../mocks/hooks");
14
+ const telemetry_1 = require("../../mocks/telemetry");
14
15
  function MobileUserProfile() {
15
16
  const { userProfile } = (0, useThemeConfig_1.useThemeConfig)();
16
17
  const { userData, handleLogout, loginUrl } = (0, useProfileProps_1.useProfileProps)();
@@ -20,12 +21,15 @@ function MobileUserProfile() {
20
21
  };
21
22
  if (!(userData === null || userData === void 0 ? void 0 : userData.isAuthenticated) && !loginUrl)
22
23
  return null;
23
- return (react_1.default.createElement(MobileProfileWrapper, { "data-component-name": "Navbar/MobileUserProfile" }, !(userData === null || userData === void 0 ? void 0 : userData.isAuthenticated) ? (react_1.default.createElement(LoginButton, { href: loginUrl, "data-cy": "login-btn" }, translate(translationKeys.login, (userProfile === null || userProfile === void 0 ? void 0 : userProfile.loginLabel) || 'Login'))) : (react_1.default.createElement(react_1.default.Fragment, null,
24
+ return (react_1.default.createElement(MobileProfileWrapper, { "data-component-name": "Navbar/MobileUserProfile" }, !(userData === null || userData === void 0 ? void 0 : userData.isAuthenticated) ? (react_1.default.createElement(LoginButton, { href: loginUrl, "data-cy": "login-btn", onClick: () => telemetry_1.telemetry.send('login_button_clicked', {}) }, translate(translationKeys.login, (userProfile === null || userProfile === void 0 ? void 0 : userProfile.loginLabel) || 'Login'))) : (react_1.default.createElement(react_1.default.Fragment, null,
24
25
  react_1.default.createElement(UserDataWrapper, null,
25
26
  react_1.default.createElement(ProfilePicture, null,
26
27
  react_1.default.createElement(Profile_1.Profile, { name: userData.name, imageUrl: userData.picture })),
27
28
  react_1.default.createElement(UserName, null, userData.name)),
28
- react_1.default.createElement(LogoutButton, { onClick: () => handleLogout() },
29
+ react_1.default.createElement(LogoutButton, { onClick: () => {
30
+ handleLogout();
31
+ telemetry_1.telemetry.send('logout_menu_item_clicked', {});
32
+ } },
29
33
  react_1.default.createElement(LogoutIcon_1.LogoutIcon, null))))));
30
34
  }
31
35
  exports.MobileUserProfile = MobileUserProfile;
@@ -34,6 +34,7 @@ const Link_1 = require("../../mocks/Link");
34
34
  const utils_1 = require("../../mocks/utils");
35
35
  const hooks_1 = require("../../mocks/hooks");
36
36
  const utils_2 = require("../../utils");
37
+ const telemetry_1 = require("../../mocks/telemetry");
37
38
  const Dropdown_1 = require("../../components/Dropdown");
38
39
  function NavbarItem({ navItem, className }) {
39
40
  const { pathname } = (0, react_router_dom_1.useLocation)();
@@ -43,7 +44,7 @@ function NavbarItem({ navItem, className }) {
43
44
  const item = navItem;
44
45
  const isActive = pathname ===
45
46
  (0, utils_2.withPathPrefix)((0, utils_1.getPathnameForLocale)(item.link, defaultLocale, currentLocale, locales));
46
- return (react_1.default.createElement(exports.NavbarMenuItem, { active: isActive, "data-component-name": "Navbar/NavbarItem", className: className },
47
+ return (react_1.default.createElement(exports.NavbarMenuItem, { active: isActive, "data-component-name": "Navbar/NavbarItem", className: className, onClick: () => telemetry_1.telemetry.send('navbar_menu_item_clicked', { type: 'link' }) },
47
48
  react_1.default.createElement(exports.NavbarLink, { to: item.link, external: item.external, target: item.target, active: isActive },
48
49
  item.icon ? react_1.default.createElement(exports.NavbarIcon, { url: item.icon }) : null,
49
50
  react_1.default.createElement(NavbarLabel, null, translate(item.labelTranslationKey, item.label)))));
@@ -53,7 +54,7 @@ function NavbarItem({ navItem, className }) {
53
54
  const groupItems = item.items;
54
55
  const groupItemsComponents = groupItems.map((item, index) => (react_1.default.createElement(Link_1.Link, { key: `${item.label}_${index}`, to: item.link }, translate(item.labelTranslationKey, item.label))));
55
56
  return (react_1.default.createElement(exports.NavbarMenuItemDropdown, { items: groupItemsComponents, triggerEvent: "hover" },
56
- react_1.default.createElement(exports.NavbarMenuItem, { active: false, "data-component-name": "Navbar/NavbarItem", className: className },
57
+ react_1.default.createElement(exports.NavbarMenuItem, { active: false, "data-component-name": "Navbar/NavbarItem", className: className, onClick: () => telemetry_1.telemetry.send('navbar_menu_item_clicked', { type: 'group' }) },
57
58
  react_1.default.createElement(exports.NavbarIcon, { url: item.icon }),
58
59
  react_1.default.createElement(NavbarLabel, null, translate(item.labelTranslationKey, item.label)))));
59
60
  }
@@ -7,11 +7,12 @@ exports.NavbarLogo = void 0;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const styled_components_1 = __importDefault(require("styled-components"));
9
9
  const Link_1 = require("../../mocks/Link");
10
+ const telemetry_1 = require("../../mocks/telemetry");
10
11
  function NavbarLogo({ logo, className }) {
11
12
  if (!logo.image) {
12
13
  return null;
13
14
  }
14
- const img = (react_1.default.createElement(NavLogo, { className: className, src: logo.image, alt: logo.altText, "data-component-name": "NavbarLogo/NavbarLogo" }));
15
+ const img = (react_1.default.createElement(NavLogo, { className: className, src: logo.image, alt: logo.altText, "data-component-name": "NavbarLogo/NavbarLogo", onClick: () => telemetry_1.telemetry.send('logo_clicked', {}) }));
15
16
  return logo.link ? react_1.default.createElement(Link_1.Link, { to: logo.link }, img) : img;
16
17
  }
17
18
  exports.NavbarLogo = NavbarLogo;
@@ -8,13 +8,16 @@ const react_1 = __importDefault(require("react"));
8
8
  const styled_components_1 = __importDefault(require("styled-components"));
9
9
  const useThemeConfig_1 = require("../../hooks/useThemeConfig");
10
10
  const hooks_1 = require("../../mocks/hooks");
11
+ const telemetry_1 = require("../../mocks/telemetry");
11
12
  function LoginLink({ href }) {
12
13
  const { userProfile } = (0, useThemeConfig_1.useThemeConfig)();
13
14
  const { translate } = (0, hooks_1.useTranslate)();
14
15
  const translationKeys = {
15
16
  login: 'theme.profile.login',
16
17
  };
17
- return (react_1.default.createElement(StyledLink, { href: href, "data-translation-key": translationKeys.login }, translate(translationKeys.login, (userProfile === null || userProfile === void 0 ? void 0 : userProfile.loginLabel) || 'Login')));
18
+ return (react_1.default.createElement(StyledLink, { href: href, "data-translation-key": translationKeys.login, onClick: () => {
19
+ telemetry_1.telemetry.send('login_button_clicked', {});
20
+ } }, translate(translationKeys.login, (userProfile === null || userProfile === void 0 ? void 0 : userProfile.loginLabel) || 'Login')));
18
21
  }
19
22
  exports.LoginLink = LoginLink;
20
23
  const StyledLink = styled_components_1.default.a.attrs(() => ({
@@ -33,6 +33,7 @@ const Profile_1 = require("../../components/Profile/Profile");
33
33
  const Link_1 = require("../../mocks/Link");
34
34
  const useThemeConfig_1 = require("../../hooks/useThemeConfig");
35
35
  const hooks_1 = require("../../mocks/hooks");
36
+ const telemetry_1 = require("../../mocks/telemetry");
36
37
  const Dropdown_1 = require("../../components/Dropdown");
37
38
  function UserProfile({ userData, handleLogout, hasDeveloperOnboarding = false, }) {
38
39
  const { userProfile } = (0, useThemeConfig_1.useThemeConfig)();
@@ -51,7 +52,10 @@ function UserProfile({ userData, handleLogout, hasDeveloperOnboarding = false, }
51
52
  menuItems.push(react_1.default.createElement(Link_1.Link, { external: item.external, key: item.label, to: item.link || '', separator: item === null || item === void 0 ? void 0 : item.separatorLine }, item.label));
52
53
  }
53
54
  }
54
- menuItems.push(react_1.default.createElement(Logout, { onClick: () => handleLogout(), "data-translation-key": translationKeys.logout, role: "link" }, translate(translationKeys.logout, (userProfile === null || userProfile === void 0 ? void 0 : userProfile.logoutLabel) || 'Log out')));
55
+ menuItems.push(react_1.default.createElement(Logout, { onClick: () => {
56
+ handleLogout();
57
+ telemetry_1.telemetry.send('logout_menu_item_clicked', {});
58
+ }, "data-translation-key": translationKeys.logout, role: "link" }, translate(translationKeys.logout, (userProfile === null || userProfile === void 0 ? void 0 : userProfile.logoutLabel) || 'Log out')));
55
59
  return (react_1.default.createElement(ProfileDropdown, { items: menuItems },
56
60
  react_1.default.createElement(Profile_1.Profile, { name: userData.name, imageUrl: userData.picture, onClick: userData.logoutDisabled ? undefined : () => setIsOpened(!isOpened) })));
57
61
  }
@@ -34,7 +34,7 @@ const Link_1 = require("../../mocks/Link");
34
34
  const utils_1 = require("../../components/Search/utils");
35
35
  const ProductTag_1 = require("../../components/Search/ProductTag");
36
36
  function SearchItem({ item, className, product }) {
37
- var _a, _b;
37
+ var _a, _b, _c, _d;
38
38
  const ref = (0, react_1.useRef)();
39
39
  (0, react_1.useEffect)(() => {
40
40
  var _a;
@@ -49,19 +49,16 @@ function SearchItem({ item, className, product }) {
49
49
  item.pathName ? (0, utils_1.highlight)(item.pathName) : null)) : null,
50
50
  react_1.default.createElement(Title, null, (0, utils_1.highlight)(item.title)),
51
51
  Array.isArray(item.text) ? react_1.default.createElement(Description, null, (0, utils_1.highlight)(item.text)) : null));
52
- return (react_1.default.createElement(react_1.default.Fragment, null, ((_a = item.parameters) === null || _a === void 0 ? void 0 : _a.length) ? (react_1.default.createElement(react_1.default.Fragment, null, item.parameters.map((param, index) => {
53
- var _a, _b;
54
- const path = `${param.place} → ${((_a = param.path) === null || _a === void 0 ? void 0 : _a.length) ? ((_b = param.path) === null || _b === void 0 ? void 0 : _b.join(' → ')) + ' → ' : ''}`;
55
- return (react_1.default.createElement(SearchLink, { className: className, key: `${item.id}-${index}`, to: item.url, tabIndex: 0, innerRef: ref, "data-component-name": "Search/SearchItem" },
56
- header,
57
- react_1.default.createElement(Place, null,
58
- react_1.default.createElement("div", null,
59
- path,
60
- (0, utils_1.highlight)(param.name)),
61
- react_1.default.createElement("div", null, (0, utils_1.highlight)(param.description)))));
62
- }))) : (react_1.default.createElement(SearchLink, { className: className, to: item.url, tabIndex: 0, innerRef: ref, "data-component-name": "Search/SearchItem" },
52
+ const itemParam = (_a = item.parameters) === null || _a === void 0 ? void 0 : _a[0];
53
+ return (react_1.default.createElement(react_1.default.Fragment, null, itemParam ? (react_1.default.createElement(SearchLink, { className: className, key: `${item.id}-${0}`, to: item.url, tabIndex: 0, innerRef: ref, "data-component-name": "Search/SearchItem" },
63
54
  header,
64
- react_1.default.createElement(Path, null, (_b = item.path) === null || _b === void 0 ? void 0 : _b.join(' → '))))));
55
+ react_1.default.createElement(Place, null,
56
+ react_1.default.createElement("div", null,
57
+ `${itemParam.place} → ${((_b = itemParam.path) === null || _b === void 0 ? void 0 : _b.length) ? ((_c = itemParam.path) === null || _c === void 0 ? void 0 : _c.join(' → ')) + ' → ' : ''}`,
58
+ (0, utils_1.highlight)(itemParam.name)),
59
+ react_1.default.createElement("div", null, (0, utils_1.highlight)(itemParam.description))))) : (react_1.default.createElement(SearchLink, { className: className, to: item.url, tabIndex: 0, innerRef: ref, "data-component-name": "Search/SearchItem" },
60
+ header,
61
+ react_1.default.createElement(Path, null, (_d = item.path) === null || _d === void 0 ? void 0 : _d.join(' → '))))));
65
62
  }
66
63
  exports.SearchItem = SearchItem;
67
64
  const Wrapper = styled_components_1.default.div `
@@ -37,6 +37,7 @@ const MobileSidebarButton_1 = require("../../components/Sidebar/MobileSidebarBut
37
37
  const MenuContainer_1 = require("../../components/Menu/MenuContainer");
38
38
  const SidebarSearch_1 = require("../../components/Search/SidebarSearch");
39
39
  const useThemeConfig_1 = require("../../hooks/useThemeConfig");
40
+ const telemetry_1 = require("../../mocks/telemetry");
40
41
  const MobileSidebarIcon_1 = require("./MobileSidebarIcon");
41
42
  const StyledFooterWrapper = (0, styled_components_1.default)(FooterWrapper_1.FooterWrapper) `
42
43
  display: none;
@@ -65,7 +66,7 @@ function SidebarLayout({ versions, menu, footer, header, growContent, collapsed,
65
66
  react_1.default.createElement(MobileSidebarIcon_1.MobileSidebarIcon, null)))) : null) : (react_1.default.createElement(Wrapper, { "data-component-name": "Sidebar/SidebarLayout", className: className },
66
67
  !(search === null || search === void 0 ? void 0 : search.hide) && (search === null || search === void 0 ? void 0 : search.placement) === 'sidebar' ? react_1.default.createElement(SidebarSearch_1.SidebarSearch, null) : null,
67
68
  react_1.default.createElement(Sidebar_1.Sidebar, { animate: true, opened: isOpen },
68
- header ? react_1.default.createElement(HeaderWrapper_1.HeaderWrapper, null, header) : null,
69
+ header ? (react_1.default.createElement(HeaderWrapper_1.HeaderWrapper, { onClick: () => telemetry_1.telemetry.send('back_to_catalog_button_clicked', {}) }, header)) : null,
69
70
  versions ? react_1.default.createElement(react_1.default.Fragment, null, versions) : null,
70
71
  react_1.default.createElement(MenuContainer_1.MenuContainer, { growContent: growContent }, menu),
71
72
  footer && !isOpen ? (react_1.default.createElement(FooterWrapper_1.FooterWrapper, { "data-component-name": "Sidebar/FooterWrapper" }, footer)) : null)))));
@@ -9,6 +9,7 @@ const ChangeViewButton_1 = require("../../components/SidebarActions/ChangeViewBu
9
9
  const ToggleRightPanelButton_1 = require("../../components/SidebarActions/ToggleRightPanelButton");
10
10
  const CollapseSidebarButton_1 = require("../../components/SidebarActions/CollapseSidebarButton");
11
11
  const styled_1 = require("../../components/SidebarActions/styled");
12
+ const telemetry_1 = require("../../mocks/telemetry");
12
13
  var LayoutVariant;
13
14
  (function (LayoutVariant) {
14
15
  LayoutVariant["STACKED"] = "stacked";
@@ -17,10 +18,21 @@ var LayoutVariant;
17
18
  const SidebarActions = ({ showChangeLayoutButton, showRightPanelToggle, layout, initialShowRightPanelToggle, hideCollapseSidebarButton = false, collapsedSidebar, isOpenapiDocs, onChangeRightPanelViewClick, onChangeViewClick, onChangeCollapseSidebarClick, requestAccessButton, className, }) => {
18
19
  return (react_1.default.createElement(styled_1.ControlsWrap, { className: className, isOpenapiDocs: isOpenapiDocs, isCollapsed: collapsedSidebar, "data-component-name": "Sidebar/Actions" },
19
20
  isOpenapiDocs && (react_1.default.createElement(styled_1.ControlsWrapChangeLayoutButtons, { isCollapsed: collapsedSidebar },
20
- initialShowRightPanelToggle && (react_1.default.createElement(ToggleRightPanelButton_1.ToggleRightPanelButton, { showRightPanelToggle: showRightPanelToggle, onClick: onChangeRightPanelViewClick })),
21
- showChangeLayoutButton && showRightPanelToggle && (react_1.default.createElement(ChangeViewButton_1.ChangeViewButton, { layout: layout, onClick: onChangeViewClick })))),
21
+ initialShowRightPanelToggle && (react_1.default.createElement(ToggleRightPanelButton_1.ToggleRightPanelButton, { showRightPanelToggle: showRightPanelToggle, onClick: () => {
22
+ onChangeRightPanelViewClick();
23
+ telemetry_1.telemetry.send('sidebar_samples_button_clicked', {});
24
+ } })),
25
+ showChangeLayoutButton && showRightPanelToggle && (react_1.default.createElement(ChangeViewButton_1.ChangeViewButton, { layout: layout, onClick: () => {
26
+ onChangeViewClick();
27
+ telemetry_1.telemetry.send('change_layout_button_clicked', {});
28
+ } })))),
22
29
  !collapsedSidebar && requestAccessButton,
23
- !hideCollapseSidebarButton && (react_1.default.createElement(CollapseSidebarButton_1.CollapseSidebarButton, { initialValue: collapsedSidebar, onClick: onChangeCollapseSidebarClick }))));
30
+ !hideCollapseSidebarButton && (react_1.default.createElement(CollapseSidebarButton_1.CollapseSidebarButton, { initialValue: collapsedSidebar, onClick: () => {
31
+ onChangeCollapseSidebarClick();
32
+ collapsedSidebar
33
+ ? telemetry_1.telemetry.send('sidebar_item_expanded', {})
34
+ : telemetry_1.telemetry.send('sidebar_item_collapsed', {});
35
+ } }))));
24
36
  };
25
37
  exports.SidebarActions = SidebarActions;
26
38
  //# sourceMappingURL=SidebarActions.js.map
@@ -34,6 +34,7 @@ const useActiveHeading_1 = require("../../hooks/useActiveHeading");
34
34
  const useThemeConfig_1 = require("../../hooks/useThemeConfig");
35
35
  const hooks_1 = require("../../mocks/hooks");
36
36
  const utils_1 = require("../../components/TableOfContent/utils");
37
+ const telemetry_1 = require("../../mocks/telemetry");
37
38
  function TableOfContent(props) {
38
39
  const { headings, contentWrapper, className } = props;
39
40
  const sidebar = React.useRef(null);
@@ -58,7 +59,7 @@ function TableOfContent(props) {
58
59
  return null;
59
60
  }
60
61
  const href = '#' + heading.id;
61
- return (React.createElement(MenuItem, { key: href + idx, href: href, depth: heading.depth - leastDepth + 1 || 0, className: activeHeadingId === heading.id ? 'active' : '', dangerouslySetInnerHTML: { __html: heading.value || '' }, "data-cy": `toc-${heading.value}` }));
62
+ return (React.createElement(MenuItem, { key: href + idx, href: href, depth: heading.depth - leastDepth + 1 || 0, className: activeHeadingId === heading.id ? 'active' : '', dangerouslySetInnerHTML: { __html: heading.value || '' }, "data-cy": `toc-${heading.value}`, onClick: () => telemetry_1.telemetry.send('toc_item_clicked', {}) }));
62
63
  })))));
63
64
  }
64
65
  exports.TableOfContent = TableOfContent;
package/lib/config.d.ts CHANGED
@@ -412,7 +412,7 @@ declare const scorecardConfigSchema: {
412
412
  declare const catalogSchema: {
413
413
  readonly type: "object";
414
414
  readonly additionalProperties: true;
415
- readonly required: readonly ["slug", "filters", "groupByFirstFilter", "items"];
415
+ readonly required: readonly ["slug", "filters", "items"];
416
416
  readonly properties: {
417
417
  readonly slug: {
418
418
  readonly type: "string";
@@ -1662,7 +1662,7 @@ export declare const themeConfigSchema: {
1662
1662
  readonly '.*': {
1663
1663
  readonly type: "object";
1664
1664
  readonly additionalProperties: true;
1665
- readonly required: readonly ["slug", "filters", "groupByFirstFilter", "items"];
1665
+ readonly required: readonly ["slug", "filters", "items"];
1666
1666
  readonly properties: {
1667
1667
  readonly slug: {
1668
1668
  readonly type: "string";
package/lib/config.js CHANGED
@@ -321,7 +321,7 @@ const scorecardConfigSchema = {
321
321
  const catalogSchema = {
322
322
  type: 'object',
323
323
  additionalProperties: true,
324
- required: ['slug', 'filters', 'groupByFirstFilter', 'items'],
324
+ required: ['slug', 'filters', 'items'],
325
325
  properties: {
326
326
  slug: { type: 'string' },
327
327
  filters: { type: 'array', items: catalogFilterSchema },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.18.2",
3
+ "version": "0.18.3",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -81,7 +81,7 @@
81
81
  "tsconfig-paths-webpack-plugin": "^3.5.2",
82
82
  "typescript": "^4.8.4",
83
83
  "webpack": "^5.72.0",
84
- "@redocly/portal-types": "1.0.1"
84
+ "@redocly/portal-types": "1.0.2"
85
85
  },
86
86
  "dependencies": {
87
87
  "@redocly/ajv": "^8.11.0",
@@ -3,14 +3,20 @@ import styled from 'styled-components';
3
3
 
4
4
  import { Link } from '@portal/Link';
5
5
 
6
- export const Breadcrumb = (props: { label: string; link?: string; isActive: boolean }) => {
7
- const { label, link, isActive } = props;
6
+ export const Breadcrumb = (props: {
7
+ label: string;
8
+ link?: string;
9
+ isActive: boolean;
10
+ onClick?: () => void;
11
+ }) => {
12
+ const { label, link, isActive, onClick } = props;
8
13
 
9
14
  return (
10
15
  <BreadcrumbWrapper
11
16
  data-component-name="Breadcrumbs/Breadcrumb"
12
17
  isLink={link != null}
13
18
  isActive={isActive}
19
+ onClick={onClick}
14
20
  >
15
21
  {link ? (
16
22
  <BreadcrumbLink to={link}>{label}</BreadcrumbLink>
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
4
  import { useBreadcrumbs } from '@portal/Sidebar/useBreadcrumbs';
5
+ import { telemetry } from '@portal/telemetry';
5
6
 
6
7
  import { Breadcrumb } from './Breadcrumb';
7
8
 
@@ -18,7 +19,18 @@ export const Breadcrumbs = (props: { className?: string }) => {
18
19
  const isLast = idx === breadcrumbs.length - 1;
19
20
  return (
20
21
  <React.Fragment key={idx}>
21
- <Breadcrumb link={breadcrumb.link} label={breadcrumb.label} isActive={isLast} />
22
+ <Breadcrumb
23
+ link={breadcrumb.link}
24
+ label={breadcrumb.label}
25
+ isActive={isLast}
26
+ onClick={() => {
27
+ telemetry.send('breadcrumb_clicked', {
28
+ link: breadcrumb.link,
29
+ position: idx + 1,
30
+ total_breadcrumbs: breadcrumbs.length,
31
+ });
32
+ }}
33
+ />
22
34
  {isLast ? null : <div>/</div>}
23
35
  </React.Fragment>
24
36
  );
@@ -6,6 +6,7 @@ import { Link } from '@portal/Link';
6
6
  import { Highlight } from '@theme/ui/Highlight';
7
7
  import { Tags } from '@theme/components/Tags';
8
8
  import { useTranslate } from '@portal/hooks';
9
+ import { telemetry } from '@portal/telemetry';
9
10
 
10
11
  export function CatalogCard({ item }: { item: CatalogItem }): JSX.Element {
11
12
  const { translate } = useTranslate();
@@ -14,7 +15,7 @@ export function CatalogCard({ item }: { item: CatalogItem }): JSX.Element {
14
15
  };
15
16
  return (
16
17
  <Link key={item.docsLink || item.link} to={item.docsLink || item.link}>
17
- <StyledCard>
18
+ <StyledCard onClick={() => telemetry.send('catalog_item_clicked', {})}>
18
19
  <CardTitle>
19
20
  <Highlight>{item.title}</Highlight>
20
21
  </CardTitle>
@@ -3,6 +3,7 @@ import { useLocation, useNavigate } from 'react-router-dom';
3
3
 
4
4
  import type { Location } from 'react-router-dom';
5
5
 
6
+ import { telemetry } from '@portal/telemetry';
6
7
  import type { ResolvedNavItem } from '@theme/types/portal';
7
8
  import type {
8
9
  CatalogItem,
@@ -45,6 +46,7 @@ export function useCatalog(items: ResolvedNavItem[], config: CatalogConfig): Fil
45
46
  }
46
47
  return [...prev.slice(0, filterIdx), newFilterOptions, ...prev.slice(filterIdx + 1)];
47
48
  });
49
+ telemetry.send('catalog_filter_changed', { type: 'toggle' });
48
50
  window.scrollTo(0, 0);
49
51
  }, []);
50
52
 
@@ -68,6 +70,7 @@ export function useCatalog(items: ResolvedNavItem[], config: CatalogConfig): Fil
68
70
  : f,
69
71
  );
70
72
  });
73
+ telemetry.send('catalog_filter_changed', { type: 'select' });
71
74
  window.scrollTo(0, 0);
72
75
  },
73
76
  [filtersWithOptions],
@@ -153,7 +156,16 @@ export function useCatalog(items: ResolvedNavItem[], config: CatalogConfig): Fil
153
156
  ? groupByFirstFilter(resolvedFilters, filteredItems)
154
157
  : [{ title: 'APIs', items: filteredItems }];
155
158
 
156
- return { groups, filters: resolvedFilters, setFilterTerm, filterTerm };
159
+ return {
160
+ groups,
161
+ filters: resolvedFilters,
162
+ setFilterTerm: (newTerm) => {
163
+ setFilterTerm(newTerm);
164
+
165
+ telemetry.send('catalog_filter_changed', { type: 'term' });
166
+ },
167
+ filterTerm,
168
+ };
157
169
  }, [
158
170
  filtersWithOptions,
159
171
  normalizedItems,
@@ -4,6 +4,7 @@ import styled from 'styled-components';
4
4
  import { CodeBlockControlButton } from '@theme/components/CodeBlock';
5
5
  import { CollapseIcon, DeselectIcon, ExpandIcon, ReportIcon, SelectIcon } from '@theme/icons';
6
6
  import { useThemeConfig } from '@theme/hooks';
7
+ import { telemetry } from '@portal/telemetry';
7
8
 
8
9
  import { CopyButton } from '../CopyButton';
9
10
 
@@ -68,7 +69,10 @@ export function CodeBlockControls({
68
69
  toasterDuration={copy.toasterDuration}
69
70
  buttonText={copy.label}
70
71
  tooltipText={copy.tooltipText}
71
- onCopyClick={copy?.onClick}
72
+ onCopyClick={() => {
73
+ copy?.onClick;
74
+ telemetry.send('code_snippet_copied', {});
75
+ }}
72
76
  />
73
77
  ) : null}
74
78
 
@@ -139,6 +143,7 @@ export function CodeBlockControls({
139
143
  asIcon={controlsType === 'icon'}
140
144
  title={report.tooltipText}
141
145
  {...report.props}
146
+ onClick={() => telemetry.send('code_snippet_reported', {})}
142
147
  >
143
148
  {controlsType === 'icon' ? <ReportIcon /> : report.props?.buttonText || 'Report'}
144
149
  </CodeBlockControlButton>
@@ -3,6 +3,7 @@ import styled from 'styled-components';
3
3
 
4
4
  import { ColorModeIcon } from '@theme/icons/ColorModeIcon';
5
5
  import { useMount, useThemeConfig } from '@theme/hooks';
6
+ import { telemetry } from '@portal/telemetry';
6
7
 
7
8
  interface ColorModeSwitcherProps {
8
9
  className?: string;
@@ -34,6 +35,8 @@ export function ColorModeSwitcher(props: ColorModeSwitcherProps): JSX.Element |
34
35
  window.requestAnimationFrame(() => {
35
36
  document.documentElement.classList.remove('notransition');
36
37
  });
38
+
39
+ telemetry.send('color_mode_switched', { from: activeColorMode, to: mode });
37
40
  };
38
41
 
39
42
  return (
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
4
  import { Link } from '@portal/Link';
5
+ import { telemetry } from '@portal/telemetry';
5
6
 
6
7
  export interface EditPageButtonProps {
7
8
  text: string;
@@ -11,7 +12,7 @@ export interface EditPageButtonProps {
11
12
 
12
13
  export const EditPageButton = ({ text, to, icon }: EditPageButtonProps): JSX.Element => {
13
14
  return (
14
- <EditButton to={to}>
15
+ <EditButton to={to} onClick={() => telemetry.send('edit_page_link_clicked', {})}>
15
16
  {icon ? <ButtonIcon src={icon} /> : null}
16
17
  <ButtonText>{text}</ButtonText>
17
18
  </EditButton>
@@ -6,6 +6,7 @@ import type { FeedbackProps } from '@theme/types/portal/src/shared/types/feedbac
6
6
  import { Rating, Sentiment, Comment } from '@theme/components/Feedback';
7
7
  import { useThemeConfig } from '@theme/hooks';
8
8
  import { useSubmitFeedback } from '@portal/Feedback/useSubmitFeedback';
9
+ import { telemetry } from '@portal/telemetry';
9
10
 
10
11
  export const Feedback = (props: FeedbackProps & { path?: string }) => {
11
12
  const { submitFeedback } = useSubmitFeedback();
@@ -20,7 +21,10 @@ export const Feedback = (props: FeedbackProps & { path?: string }) => {
20
21
  <Wrapper>
21
22
  <Rating
22
23
  settings={settings}
23
- onSubmit={(values) => submitFeedback({ type: 'rating', values, path })}
24
+ onSubmit={(values) => {
25
+ submitFeedback({ type: 'rating', values, path });
26
+ telemetry.send('feedback_sent', { type: 'rating' });
27
+ }}
24
28
  />
25
29
  </Wrapper>
26
30
  );
@@ -29,7 +33,10 @@ export const Feedback = (props: FeedbackProps & { path?: string }) => {
29
33
  <Wrapper>
30
34
  <Sentiment
31
35
  settings={settings}
32
- onSubmit={(values) => submitFeedback({ type: 'sentiment', values, path })}
36
+ onSubmit={(values) => {
37
+ submitFeedback({ type: 'sentiment', values, path });
38
+ telemetry.send('feedback_sent', { type: 'sentiment' });
39
+ }}
33
40
  />
34
41
  </Wrapper>
35
42
  );
@@ -38,7 +45,10 @@ export const Feedback = (props: FeedbackProps & { path?: string }) => {
38
45
  <Wrapper>
39
46
  <Comment
40
47
  settings={settings}
41
- onSubmit={(values) => submitFeedback({ type: 'comment', values, path })}
48
+ onSubmit={(values) => {
49
+ submitFeedback({ type: 'comment', values, path });
50
+ telemetry.send('feedback_sent', { type: 'comment' });
51
+ }}
42
52
  />
43
53
  </Wrapper>
44
54
  );
@@ -69,10 +69,18 @@ export const Sentiment = ({ settings, onSubmit, className }: SentimentProps): JS
69
69
  <Label data-translation-key={translationKeys.label}>
70
70
  {translate(translationKeys.label, label || 'Was this page helpful?')}
71
71
  </Label>
72
- <Vote onClick={() => setScore(1)}>
72
+ <Vote
73
+ onClick={() => {
74
+ setScore(1);
75
+ }}
76
+ >
73
77
  <ThumbUp text="Yes" />
74
78
  </Vote>
75
- <Vote onClick={() => setScore(-1)}>
79
+ <Vote
80
+ onClick={() => {
81
+ setScore(-1);
82
+ }}
83
+ >
76
84
  <ThumbDown />
77
85
  </Vote>
78
86
  </Wrapper>
@@ -4,6 +4,7 @@ import styled, { css } from 'styled-components';
4
4
  import { Link } from '@portal/Link';
5
5
  import type { ResolvedNavItem } from '@theme/types/portal';
6
6
  import { useTranslate } from '@portal/hooks';
7
+ import { telemetry } from '@portal/telemetry';
7
8
 
8
9
  interface FooterColumnProps {
9
10
  column: ResolvedNavItem;
@@ -35,6 +36,7 @@ export function FooterColumn({ column, className }: FooterColumnProps): JSX.Elem
35
36
  external={columnItem.external}
36
37
  target={columnItem.target}
37
38
  data-cy={columnItem.label}
39
+ onClick={() => telemetry.send('footer_item_clicked', {})}
38
40
  >
39
41
  <FooterLinkIcon url={columnItem.icon} withIconPadding={hasIcon} />
40
42
  {translate(columnItem.labelTranslationKey, columnItem.label)}
@@ -3,6 +3,7 @@ import styled from 'styled-components';
3
3
 
4
4
  import { MenuLink } from '@theme/components/Menu/MenuLink';
5
5
  import type { MenuItemProps } from '@theme/components/Sidebar/types';
6
+ import { telemetry } from '@portal/telemetry';
6
7
 
7
8
  export function MenuLinkItem({
8
9
  item,
@@ -10,7 +11,11 @@ export function MenuLinkItem({
10
11
  className,
11
12
  }: React.PropsWithChildren<MenuItemProps>): JSX.Element {
12
13
  return (
13
- <Wrapper data-component-name="Sidebar/MenuLinkItem" className={className}>
14
+ <Wrapper
15
+ data-component-name="Sidebar/MenuLinkItem"
16
+ className={className}
17
+ onClick={() => telemetry.send('sidebar_item_clicked', { label: item.label, type: item.type })}
18
+ >
14
19
  {item.link ? (
15
20
  <MenuLink to={item.link} {...item}>
16
21
  {children}
@@ -6,6 +6,7 @@ import { LogoutIcon } from '@theme/icons/LogoutIcon';
6
6
  import { AvatarWrapper, ProfileWrapper, Profile } from '@theme/components/Profile/Profile';
7
7
  import { useThemeConfig } from '@theme/hooks/useThemeConfig';
8
8
  import { useTranslate } from '@portal/hooks';
9
+ import { telemetry } from '@portal/telemetry';
9
10
 
10
11
  export function MobileUserProfile() {
11
12
  const { userProfile } = useThemeConfig();
@@ -20,7 +21,11 @@ export function MobileUserProfile() {
20
21
  return (
21
22
  <MobileProfileWrapper data-component-name="Navbar/MobileUserProfile">
22
23
  {!userData?.isAuthenticated ? (
23
- <LoginButton href={loginUrl} data-cy="login-btn">
24
+ <LoginButton
25
+ href={loginUrl}
26
+ data-cy="login-btn"
27
+ onClick={() => telemetry.send('login_button_clicked', {})}
28
+ >
24
29
  {translate(translationKeys.login, userProfile?.loginLabel || 'Login')}
25
30
  </LoginButton>
26
31
  ) : (
@@ -31,7 +36,12 @@ export function MobileUserProfile() {
31
36
  </ProfilePicture>
32
37
  <UserName>{userData.name}</UserName>
33
38
  </UserDataWrapper>
34
- <LogoutButton onClick={() => handleLogout()}>
39
+ <LogoutButton
40
+ onClick={() => {
41
+ handleLogout();
42
+ telemetry.send('logout_menu_item_clicked', {});
43
+ }}
44
+ >
35
45
  <LogoutIcon />
36
46
  </LogoutButton>
37
47
  </>
@@ -11,6 +11,7 @@ import type {
11
11
  } from '@theme/types/portal';
12
12
  import { useI18nConfig, useTranslate } from '@portal/hooks';
13
13
  import { withPathPrefix } from '@theme/utils';
14
+ import { telemetry } from '@portal/telemetry';
14
15
  import { Dropdown } from '@theme/components/Dropdown';
15
16
 
16
17
  export interface NavbarItemProps {
@@ -34,6 +35,7 @@ export function NavbarItem({ navItem, className }: NavbarItemProps): JSX.Element
34
35
  active={isActive}
35
36
  data-component-name="Navbar/NavbarItem"
36
37
  className={className}
38
+ onClick={() => telemetry.send('navbar_menu_item_clicked', { type: 'link' })}
37
39
  >
38
40
  <NavbarLink to={item.link} external={item.external} target={item.target} active={isActive}>
39
41
  {item.icon ? <NavbarIcon url={item.icon} /> : null}
@@ -58,6 +60,7 @@ export function NavbarItem({ navItem, className }: NavbarItemProps): JSX.Element
58
60
  active={false}
59
61
  data-component-name="Navbar/NavbarItem"
60
62
  className={className}
63
+ onClick={() => telemetry.send('navbar_menu_item_clicked', { type: 'group' })}
61
64
  >
62
65
  <NavbarIcon url={item.icon} />
63
66
  <NavbarLabel>{translate(item.labelTranslationKey, item.label)}</NavbarLabel>
@@ -3,6 +3,7 @@ import styled from 'styled-components';
3
3
 
4
4
  import { Link } from '@portal/Link';
5
5
  import type { LogoConfig } from '@theme/types/portal';
6
+ import { telemetry } from '@portal/telemetry';
6
7
 
7
8
  export type NavbarLogoProps = {
8
9
  logo: Pick<LogoConfig, 'image' | 'link' | 'altText'>;
@@ -19,6 +20,7 @@ export function NavbarLogo({ logo, className }: NavbarLogoProps): JSX.Element |
19
20
  src={logo.image}
20
21
  alt={logo.altText}
21
22
  data-component-name="NavbarLogo/NavbarLogo"
23
+ onClick={() => telemetry.send('logo_clicked', {})}
22
24
  />
23
25
  );
24
26
  return logo.link ? <Link to={logo.link}>{img}</Link> : img;
@@ -3,6 +3,7 @@ import styled from 'styled-components';
3
3
 
4
4
  import { useThemeConfig } from '@theme/hooks/useThemeConfig';
5
5
  import { useTranslate } from '@portal/hooks';
6
+ import { telemetry } from '@portal/telemetry';
6
7
 
7
8
  export interface LoginLinkProps {
8
9
  href: string;
@@ -16,7 +17,13 @@ export function LoginLink({ href }: LoginLinkProps): JSX.Element {
16
17
  };
17
18
 
18
19
  return (
19
- <StyledLink href={href} data-translation-key={translationKeys.login}>
20
+ <StyledLink
21
+ href={href}
22
+ data-translation-key={translationKeys.login}
23
+ onClick={() => {
24
+ telemetry.send('login_button_clicked', {});
25
+ }}
26
+ >
20
27
  {translate(translationKeys.login, userProfile?.loginLabel || 'Login')}
21
28
  </StyledLink>
22
29
  );
@@ -7,6 +7,7 @@ import { Profile } from '@theme/components/Profile/Profile';
7
7
  import { Link } from '@portal/Link';
8
8
  import { useThemeConfig } from '@theme/hooks/useThemeConfig';
9
9
  import { useTranslate } from '@portal/hooks';
10
+ import { telemetry } from '@portal/telemetry';
10
11
  import { Dropdown, DropdownList, DropdownListItem } from '@theme/components/Dropdown';
11
12
 
12
13
  export function UserProfile({
@@ -49,7 +50,10 @@ export function UserProfile({
49
50
 
50
51
  menuItems.push(
51
52
  <Logout
52
- onClick={() => handleLogout()}
53
+ onClick={() => {
54
+ handleLogout();
55
+ telemetry.send('logout_menu_item_clicked', {});
56
+ }}
53
57
  data-translation-key={translationKeys.logout}
54
58
  role="link"
55
59
  >
@@ -37,35 +37,30 @@ export function SearchItem({ item, className, product }: SearchItemProps): JSX.E
37
37
  </Wrapper>
38
38
  );
39
39
 
40
+ const itemParam = item.parameters?.[0];
41
+
40
42
  return (
41
43
  <>
42
- {item.parameters?.length ? (
43
- <>
44
- {item.parameters.map((param, index) => {
45
- const path = `${param.place} → ${
46
- param.path?.length ? param.path?.join(' → ') + ' → ' : ''
47
- }`;
48
- return (
49
- <SearchLink
50
- className={className}
51
- key={`${item.id}-${index}`}
52
- to={item.url}
53
- tabIndex={0}
54
- innerRef={ref}
55
- data-component-name="Search/SearchItem"
56
- >
57
- {header}
58
- <Place>
59
- <div>
60
- {path}
61
- {highlight(param.name)}
62
- </div>
63
- <div>{highlight(param.description)}</div>
64
- </Place>
65
- </SearchLink>
66
- );
67
- })}
68
- </>
44
+ {itemParam ? (
45
+ <SearchLink
46
+ className={className}
47
+ key={`${item.id}-${0}`}
48
+ to={item.url}
49
+ tabIndex={0}
50
+ innerRef={ref}
51
+ data-component-name="Search/SearchItem"
52
+ >
53
+ {header}
54
+ <Place>
55
+ <div>
56
+ {`${itemParam.place} → ${
57
+ itemParam.path?.length ? itemParam.path?.join(' → ') + ' → ' : ''
58
+ }`}
59
+ {highlight(itemParam.name)}
60
+ </div>
61
+ <div>{highlight(itemParam.description)}</div>
62
+ </Place>
63
+ </SearchLink>
69
64
  ) : (
70
65
  <SearchLink
71
66
  className={className}
@@ -9,6 +9,7 @@ import { MobileSidebarButton } from '@theme/components/Sidebar/MobileSidebarButt
9
9
  import { MenuContainer } from '@theme/components/Menu/MenuContainer';
10
10
  import { SidebarSearch } from '@theme/components/Search/SidebarSearch';
11
11
  import { useThemeConfig } from '@theme/hooks/useThemeConfig';
12
+ import { telemetry } from '@portal/telemetry';
12
13
 
13
14
  import { MobileSidebarIcon } from './MobileSidebarIcon';
14
15
 
@@ -81,7 +82,11 @@ export function SidebarLayout({
81
82
  <Wrapper data-component-name="Sidebar/SidebarLayout" className={className}>
82
83
  {!search?.hide && search?.placement === 'sidebar' ? <SidebarSearch /> : null}
83
84
  <Sidebar animate={true} opened={isOpen}>
84
- {header ? <HeaderWrapper>{header}</HeaderWrapper> : null}
85
+ {header ? (
86
+ <HeaderWrapper onClick={() => telemetry.send('back_to_catalog_button_clicked', {})}>
87
+ {header}
88
+ </HeaderWrapper>
89
+ ) : null}
85
90
  {versions ? <>{versions}</> : null}
86
91
  <MenuContainer growContent={growContent}>{menu}</MenuContainer>
87
92
  {footer && !isOpen ? (
@@ -7,6 +7,7 @@ import {
7
7
  ControlsWrap,
8
8
  ControlsWrapChangeLayoutButtons,
9
9
  } from '@theme/components/SidebarActions/styled';
10
+ import { telemetry } from '@portal/telemetry';
10
11
 
11
12
  export enum LayoutVariant {
12
13
  STACKED = 'stacked',
@@ -54,11 +55,20 @@ export const SidebarActions = ({
54
55
  {initialShowRightPanelToggle && (
55
56
  <ToggleRightPanelButton
56
57
  showRightPanelToggle={showRightPanelToggle}
57
- onClick={onChangeRightPanelViewClick}
58
+ onClick={() => {
59
+ onChangeRightPanelViewClick();
60
+ telemetry.send('sidebar_samples_button_clicked', {});
61
+ }}
58
62
  />
59
63
  )}
60
64
  {showChangeLayoutButton && showRightPanelToggle && (
61
- <ChangeViewButton layout={layout} onClick={onChangeViewClick} />
65
+ <ChangeViewButton
66
+ layout={layout}
67
+ onClick={() => {
68
+ onChangeViewClick();
69
+ telemetry.send('change_layout_button_clicked', {});
70
+ }}
71
+ />
62
72
  )}
63
73
  </ControlsWrapChangeLayoutButtons>
64
74
  )}
@@ -66,7 +76,12 @@ export const SidebarActions = ({
66
76
  {!hideCollapseSidebarButton && (
67
77
  <CollapseSidebarButton
68
78
  initialValue={collapsedSidebar}
69
- onClick={onChangeCollapseSidebarClick}
79
+ onClick={() => {
80
+ onChangeCollapseSidebarClick();
81
+ collapsedSidebar
82
+ ? telemetry.send('sidebar_item_expanded', {})
83
+ : telemetry.send('sidebar_item_collapsed', {});
84
+ }}
70
85
  />
71
86
  )}
72
87
  </ControlsWrap>
@@ -11,6 +11,7 @@ import {
11
11
  getDisplayedHeadings,
12
12
  getLeastDepth,
13
13
  } from '@theme/components/TableOfContent/utils';
14
+ import { telemetry } from '@portal/telemetry';
14
15
 
15
16
  interface TableOfContentProps {
16
17
  headings?: Array<MdHeading | null> | null | undefined;
@@ -64,6 +65,7 @@ export function TableOfContent(props: TableOfContentProps): JSX.Element | null {
64
65
  className={activeHeadingId === heading.id ? 'active' : ''}
65
66
  dangerouslySetInnerHTML={{ __html: heading.value || '' }}
66
67
  data-cy={`toc-${heading.value}`}
68
+ onClick={() => telemetry.send('toc_item_clicked', {})}
67
69
  />
68
70
  );
69
71
  })}
package/src/config.ts CHANGED
@@ -362,7 +362,7 @@ const scorecardConfigSchema = {
362
362
  const catalogSchema = {
363
363
  type: 'object',
364
364
  additionalProperties: true,
365
- required: ['slug', 'filters', 'groupByFirstFilter', 'items'],
365
+ required: ['slug', 'filters', 'items'],
366
366
  properties: {
367
367
  slug: { type: 'string' },
368
368
  filters: { type: 'array', items: catalogFilterSchema },