diff --git a/packages/sanity/src/core/bundles/__telemetry__/releases.telemetry.ts b/packages/sanity/src/core/bundles/__telemetry__/releases.telemetry.ts new file mode 100644 index 00000000000..35b678ed4de --- /dev/null +++ b/packages/sanity/src/core/bundles/__telemetry__/releases.telemetry.ts @@ -0,0 +1,84 @@ +import {defineEvent} from '@sanity/telemetry' + +interface VersionInfo { + /** + * document type that was added + */ + schemaType: string + + /** + * the origin of the version created (from a draft or from a version) + */ + documentOrigin: 'draft' | 'version' +} + +export interface OriginInfo { + /** + * determines where the release was created, either from the structure view or the release plugin + */ + origin: 'structure' | 'release-plugin' +} + +/** + * When a document (version) is successfully added to a release + * @internal + */ +export const AddedVersion = defineEvent({ + name: 'Add version of document to release', + version: 1, + description: 'User added a document to a release', +}) + +/** When a release is successfully created + * @internal + */ +export const CreatedRelease = defineEvent({ + name: 'Create release', + version: 1, + description: 'User created a release', +}) + +/** When a release is successfully updated + * @internal + */ +export const UpdatedRelease = defineEvent({ + name: 'Update release', + version: 1, + description: 'User updated a release', +}) + +/** When a release is successfully deleted + * @internal + */ +export const DeletedRelease = defineEvent({ + name: 'Delete release', + version: 1, + description: 'User deleted a release', +}) + +/** When a release is successfully published + * @internal + */ +export const PublishedRelease = defineEvent({ + name: 'Publish release', + version: 1, + description: 'User published a release', +}) + +/** When a release is successfully archived + * @internal + */ +export const ArchivedRelease = defineEvent({ + name: 'Archive release', + version: 1, + description: 'User archived a release', +}) + +/** When a release is successfully unarchived + * @internal + */ +export const UnarchivedRelease = defineEvent({ + name: 'Unarchive release', + version: 1, + description: 'User unarchived a release', +}) diff --git a/packages/sanity/src/core/bundles/components/dialog/BundleDetailsDialog.tsx b/packages/sanity/src/core/bundles/components/dialog/BundleDetailsDialog.tsx index 8a350dca91f..b80964451a5 100644 --- a/packages/sanity/src/core/bundles/components/dialog/BundleDetailsDialog.tsx +++ b/packages/sanity/src/core/bundles/components/dialog/BundleDetailsDialog.tsx @@ -1,4 +1,5 @@ import {ArrowRightIcon} from '@sanity/icons' +import {useTelemetry} from '@sanity/telemetry/react' import {Box, Flex, useToast} from '@sanity/ui' import {type FormEvent, useCallback, useState} from 'react' import {type FormBundleDocument, useTranslation} from 'sanity' @@ -6,6 +7,11 @@ import {type FormBundleDocument, useTranslation} from 'sanity' import {Button, Dialog} from '../../../../ui-components' import {type BundleDocument} from '../../../store/bundles/types' import {useBundleOperations} from '../../../store/bundles/useBundleOperations' +import { + CreatedRelease, + type OriginInfo, + UpdatedRelease, +} from '../../__telemetry__/releases.telemetry' import {usePerspective} from '../../hooks/usePerspective' import {createReleaseId} from '../../util/createReleaseId' import {BundleForm} from './BundleForm' @@ -14,14 +20,16 @@ interface BundleDetailsDialogProps { onCancel: () => void onSubmit: () => void bundle?: BundleDocument + origin?: OriginInfo['origin'] } export function BundleDetailsDialog(props: BundleDetailsDialogProps): JSX.Element { - const {onCancel, onSubmit, bundle} = props + const {onCancel, onSubmit, bundle, origin} = props const toast = useToast() const {createBundle, updateBundle} = useBundleOperations() const formAction = bundle ? 'edit' : 'create' const {t} = useTranslation() + const telemetry = useTelemetry() const [value, setValue] = useState((): FormBundleDocument => { return { @@ -56,6 +64,9 @@ export function BundleDetailsDialog(props: BundleDetailsDialogProps): JSX.Elemen await submit(submitValue) if (formAction === 'create') { setPerspective(value._id) + telemetry.log(CreatedRelease, {origin}) + } else { + telemetry.log(UpdatedRelease) } } catch (err) { console.error(err) @@ -69,7 +80,7 @@ export function BundleDetailsDialog(props: BundleDetailsDialogProps): JSX.Elemen onSubmit() } }, - [value, submit, formAction, setPerspective, toast, onSubmit], + [value, submit, formAction, setPerspective, telemetry, origin, toast, onSubmit], ) const handleOnChange = useCallback((changedValue: FormBundleDocument) => { diff --git a/packages/sanity/src/core/bundles/components/panes/BundleActions.tsx b/packages/sanity/src/core/bundles/components/panes/BundleActions.tsx index 871eab3056f..280d77feb29 100644 --- a/packages/sanity/src/core/bundles/components/panes/BundleActions.tsx +++ b/packages/sanity/src/core/bundles/components/panes/BundleActions.tsx @@ -1,4 +1,5 @@ import {AddIcon, CheckmarkIcon} from '@sanity/icons' +import {useTelemetry} from '@sanity/telemetry/react' import {useToast} from '@sanity/ui' import {type ReactNode, useCallback, useState} from 'react' import {filter, firstValueFrom} from 'rxjs' @@ -6,6 +7,7 @@ import { getPublishedId, getVersionFromId, getVersionId, + isVersionId, useDocumentOperation, useDocumentStore, useTranslation, @@ -13,6 +15,7 @@ import { import {Button} from '../../../../ui-components' import {type BundleDocument} from '../../../store/bundles/types' +import {AddedVersion} from '../../__telemetry__/releases.telemetry' interface BundleActionsProps { currentGlobalBundle: BundleDocument @@ -42,6 +45,7 @@ export function BundleActions(props: BundleActionsProps): ReactNode { const toast = useToast() const {newVersion} = useDocumentOperation(publishedId, documentType, bundleId) const {t} = useTranslation() + const telemetry = useTelemetry() const handleAddVersion = useCallback(async () => { if (!documentId) { @@ -67,7 +71,7 @@ export function BundleActions(props: BundleActionsProps): ReactNode { // set up the listener before executing const createVersionSuccess = firstValueFrom( documentStore.pair - .operationEvents(versionId, documentType) + .operationEvents(getPublishedId(documentId), documentType) .pipe(filter((e) => e.op === 'newVersion' && e.type === 'success')), ) @@ -76,7 +80,21 @@ export function BundleActions(props: BundleActionsProps): ReactNode { // only change if the version was created successfully await createVersionSuccess setIsInVersion(true) - }, [documentId, globalBundleId, documentStore.pair, documentType, newVersion, toast, title]) + + telemetry.log(AddedVersion, { + schemaType: documentType, + documentOrigin: isVersionId(documentId) ? 'version' : 'draft', + }) + }, [ + documentId, + globalBundleId, + documentStore.pair, + documentType, + newVersion, + telemetry, + toast, + title, + ]) /** TODO what should happen when you add a version if we don't have the ready button */ diff --git a/packages/sanity/src/core/releases/components/BundleMenuButton/BundleMenuButton.tsx b/packages/sanity/src/core/releases/components/BundleMenuButton/BundleMenuButton.tsx index 686c9af5912..be7e62eab4b 100644 --- a/packages/sanity/src/core/releases/components/BundleMenuButton/BundleMenuButton.tsx +++ b/packages/sanity/src/core/releases/components/BundleMenuButton/BundleMenuButton.tsx @@ -5,11 +5,17 @@ import { TrashIcon, UnarchiveIcon, } from '@sanity/icons' +import {useTelemetry} from '@sanity/telemetry/react' import {Menu, Spinner, Text, useToast} from '@sanity/ui' import {useState} from 'react' import {useRouter} from 'sanity/router' import {Button, Dialog, MenuButton, MenuItem} from '../../../../ui-components' +import { + ArchivedRelease, + DeletedRelease, + UnarchivedRelease, +} from '../../../bundles/__telemetry__/releases.telemetry' import {BundleDetailsDialog} from '../../../bundles/components/dialog/BundleDetailsDialog' import {Translate, useTranslation} from '../../../i18n' import {type BundleDocument} from '../../../store/bundles/types' @@ -33,6 +39,7 @@ export const BundleMenuButton = ({disabled, bundle, documentCount}: BundleMenuBu const bundleMenuDisabled = !bundle || disabled const toast = useToast() const {t} = useTranslation(releasesLocaleNamespace) + const telemetry = useTelemetry() const resetSelectedAction = () => setSelectedAction(undefined) @@ -41,6 +48,7 @@ export const BundleMenuButton = ({disabled, bundle, documentCount}: BundleMenuBu try { setDiscardStatus('discarding') await deleteBundle(bundle) + telemetry.log(DeletedRelease) toast.push({ closable: true, status: 'success', @@ -74,6 +82,14 @@ export const BundleMenuButton = ({disabled, bundle, documentCount}: BundleMenuBu ...bundle, archivedAt: isBundleArchived ? undefined : new Date().toISOString(), }) + + if (isBundleArchived) { + // it's in the process of becoming false, so the event we want to track is unarchive + telemetry.log(UnarchivedRelease) + } else { + // it's in the process of becoming true, so the event we want to track is archive + telemetry.log(ArchivedRelease) + } setIsPerformingOperation(false) } diff --git a/packages/sanity/src/core/releases/components/ReleasePublishAllButton/ReleasePublishAllButton.tsx b/packages/sanity/src/core/releases/components/ReleasePublishAllButton/ReleasePublishAllButton.tsx index 2f37aebf6bb..b9f65f16c2f 100644 --- a/packages/sanity/src/core/releases/components/ReleasePublishAllButton/ReleasePublishAllButton.tsx +++ b/packages/sanity/src/core/releases/components/ReleasePublishAllButton/ReleasePublishAllButton.tsx @@ -1,9 +1,11 @@ import {ErrorOutlineIcon, PublishIcon} from '@sanity/icons' +import {useTelemetry} from '@sanity/telemetry/react' import {Flex, Text, useToast} from '@sanity/ui' import {useCallback, useMemo, useState} from 'react' import {type BundleDocument} from 'sanity' import {Button, Dialog} from '../../../../ui-components' +import {PublishedRelease} from '../../../bundles/__telemetry__/releases.telemetry' import {Translate, useTranslation} from '../../../i18n' import {useBundleOperations} from '../../../store/bundles/useBundleOperations' import {releasesLocaleNamespace} from '../../i18n' @@ -24,6 +26,7 @@ export const ReleasePublishAllButton = ({ const toast = useToast() const {publishBundle} = useBundleOperations() const {t} = useTranslation(releasesLocaleNamespace) + const telemetry = useTelemetry() const [publishBundleStatus, setPublishBundleStatus] = useState<'idle' | 'confirm' | 'publishing'>( 'idle', ) @@ -47,6 +50,7 @@ export const ReleasePublishAllButton = ({ bundleDocuments.map(({document}) => document), publishedDocumentsRevisions, ) + telemetry.log(PublishedRelease) toast.push({ closable: true, status: 'success', @@ -69,7 +73,7 @@ export const ReleasePublishAllButton = ({ } finally { setPublishBundleStatus('idle') } - }, [bundle, bundleDocuments, publishBundle, publishedDocumentsRevisions, t, toast]) + }, [bundle, bundleDocuments, publishBundle, publishedDocumentsRevisions, t, telemetry, toast]) const confirmPublishDialog = useMemo(() => { if (publishBundleStatus === 'idle') return null diff --git a/packages/sanity/src/core/releases/tool/overview/ReleasesOverview.tsx b/packages/sanity/src/core/releases/tool/overview/ReleasesOverview.tsx index 05a91d52344..670a7270190 100644 --- a/packages/sanity/src/core/releases/tool/overview/ReleasesOverview.tsx +++ b/packages/sanity/src/core/releases/tool/overview/ReleasesOverview.tsx @@ -147,6 +147,7 @@ export function ReleasesOverview() { setIsCreateBundleDialogOpen(false)} onSubmit={() => setIsCreateBundleDialogOpen(false)} + origin="release-plugin" /> ) } diff --git a/packages/sanity/src/core/studio/components/navbar/perspective/GlobalPerspectiveMenu.tsx b/packages/sanity/src/core/studio/components/navbar/perspective/GlobalPerspectiveMenu.tsx index 8ee36b4335b..04b1b88e138 100644 --- a/packages/sanity/src/core/studio/components/navbar/perspective/GlobalPerspectiveMenu.tsx +++ b/packages/sanity/src/core/studio/components/navbar/perspective/GlobalPerspectiveMenu.tsx @@ -60,7 +60,7 @@ export function GlobalPerspectiveMenu(): JSX.Element { /> {createBundleDialogOpen && ( - + )} ) diff --git a/packages/sanity/src/structure/panes/document/DocumentPaneProvider.tsx b/packages/sanity/src/structure/panes/document/DocumentPaneProvider.tsx index d802c201923..c00dde3adfb 100644 --- a/packages/sanity/src/structure/panes/document/DocumentPaneProvider.tsx +++ b/packages/sanity/src/structure/panes/document/DocumentPaneProvider.tsx @@ -50,7 +50,7 @@ import {usePaneRouter} from '../../components' import {structureLocaleNamespace} from '../../i18n' import {type PaneMenuItem} from '../../types' import {useStructureTool} from '../../useStructureTool' -import {DocumentURLCopied} from './__telemetry__' +import {CreatedDraft, DocumentURLCopied} from './__telemetry__' import { DEFAULT_MENU_ITEM_GROUPS, EMPTY_PARAMS, @@ -328,6 +328,10 @@ export const DocumentPaneProvider = memo((props: DocumentPaneProviderProps) => { }) patchRef.current = (event: PatchEvent) => { + // when creating a new draft + if (!editState.draft && !editState.published && !editState.version) { + telemetry.log(CreatedDraft) + } patch.execute(toMutationPatches(event.patches), initialValue.value) } diff --git a/packages/sanity/src/structure/panes/document/__telemetry__/documentPanes.telemetry.ts b/packages/sanity/src/structure/panes/document/__telemetry__/documentPanes.telemetry.ts index b2b83cc66c1..5718de4f404 100644 --- a/packages/sanity/src/structure/panes/document/__telemetry__/documentPanes.telemetry.ts +++ b/packages/sanity/src/structure/panes/document/__telemetry__/documentPanes.telemetry.ts @@ -8,3 +8,13 @@ export const DocumentURLCopied = defineEvent({ version: 1, description: 'User copied document URL to clipboard', }) + +/** + * When a draft is successfully created + * @internal + */ +export const CreatedDraft = defineEvent({ + name: 'Create a new draft', + version: 1, + description: 'User created a new draft', +})