From 66b88f7ebed603eb02854e573d99841aa106b55a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rge=20N=C3=A6ss?= Date: Tue, 23 Jan 2024 14:45:49 +0100 Subject: [PATCH] feat(core): add telemetry scaffolding to Studio (#5503) * feat(core): add telemetry scaffolding to Studio * fix(telemetry): log whether document was published for the first time, and published immediately after created --- packages/@sanity/cli/package.json | 2 +- packages/sanity/package.json | 4 +- .../sanity/src/core/studio/StudioProvider.tsx | 9 ++-- .../core/studio/StudioTelemetryProvider.tsx | 52 +++++++++++++++++++ .../documentActions/PublishAction.tsx | 12 +++++ .../documentActions.telemetry.ts | 18 +++++++ yarn.lock | 13 +++-- 7 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 packages/sanity/src/core/studio/StudioTelemetryProvider.tsx create mode 100644 packages/sanity/src/structure/documentActions/__telemetry__/documentActions.telemetry.ts diff --git a/packages/@sanity/cli/package.json b/packages/@sanity/cli/package.json index a6fc1d37c2d..0f3eba2c145 100644 --- a/packages/@sanity/cli/package.json +++ b/packages/@sanity/cli/package.json @@ -63,7 +63,7 @@ }, "dependencies": { "@babel/traverse": "^7.23.5", - "@sanity/telemetry": "^0.7.5", + "@sanity/telemetry": "^0.7.6", "chalk": "^4.1.2", "esbuild": "^0.19.8", "esbuild-register": "^3.4.1", diff --git a/packages/sanity/package.json b/packages/sanity/package.json index 1b8a2eea86d..250c853842e 100644 --- a/packages/sanity/package.json +++ b/packages/sanity/package.json @@ -199,7 +199,7 @@ "@sanity/portable-text-editor": "3.25.0", "@sanity/presentation": "1.7.0", "@sanity/schema": "3.25.0", - "@sanity/telemetry": "^0.7.5", + "@sanity/telemetry": "^0.7.6", "@sanity/types": "3.25.0", "@sanity/ui": "^2.0.0", "@sanity/util": "3.25.0", @@ -212,6 +212,7 @@ "@types/speakingurl": "^13.0.3", "@types/use-sync-external-store": "^0.0.5", "@vitejs/plugin-react": "^4.2.0", + "arrify": "^1.0.1", "chalk": "^4.1.2", "chokidar": "^3.5.3", "classnames": "^2.2.5", @@ -289,6 +290,7 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.0.16", + "@types/arrify": "^1.0.4", "@types/connect-history-api-fallback": "^1.5.2", "@types/lodash": "^4.14.149", "@types/log-symbols": "^2.0.0", diff --git a/packages/sanity/src/core/studio/StudioProvider.tsx b/packages/sanity/src/core/studio/StudioProvider.tsx index 781fa32b71f..75e7d72a64e 100644 --- a/packages/sanity/src/core/studio/StudioProvider.tsx +++ b/packages/sanity/src/core/studio/StudioProvider.tsx @@ -26,6 +26,7 @@ import { } from './screens' import {WorkspaceLoader} from './workspaceLoader' import {WorkspacesProvider} from './workspaces' +import {StudioTelemetryProvider} from './StudioTelemetryProvider' Refractor.registerLanguage(bash) Refractor.registerLanguage(javascript) @@ -54,9 +55,11 @@ export function StudioProvider({ }: StudioProviderProps) { const _children = ( - - {children} - + + + {children} + + ) diff --git a/packages/sanity/src/core/studio/StudioTelemetryProvider.tsx b/packages/sanity/src/core/studio/StudioTelemetryProvider.tsx new file mode 100644 index 00000000000..2f88433355d --- /dev/null +++ b/packages/sanity/src/core/studio/StudioTelemetryProvider.tsx @@ -0,0 +1,52 @@ +import {createBatchedStore, createSessionId} from '@sanity/telemetry' +import {TelemetryProvider} from '@sanity/telemetry/react' +import React, {ReactNode, useEffect, useMemo} from 'react' +import arrify from 'arrify' +import {SANITY_VERSION} from '../version' +import {Config} from '../config' +import {useClient} from '../hooks' + +const sessionId = createSessionId() + +// Wrap the app in a TelemetryProvider +// This will enable usage of the `useTelemetry()` hook +export function StudioTelemetryProvider(props: {children: ReactNode; config: Config}) { + const client = useClient({apiVersion: 'v2023-12-18'}) + + const projectId = client.config().projectId + const store = useMemo(() => { + return createBatchedStore(sessionId, { + // submit any pending events every ms + flushInterval: 30000, + + // implements user consent resolving + resolveConsent: () => client.request({uri: '/intake/telemetry-status'}), + + // implements sending events to backend + sendEvents: (batch) => + client.request({ + uri: '/intake/batch', + method: 'POST', + json: true, + body: {projectId, batch}, + }), + // opts into a different strategy for sending events when the browser close, reload or navigate away from the current page + sendBeacon: (batch) => + navigator.sendBeacon(client.getUrl('/intake/batch'), JSON.stringify({projectId, batch})), + }) + }, [client, projectId]) + + useEffect(() => { + store.logger.updateUserProperties({ + studioVersion: SANITY_VERSION, + plugins: arrify(props.config).flatMap( + (config) => + config.plugins?.flatMap((plugin) => ({ + name: plugin.name, + })) || [], + ), + }) + }, [props.config, store.logger]) + + return {props.children} +} diff --git a/packages/sanity/src/structure/documentActions/PublishAction.tsx b/packages/sanity/src/structure/documentActions/PublishAction.tsx index cd2111aefe9..00e15edc8ef 100644 --- a/packages/sanity/src/structure/documentActions/PublishAction.tsx +++ b/packages/sanity/src/structure/documentActions/PublishAction.tsx @@ -1,8 +1,11 @@ import {CheckmarkIcon, PublishIcon} from '@sanity/icons' import {isValidationErrorMarker} from '@sanity/types' import React, {useCallback, useEffect, useState} from 'react' +import {useTelemetry} from '@sanity/telemetry/react' import {useDocumentPane} from '../panes/document/useDocumentPane' import {structureLocaleNamespace, type StructureLocaleResourceKeys} from '../i18n' + +import {DocumentPublished} from './__telemetry__/documentActions.telemetry' import { DocumentActionComponent, InsufficientPermissionsMessage, @@ -122,7 +125,13 @@ export const PublishAction: DocumentActionComponent = (props) => { return () => clearTimeout(timer) }, [changesOpen, publishState, hasDraft, onHistoryOpen]) + const telemetry = useTelemetry() + const handle = useCallback(() => { + telemetry.log(DocumentPublished, { + publishedImmediately: !draft?._createdAt, + previouslyPublished: Boolean(published), + }) if ( syncState.isSyncing || validationStatus.isValidating || @@ -133,6 +142,9 @@ export const PublishAction: DocumentActionComponent = (props) => { doPublish() } }, [ + telemetry, + draft?._createdAt, + published, syncState.isSyncing, validationStatus.isValidating, validationStatus.revision, diff --git a/packages/sanity/src/structure/documentActions/__telemetry__/documentActions.telemetry.ts b/packages/sanity/src/structure/documentActions/__telemetry__/documentActions.telemetry.ts new file mode 100644 index 00000000000..4535a379ea3 --- /dev/null +++ b/packages/sanity/src/structure/documentActions/__telemetry__/documentActions.telemetry.ts @@ -0,0 +1,18 @@ +import {defineEvent} from '@sanity/telemetry' + +interface DocumentPublishedInfo { + /** + * The document was created and published straight away + */ + publishedImmediately: boolean + + /** + * The document had a previously published version when it was published + */ + previouslyPublished: boolean +} +export const DocumentPublished = defineEvent({ + name: 'Document Published', + version: 1, + description: 'User clicked the "Publish" button in the document pane', +}) diff --git a/yarn.lock b/yarn.lock index a801adc18ff..a9771cfb889 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3773,10 +3773,10 @@ dependencies: "@sanity/uuid" "3.0.2" -"@sanity/telemetry@^0.7.5": - version "0.7.5" - resolved "https://registry.yarnpkg.com/@sanity/telemetry/-/telemetry-0.7.5.tgz#e9c39d35a55892ec230654f33d008f75aa2a398a" - integrity sha512-lY1Lmt2zl9YIIXO2bAFGXR542ovpn8jmLmIos1mQU4D+ItbZsPBxt9g72bJCsUZ7yodMf2K4t4Tp7BTv0as9sg== +"@sanity/telemetry@^0.7.7": + version "0.7.7" + resolved "https://registry.yarnpkg.com/@sanity/telemetry/-/telemetry-0.7.7.tgz#7905bb51aaf4217363242d229618ca65a0ec773f" + integrity sha512-YUoAMrl0XEf5C4Jt0n+wmJAR7gDrraic3u7yxog0U2QukgeOn9BDhXF5rF9jMuDllGZmUbBaFq+mh5sW/tACWw== dependencies: lodash "^4.17.21" react "^18.2.0" @@ -4067,6 +4067,11 @@ resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.1.tgz#3286741fb8f1e1580ac28784add4c7a1d49bdfbc" integrity sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q== +"@types/arrify@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@types/arrify/-/arrify-1.0.4.tgz#0b7bc8d80e29db296e8e0c59e009274a03dcf783" + integrity sha512-63nK8r8jvEVJ1r0ENaY9neB1wDzPHFYAzKiIxPawuzcijEX8XeOywwPL8fkSCwiTIYop9MSh+TKy9L2ZzTXV6g== + "@types/babel__core@^7.1.14", "@types/babel__core@^7.20.4": version "7.20.4" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.4.tgz#26a87347e6c6f753b3668398e34496d6d9ac6ac0"