@redocly/theme 0.67.0-next.4 → 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.js +2 -6
- package/lib/components/Breadcrumbs/Breadcrumbs.js +3 -18
- 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/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/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/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 +2 -1
- package/src/components/Breadcrumbs/Breadcrumbs.tsx +4 -4
- 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/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/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/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
|
@@ -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/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
|
+
}
|