chore(core): tracking events for doc properties (#8651)

fix AF-1565
This commit is contained in:
pengx17
2024-11-05 11:30:57 +00:00
parent 4977055a2e
commit ef82b9d3e7
18 changed files with 301 additions and 116 deletions
@@ -3,11 +3,16 @@ import {
useConfirmModal,
useLitPortalFactory,
} from '@affine/component';
import type {
DatabaseRow,
DatabaseValueCell,
} from '@affine/core/modules/doc-info/types';
import { EditorService } from '@affine/core/modules/editor';
import { EditorSettingService } from '@affine/core/modules/editor-setting';
import { JournalService } from '@affine/core/modules/journal';
import { toURLSearchParams } from '@affine/core/modules/navigation';
import { PeekViewService } from '@affine/core/modules/peek-view/services/peek-view';
import track from '@affine/track';
import type { DocMode } from '@blocksuite/affine/blocks';
import {
DocTitle,
@@ -16,6 +21,7 @@ import {
} from '@blocksuite/affine/presets';
import type { Doc } from '@blocksuite/affine/store';
import {
type DocCustomPropertyInfo,
DocService,
DocsService,
FeatureFlagService,
@@ -239,6 +245,28 @@ export const BlocksuiteDocEditor = forwardRef<
editorSettingService.editorSetting.settings$.selector(s => s.displayDocInfo)
);
const onPropertyChange = useCallback((property: DocCustomPropertyInfo) => {
track.doc.inlineDocInfo.property.editProperty({
type: property.type,
});
}, []);
const onPropertyAdded = useCallback((property: DocCustomPropertyInfo) => {
track.doc.inlineDocInfo.property.addProperty({
type: property.type,
control: 'at menu',
});
}, []);
const onDatabasePropertyChange = useCallback(
(_row: DatabaseRow, cell: DatabaseValueCell) => {
track.doc.inlineDocInfo.databaseProperty.editProperty({
type: cell.property.type$.value,
});
},
[]
);
return (
<>
<div className={styles.affineDocViewport} style={{ height: '100%' }}>
@@ -248,7 +276,12 @@ export const BlocksuiteDocEditor = forwardRef<
<BlocksuiteEditorJournalDocTitle page={page} />
)}
{!shared && displayDocInfo ? (
<DocPropertiesTable defaultOpenProperty={defaultOpenProperty} />
<DocPropertiesTable
onDatabasePropertyChange={onDatabasePropertyChange}
onPropertyChange={onPropertyChange}
onPropertyAdded={onPropertyAdded}
defaultOpenProperty={defaultOpenProperty}
/>
) : null}
<adapted.DocEditor
className={styles.docContainer}
@@ -1,7 +1,12 @@
import { MenuItem, MenuSeparator } from '@affine/component';
import { generateUniqueNameInSequence } from '@affine/core/utils/unique-name';
import { useI18n } from '@affine/i18n';
import { DocsService, useLiveData, useService } from '@toeverything/infra';
import {
type DocCustomPropertyInfo,
DocsService,
useLiveData,
useService,
} from '@toeverything/infra';
import { useCallback } from 'react';
import {
@@ -15,7 +20,7 @@ export const CreatePropertyMenuItems = ({
onCreated,
}: {
at?: 'before' | 'after';
onCreated?: (propertyId: string) => void;
onCreated?: (property: DocCustomPropertyInfo) => void;
}) => {
const t = useI18n();
const docsService = useService(DocsService);
@@ -36,13 +41,13 @@ export const CreatePropertyMenuItems = ({
? generateUniqueNameInSequence(option.name, allNames)
: option.name;
const uniqueId = typeDefined.uniqueId;
const newPropertyId = propertyList.createProperty({
const newProperty = propertyList.createProperty({
id: uniqueId,
name,
type: option.type,
index: propertyList.indexAt(at),
});
onCreated?.(newPropertyId);
onCreated?.(newProperty);
},
[at, onCreated, propertyList, properties]
);
@@ -1,6 +1,7 @@
import { Divider, IconButton, Tooltip } from '@affine/component';
import { generateUniqueNameInSequence } from '@affine/core/utils/unique-name';
import { useI18n } from '@affine/i18n';
import track from '@affine/track';
import { PlusIcon } from '@blocksuite/icons/rc';
import {
Content as CollapsibleContent,
@@ -41,13 +42,17 @@ export const DocPropertySidebar = () => {
const name = nameExists
? generateUniqueNameInSequence(option.name, allNames)
: option.name;
const newPropertyId = propertyList.createProperty({
const newProperty = propertyList.createProperty({
id: typeDefined.uniqueId,
name,
type: option.type,
index: propertyList.indexAt('after'),
});
setNewPropertyId(newPropertyId);
setNewPropertyId(newProperty.id);
track.doc.sidepanel.property.addProperty({
control: 'property list',
type: option.type,
});
},
[propertyList, properties]
);
@@ -9,6 +9,10 @@ import {
useDropTarget,
} from '@affine/component';
import { DocDatabaseBacklinkInfo } from '@affine/core/modules/doc-info';
import type {
DatabaseRow,
DatabaseValueCell,
} from '@affine/core/modules/doc-info/types';
import { WorkbenchService } from '@affine/core/modules/workbench';
import { ViewService } from '@affine/core/modules/workbench/services/view';
import type { AffineDNDData } from '@affine/core/types/dnd';
@@ -26,7 +30,6 @@ import {
} from '@toeverything/infra';
import clsx from 'clsx';
import type React from 'react';
import type { HTMLProps } from 'react';
import { forwardRef, useCallback, useState } from 'react';
import { DocPropertyIcon } from './icons/doc-property-icon';
@@ -47,6 +50,13 @@ export type DefaultOpenProperty =
export interface DocPropertiesTableProps {
defaultOpenProperty?: DefaultOpenProperty;
onPropertyAdded?: (property: DocCustomPropertyInfo) => void;
onPropertyChange?: (property: DocCustomPropertyInfo, value: unknown) => void;
onDatabasePropertyChange?: (
row: DatabaseRow,
cell: DatabaseValueCell,
value: unknown
) => void;
}
interface DocPropertiesTableHeaderProps {
@@ -95,11 +105,13 @@ interface DocPropertyRowProps {
propertyInfo: DocCustomPropertyInfo;
showAll?: boolean;
defaultOpenEditMenu?: boolean;
onChange?: (value: unknown) => void;
}
export const DocPropertyRow = ({
propertyInfo,
defaultOpenEditMenu,
onChange,
}: DocPropertyRowProps) => {
const t = useI18n();
const docService = useService(DocService);
@@ -123,8 +135,9 @@ export const DocPropertyRow = ({
throw new Error('only allow string value');
}
docService.doc.record.setCustomProperty(propertyInfo.id, value);
onChange?.(value);
},
[docService, propertyInfo]
[docService, onChange, propertyInfo]
);
const docId = docService.doc.id;
@@ -214,6 +227,8 @@ interface DocWorkspacePropertiesTableBodyProps {
className?: string;
style?: React.CSSProperties;
defaultOpen?: boolean;
onChange?: (property: DocCustomPropertyInfo, value: unknown) => void;
onPropertyAdded?: (property: DocCustomPropertyInfo) => void;
}
// 🏷️ Tags (⋅ xxx) (⋅ yyy)
@@ -221,104 +236,121 @@ interface DocWorkspacePropertiesTableBodyProps {
// + Add a property
const DocWorkspacePropertiesTableBody = forwardRef<
HTMLDivElement,
DocWorkspacePropertiesTableBodyProps & HTMLProps<HTMLDivElement>
>(({ className, style, defaultOpen, ...props }, ref) => {
const t = useI18n();
const docsService = useService(DocsService);
const workbenchService = useService(WorkbenchService);
const viewService = useServiceOptional(ViewService);
const properties = useLiveData(docsService.propertyList.sortedProperties$);
const [propertyCollapsed, setPropertyCollapsed] = useState(true);
DocWorkspacePropertiesTableBodyProps
>(
(
{ className, style, defaultOpen, onChange, onPropertyAdded, ...props },
ref
) => {
const t = useI18n();
const docsService = useService(DocsService);
const workbenchService = useService(WorkbenchService);
const viewService = useServiceOptional(ViewService);
const properties = useLiveData(docsService.propertyList.sortedProperties$);
const [propertyCollapsed, setPropertyCollapsed] = useState(true);
const [newPropertyId, setNewPropertyId] = useState<string | null>(null);
const [newPropertyId, setNewPropertyId] = useState<string | null>(null);
return (
<PropertyCollapsibleSection
ref={ref}
className={clsx(styles.tableBodyRoot, className)}
style={style}
title={t.t('com.affine.workspace.properties')}
defaultCollapsed={!defaultOpen}
{...props}
>
<PropertyCollapsibleContent
collapsible
collapsed={propertyCollapsed}
onCollapseChange={setPropertyCollapsed}
className={styles.tableBodySortable}
collapseButtonText={({ hide, isCollapsed }) =>
isCollapsed
? hide === 1
? t['com.affine.page-properties.more-property.one']({
count: hide.toString(),
})
: t['com.affine.page-properties.more-property.more']({
count: hide.toString(),
})
: hide === 1
? t['com.affine.page-properties.hide-property.one']({
count: hide.toString(),
})
: t['com.affine.page-properties.hide-property.more']({
count: hide.toString(),
})
}
const handlePropertyAdded = useCallback(
(property: DocCustomPropertyInfo) => {
setNewPropertyId(property.id);
onPropertyAdded?.(property);
},
[onPropertyAdded]
);
return (
<PropertyCollapsibleSection
ref={ref}
className={clsx(styles.tableBodyRoot, className)}
style={style}
title={t.t('com.affine.workspace.properties')}
defaultCollapsed={!defaultOpen}
{...props}
>
{properties.map(property => (
<DocPropertyRow
key={property.id}
propertyInfo={property}
defaultOpenEditMenu={newPropertyId === property.id}
/>
))}
<div className={styles.actionContainer}>
<Menu
items={
<CreatePropertyMenuItems
at="after"
onCreated={setNewPropertyId}
/>
}
contentOptions={{
onClick(e) {
e.stopPropagation();
},
}}
>
<Button
variant="plain"
prefix={<PlusIcon />}
className={styles.propertyActionButton}
data-testid="add-property-button"
>
{t['com.affine.page-properties.add-property']()}
</Button>
</Menu>
{viewService ? (
<Button
variant="plain"
prefix={<PropertyIcon />}
className={clsx(
styles.propertyActionButton,
styles.propertyConfigButton
)}
onClick={() => {
viewService.view.activeSidebarTab('properties');
workbenchService.workbench.openSidebar();
<PropertyCollapsibleContent
collapsible
collapsed={propertyCollapsed}
onCollapseChange={setPropertyCollapsed}
className={styles.tableBodySortable}
collapseButtonText={({ hide, isCollapsed }) =>
isCollapsed
? hide === 1
? t['com.affine.page-properties.more-property.one']({
count: hide.toString(),
})
: t['com.affine.page-properties.more-property.more']({
count: hide.toString(),
})
: hide === 1
? t['com.affine.page-properties.hide-property.one']({
count: hide.toString(),
})
: t['com.affine.page-properties.hide-property.more']({
count: hide.toString(),
})
}
>
{properties.map(property => (
<DocPropertyRow
key={property.id}
propertyInfo={property}
defaultOpenEditMenu={newPropertyId === property.id}
onChange={value => onChange?.(property, value)}
/>
))}
<div className={styles.actionContainer}>
<Menu
items={
<CreatePropertyMenuItems
at="after"
onCreated={handlePropertyAdded}
/>
}
contentOptions={{
onClick(e) {
e.stopPropagation();
},
}}
>
{t['com.affine.page-properties.config-properties']()}
</Button>
) : null}
</div>
</PropertyCollapsibleContent>
</PropertyCollapsibleSection>
);
});
<Button
variant="plain"
prefix={<PlusIcon />}
className={styles.propertyActionButton}
data-testid="add-property-button"
>
{t['com.affine.page-properties.add-property']()}
</Button>
</Menu>
{viewService ? (
<Button
variant="plain"
prefix={<PropertyIcon />}
className={clsx(
styles.propertyActionButton,
styles.propertyConfigButton
)}
onClick={() => {
viewService.view.activeSidebarTab('properties');
workbenchService.workbench.openSidebar();
}}
>
{t['com.affine.page-properties.config-properties']()}
</Button>
) : null}
</div>
</PropertyCollapsibleContent>
</PropertyCollapsibleSection>
);
}
);
DocWorkspacePropertiesTableBody.displayName = 'PagePropertiesTableBody';
const DocPropertiesTableInner = ({
defaultOpenProperty,
onPropertyAdded,
onPropertyChange,
onDatabasePropertyChange,
}: DocPropertiesTableProps) => {
const [expanded, setExpanded] = useState(!!defaultOpenProperty);
return (
@@ -334,9 +366,12 @@ const DocPropertiesTableInner = ({
defaultOpen={
!defaultOpenProperty || defaultOpenProperty.type === 'workspace'
}
onPropertyAdded={onPropertyAdded}
onChange={onPropertyChange}
/>
<div className={styles.tableHeaderDivider} />
<DocDatabaseBacklinkInfo
onChange={onDatabasePropertyChange}
defaultOpen={
defaultOpenProperty?.type === 'database'
? [
@@ -356,8 +391,6 @@ const DocPropertiesTableInner = ({
// this is the main component that renders the page properties table at the top of the page below
// the page title
export const DocPropertiesTable = ({
defaultOpenProperty,
}: DocPropertiesTableProps) => {
return <DocPropertiesTableInner defaultOpenProperty={defaultOpenProperty} />;
export const DocPropertiesTable = (props: DocPropertiesTableProps) => {
return <DocPropertiesTableInner {...props} />;
};
@@ -1,6 +1,7 @@
import type { TagLike } from '@affine/component/ui/tags';
import { TagsInlineEditor as TagsInlineEditorComponent } from '@affine/component/ui/tags';
import { TagService, useDeleteTagConfirmModal } from '@affine/core/modules/tag';
import track from '@affine/track';
import {
LiveData,
useLiveData,
@@ -38,6 +39,9 @@ export const TagsInlineEditor = ({
const onCreateTag = useCallback(
(name: string, color: string) => {
const newTag = tagService.tagList.createTag(name, color);
track.doc.inlineDocInfo.property.editProperty({
type: 'tags',
});
return {
id: newTag.id,
value: newTag.value$.value,
@@ -50,6 +54,9 @@ export const TagsInlineEditor = ({
const onSelectTag = useCallback(
(tagId: string) => {
tagService.tagList.tagByTagId$(tagId).value?.tag(pageId);
track.doc.inlineDocInfo.property.editProperty({
type: 'tags',
});
},
[pageId, tagService.tagList]
);
@@ -57,6 +64,9 @@ export const TagsInlineEditor = ({
const onDeselectTag = useCallback(
(tagId: string) => {
tagService.tagList.tagByTagId$(tagId).value?.untag(pageId);
track.doc.inlineDocInfo.property.editProperty({
type: 'tags',
});
},
[pageId, tagService.tagList]
);
@@ -68,6 +78,9 @@ export const TagsInlineEditor = ({
} else if (property === 'color') {
tagService.tagList.tagByTagId$(id).value?.changeColor(value);
}
track.doc.inlineDocInfo.property.editProperty({
type: 'tags',
});
},
[tagService.tagList]
);
@@ -77,6 +90,9 @@ export const TagsInlineEditor = ({
const onTagDelete = useAsyncCallback(
async (id: string) => {
await deleteTags([id]);
track.doc.inlineDocInfo.property.editProperty({
type: 'tags',
});
},
[deleteTags]
);