mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-19 15:26:59 +08:00
feat(editor): embed iframe block event tracking (#11313)
Close [BS-2958](https://linear.app/affine-design/issue/BS-2958/埋点相关)
This commit is contained in:
@@ -90,15 +90,15 @@ export const insertEmbedIframeWithUrlCommand: Command<
|
||||
surfaceBlock.model
|
||||
);
|
||||
|
||||
gfx.selection.set({
|
||||
elements: [newBlockId],
|
||||
editing: false,
|
||||
});
|
||||
|
||||
gfx.tool.setTool(
|
||||
// @ts-expect-error FIXME: resolve after gfx tool refactor
|
||||
'default'
|
||||
);
|
||||
|
||||
gfx.selection.set({
|
||||
elements: [newBlockId],
|
||||
editing: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (!newBlockId) {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { createLitPortal } from '@blocksuite/affine-components/portal';
|
||||
import type { EmbedIframeBlockModel } from '@blocksuite/affine-model';
|
||||
import {
|
||||
DocModeProvider,
|
||||
TelemetryProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { WithDisposable } from '@blocksuite/global/lit';
|
||||
import { EditIcon, InformationIcon, ResetIcon } from '@blocksuite/icons/lit';
|
||||
@@ -191,6 +195,7 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) {
|
||||
.model=${this.model}
|
||||
.abortController=${this._editAbortController}
|
||||
.std=${this.std}
|
||||
.inSurface=${this.inSurface}
|
||||
></embed-iframe-link-edit-popup>`,
|
||||
portalStyles: {
|
||||
zIndex: 'var(--affine-z-index-popover)',
|
||||
@@ -210,6 +215,15 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) {
|
||||
private readonly _handleRetry = (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
this.onRetry();
|
||||
|
||||
// track retry event
|
||||
this.telemetryService?.track('ReloadLink', {
|
||||
type: 'embed iframe block',
|
||||
page: this.editorMode === 'page' ? 'doc editor' : 'whiteboard editor',
|
||||
segment: 'editor',
|
||||
module: 'embed block',
|
||||
control: 'reload button',
|
||||
});
|
||||
};
|
||||
|
||||
override render() {
|
||||
@@ -273,6 +287,16 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) {
|
||||
return this.model.doc.readonly;
|
||||
}
|
||||
|
||||
get telemetryService() {
|
||||
return this.std.getOptional(TelemetryProvider);
|
||||
}
|
||||
|
||||
get editorMode() {
|
||||
const docModeService = this.std.get(DocModeProvider);
|
||||
const mode = docModeService.getEditorMode();
|
||||
return mode ?? 'page';
|
||||
}
|
||||
|
||||
@query('.button.edit')
|
||||
accessor _editButton: HTMLElement | null = null;
|
||||
|
||||
@@ -288,6 +312,9 @@ export class EmbedIframeErrorCard extends WithDisposable(LitElement) {
|
||||
@property({ attribute: false })
|
||||
accessor std!: BlockStdScope;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor inSurface = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor options: EmbedIframeStatusCardOptions = {
|
||||
layout: 'horizontal',
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
DocModeProvider,
|
||||
TelemetryProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { SignalWatcher } from '@blocksuite/global/lit';
|
||||
import { DoneIcon } from '@blocksuite/icons/lit';
|
||||
@@ -67,6 +71,17 @@ export class EmbedIframeLinkEditPopup extends SignalWatcher(
|
||||
}
|
||||
`;
|
||||
|
||||
protected override track(status: 'success' | 'failure') {
|
||||
this.telemetryService?.track('EditLink', {
|
||||
type: 'embed iframe block',
|
||||
page: this.editorMode === 'page' ? 'doc editor' : 'whiteboard editor',
|
||||
segment: 'editor',
|
||||
module: 'embed block',
|
||||
control: 'edit button',
|
||||
other: status,
|
||||
});
|
||||
}
|
||||
|
||||
override render() {
|
||||
const isInputEmpty = this.isInputEmpty();
|
||||
const { url$ } = this.model.props;
|
||||
@@ -94,4 +109,14 @@ export class EmbedIframeLinkEditPopup extends SignalWatcher(
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
get telemetryService() {
|
||||
return this.std.getOptional(TelemetryProvider);
|
||||
}
|
||||
|
||||
get editorMode() {
|
||||
const docModeService = this.std.get(DocModeProvider);
|
||||
const mode = docModeService.getEditorMode();
|
||||
return mode ?? 'page';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,22 @@ import {
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { isValidUrl, stopPropagation } from '@blocksuite/affine-shared/utils';
|
||||
import { WithDisposable } from '@blocksuite/global/lit';
|
||||
import { BlockSelection, type BlockStdScope } from '@blocksuite/std';
|
||||
import { noop } from '@blocksuite/global/utils';
|
||||
import {
|
||||
BlockSelection,
|
||||
type BlockStdScope,
|
||||
SurfaceSelection,
|
||||
} from '@blocksuite/std';
|
||||
import { LitElement } from 'lit';
|
||||
import { property, query, state } from 'lit/decorators.js';
|
||||
|
||||
export class EmbedIframeLinkInputBase extends WithDisposable(LitElement) {
|
||||
// this method is used to track the event when the user inputs the link
|
||||
// it should be overridden by the subclass
|
||||
protected track(status: 'success' | 'failure') {
|
||||
noop(status);
|
||||
}
|
||||
|
||||
protected isInputEmpty() {
|
||||
return this._linkInputValue.trim() === '';
|
||||
}
|
||||
@@ -33,9 +44,20 @@ export class EmbedIframeLinkInputBase extends WithDisposable(LitElement) {
|
||||
this.store.transact(() => {
|
||||
const blockId = this.store.addBlock(flavour, { url }, parent, index);
|
||||
this.store.deleteBlock(model);
|
||||
this.std.selection.setGroup('note', [
|
||||
this.std.selection.create(BlockSelection, { blockId }),
|
||||
]);
|
||||
if (this.inSurface) {
|
||||
this.std.selection.setGroup('gfx', [
|
||||
this.std.selection.create(
|
||||
SurfaceSelection,
|
||||
blockId,
|
||||
[blockId],
|
||||
false
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
this.std.selection.setGroup('note', [
|
||||
this.std.selection.create(BlockSelection, { blockId }),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
this.abortController?.abort();
|
||||
@@ -50,6 +72,7 @@ export class EmbedIframeLinkInputBase extends WithDisposable(LitElement) {
|
||||
const embedIframeService = this.std.get(EmbedIframeService);
|
||||
if (!embedIframeService) {
|
||||
console.error('iframe EmbedIframeService not found');
|
||||
this.track('failure');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -68,7 +91,9 @@ export class EmbedIframeLinkInputBase extends WithDisposable(LitElement) {
|
||||
title: '',
|
||||
description: '',
|
||||
});
|
||||
this.track('success');
|
||||
} catch (error) {
|
||||
this.track('failure');
|
||||
this.notificationService?.notify({
|
||||
title: 'Error in embed iframe creation',
|
||||
message: error instanceof Error ? error.message : 'Please try again',
|
||||
@@ -129,4 +154,7 @@ export class EmbedIframeLinkInputBase extends WithDisposable(LitElement) {
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor abortController: AbortController | undefined = undefined;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor inSurface = false;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
DocModeProvider,
|
||||
TelemetryProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { unsafeCSSVar, unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||
import { CloseIcon } from '@blocksuite/icons/lit';
|
||||
import { baseTheme } from '@toeverything/theme';
|
||||
@@ -16,6 +20,7 @@ export type EmbedLinkInputPopupOptions = {
|
||||
title?: string;
|
||||
description?: string;
|
||||
placeholder?: string;
|
||||
telemetrySegment?: string;
|
||||
};
|
||||
|
||||
const DEFAULT_OPTIONS: EmbedLinkInputPopupOptions = {
|
||||
@@ -24,6 +29,7 @@ const DEFAULT_OPTIONS: EmbedLinkInputPopupOptions = {
|
||||
title: 'Embed Link',
|
||||
description: 'Works with links of Google Drive, Spotify…',
|
||||
placeholder: 'Paste the Embed link...',
|
||||
telemetrySegment: 'editor',
|
||||
};
|
||||
|
||||
export class EmbedIframeLinkInputPopup extends EmbedIframeLinkInputBase {
|
||||
@@ -216,6 +222,17 @@ export class EmbedIframeLinkInputPopup extends EmbedIframeLinkInputBase {
|
||||
this.abortController?.abort();
|
||||
};
|
||||
|
||||
protected override track(status: 'success' | 'failure') {
|
||||
this.telemetryService?.track('CreateEmbedBlock', {
|
||||
type: 'embed iframe block',
|
||||
page: this.editorMode === 'page' ? 'doc editor' : 'whiteboard editor',
|
||||
segment: this.options?.telemetrySegment ?? 'editor',
|
||||
module: 'embed block',
|
||||
control: 'confirm embed link',
|
||||
other: status,
|
||||
});
|
||||
}
|
||||
|
||||
override render() {
|
||||
const options = { ...DEFAULT_OPTIONS, ...this.options };
|
||||
const { showCloseButton, variant, title, description, placeholder } =
|
||||
@@ -261,6 +278,16 @@ export class EmbedIframeLinkInputPopup extends EmbedIframeLinkInputBase {
|
||||
`;
|
||||
}
|
||||
|
||||
get telemetryService() {
|
||||
return this.std.getOptional(TelemetryProvider);
|
||||
}
|
||||
|
||||
get editorMode() {
|
||||
const docModeService = this.std.get(DocModeProvider);
|
||||
const mode = docModeService.getEditorMode();
|
||||
return mode ?? 'page';
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor options: EmbedLinkInputPopupOptions | undefined = undefined;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@ export const embedIframeSlashMenuConfig: SlashMenuConfig = {
|
||||
.pipe(insertEmptyEmbedIframeCommand, {
|
||||
place: 'after',
|
||||
removeEmptyLine: true,
|
||||
linkInputPopupOptions: {
|
||||
telemetrySegment: 'slash menu',
|
||||
},
|
||||
})
|
||||
.run();
|
||||
},
|
||||
|
||||
@@ -67,6 +67,11 @@ const openLinkAction = (id: string): ToolbarAction => {
|
||||
run(ctx) {
|
||||
const component = ctx.getCurrentBlockByType(EmbedIframeBlockComponent);
|
||||
component?.open();
|
||||
|
||||
ctx.track('OpenLink', {
|
||||
...trackBaseProps,
|
||||
control: 'open original link',
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -263,6 +268,11 @@ export const builtinToolbarConfig = {
|
||||
.copySlice(slice)
|
||||
.then(() => toast(ctx.host, 'Copied to clipboard'))
|
||||
.catch(console.error);
|
||||
|
||||
ctx.track('CopiedLink', {
|
||||
...trackBaseProps,
|
||||
control: 'copy link',
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -290,6 +300,11 @@ export const builtinToolbarConfig = {
|
||||
run(ctx) {
|
||||
const component = ctx.getCurrentBlockByType(EmbedIframeBlockComponent);
|
||||
component?.refreshData().catch(console.error);
|
||||
|
||||
ctx.track('ReloadLink', {
|
||||
...trackBaseProps,
|
||||
control: 'reload link',
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -225,6 +225,7 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
|
||||
.model=${this.model}
|
||||
.abortController=${this._linkInputAbortController}
|
||||
.std=${this.std}
|
||||
.inSurface=${this.inSurface}
|
||||
.options=${options}
|
||||
></embed-iframe-link-input-popup>`,
|
||||
portalStyles: {
|
||||
@@ -347,6 +348,7 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
|
||||
.model=${this.model}
|
||||
.onRetry=${this._handleRetry}
|
||||
.std=${this.std}
|
||||
.inSurface=${this.inSurface}
|
||||
.options=${this._statusCardOptions}
|
||||
></embed-iframe-error-card>`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user