UNPKG

@atlaskit/editor-common

Version:

A package that contains common classes and components for editor and renderer

261 lines (252 loc) • 11 kB
import _classCallCheck from "@babel/runtime/helpers/classCallCheck"; import _createClass from "@babel/runtime/helpers/createClass"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '../analytics'; import { DEFAULT_EXPERIENCE_SAMPLE_RATE, EXPERIENCE_ABORT_REASON } from './consts'; import { canTransition } from './experience-state'; import { ExperienceCheckComposite } from './ExperienceCheckComposite'; export var Experience = /*#__PURE__*/function () { /** * Indicates whether sampled tracking is enabled for this current experience session. * * Set to true | false upon transitioning to 'started' state. * When true, on subsequent transitions we fire experienceSampled events. * Ensures that every tracked start has corresponding abort/fail/success tracked. */ /** * Timestamp (in milliseconds) when the experience transitioned to 'started' state. * * Used to calculate experience duration. Set via Date.now() when experience starts. */ /** * Metadata provided at experience start time to merge into subsequent events. * * Used to retain experienceStartMethod and other start-specific metadata. */ /** * Creates a new Experience instance for tracking user experiences. * * @param id - Unique identifier for the experience e.g. 'toolbarOpen' 'menuAction' * @param options - Configuration options for the experience * @param options.checks - Experience checks to monitor for completion * @param options.dispatchAnalyticsEvent - Function to dispatch analytics events * @param options.sampleRate - Sample rate for experienceSampled events * @param options.metadata - Global metadata to attach to all events * @param options.action - Optional sub identifier for the specific experience action e.g. 'bold' 'insertTable' * @param options.actionSubjectId - Optional sub identifier for the experience action subject e.g. 'selectionToolbar' 'quickInsert' */ function Experience(id, options) { var _options$sampleRate; _classCallCheck(this, Experience); _defineProperty(this, "currentState", 'pending'); /** * Set of experience states that have been seen in the current session. * * Used to determine if experienceStatusFirstSeen flag should be set in events. */ _defineProperty(this, "statesSeen", new Set()); this.id = id; this.actionSubjectId = options.actionSubjectId; this.dispatchAnalyticsEvent = options.dispatchAnalyticsEvent; this.sampleRate = (_options$sampleRate = options.sampleRate) !== null && _options$sampleRate !== void 0 ? _options$sampleRate : DEFAULT_EXPERIENCE_SAMPLE_RATE; this.globalMetadata = _objectSpread(_objectSpread({}, options.metadata), {}, { experienceAction: options.action }); this.check = new ExperienceCheckComposite(options.checks || []); } return _createClass(Experience, [{ key: "startCheck", value: function startCheck() { var _this = this; this.stopCheck(); this.check.start(function (_ref) { var status = _ref.status, reason = _ref.reason, metadata = _ref.metadata; if (status === 'success') { _this.success({ reason: reason, metadata: metadata }); } else if (status === 'abort') { _this.abort({ reason: reason, metadata: metadata }); } else if (status === 'failure') { _this.failure({ reason: reason, metadata: metadata }); } }); } }, { key: "stopCheck", value: function stopCheck() { this.check.stop(); } }, { key: "getEndStateMetadata", value: function getEndStateMetadata() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var metadata = _objectSpread({}, options.metadata); if (options.reason) { metadata.experienceEndReason = options.reason; } return metadata; } /** * Transitions to a new experience state and tracks analytics events. * * Upon transition to each state, two events are tracked: * - experienceMeasured: tracked on every successful transition, used for data analysis * - experienceSampled: tracked only on 1 out of every N transitions based on sample rate * * @param toState - The target state to transition to * @param metadata - Optional metadata to attach to the analytics events * @returns true if transition was successful, false if invalid transition */ }, { key: "transitionTo", value: function transitionTo(toState, metadata) { if (!canTransition(this.currentState, toState)) { return false; } this.currentState = toState; if (toState === 'started') { this.isSampledTrackingEnabled = Math.random() < this.sampleRate; this.startTimeMs = Date.now(); this.startMetadata = metadata; } this.trackTransition(toState, metadata); if (toState === 'started') { this.startCheck(); } else { this.stopCheck(); this.startMetadata = undefined; } this.statesSeen.add(toState); return true; } /** * Tracks analytics events for a state transition. * * Fires both experienceMeasured (always) and experienceSampled (sampled) events. * * @param toState - The state that was transitioned to * @param metadata - Metadata to include in the event, including firstInSession flag */ }, { key: "trackTransition", value: function trackTransition(toState, metadata) { var attributes = _objectSpread(_objectSpread(_objectSpread({ experienceKey: this.id, experienceStatus: toState, experienceStatusFirstSeen: !this.statesSeen.has(toState), experienceStartTime: this.startTimeMs || 0, experienceDuration: this.startTimeMs ? Date.now() - this.startTimeMs : 0 }, this.globalMetadata), this.startMetadata), metadata); var experienceMeasuredEvent = { action: ACTION.EXPERIENCE_MEASURED, actionSubject: ACTION_SUBJECT.EDITOR, actionSubjectId: this.actionSubjectId, eventType: EVENT_TYPE.OPERATIONAL, attributes: attributes }; this.dispatchAnalyticsEvent(experienceMeasuredEvent); if (this.isSampledTrackingEnabled) { var experienceSampledEvent = { action: ACTION.EXPERIENCE_SAMPLED, actionSubject: ACTION_SUBJECT.EDITOR, actionSubjectId: this.actionSubjectId, eventType: EVENT_TYPE.OPERATIONAL, attributes: attributes }; this.dispatchAnalyticsEvent(experienceSampledEvent); } } /** * Starts tracking the experience and all checks which monitor for completion. * * Metadata from options will be merged with metadata provided in subsequent events. * * @param options - Configuration for starting the experience * @param options.metadata - Optional custom metadata attached to all subsequent events for this started experience * @param options.forceRestart - If true and experience already in progress will abort and restart * @param options.method - Optional method for experience start, e.g., how the experience was initiated */ }, { key: "start", value: function start() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; if (options.forceRestart && this.currentState === 'started') { this.abort({ reason: EXPERIENCE_ABORT_REASON.RESTARTED }); } return this.transitionTo('started', _objectSpread({ experienceStartMethod: options.method }, options.metadata)); } /** * Marks the experience as successful and stops any ongoing checks. * * @param options - Configuration for the success event * @param options.metadata - Optional custom metadata attached to the success event * @param options.reason - Optional reason for success * @returns false if transition to success state was not valid */ }, { key: "success", value: function success() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var metadata = this.getEndStateMetadata(options); return this.transitionTo('succeeded', metadata); } /** * Aborts the experience and stops any ongoing checks. * * Use this when a started experience terminates early due to user action or context change * (e.g., component unmount, navigation). This is neither success nor failure. * * @param options - Configuration for the abort event * @param options.metadata - Optional custom metadata attached to the abort event * @param options.reason - Optional reason for abort * * @example * // Abort on component unmount * useEffect(() => { * return () => experience.abort({ reason: 'unmount', metadata: { someKey: 'someValue' } }); * }, []); * * @returns false if transition to aborted state was not valid */ }, { key: "abort", value: function abort() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var metadata = this.getEndStateMetadata(options); return this.transitionTo('aborted', metadata); } /** * Manually marks the experience as failed and stops any ongoing checks. * * Use this for actual failures in the experience flow (e.g., timeout, error conditions). * * @param options - Configuration for the failure event * @param options.metadata - Optional custom metadata attached to the failure event * @param options.reason - Optional reason for failure * @returns false if transition to failed state was not valid */ }, { key: "failure", value: function failure() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var metadata = this.getEndStateMetadata(options); return this.transitionTo('failed', metadata); } }]); }();