@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
261 lines (252 loc) • 11 kB
JavaScript
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);
}
}]);
}();