@qoretechnologies/reqraft 0.10.57-pr.127.ge03fd3a → 0.10.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/CLAUDE.md +1 -1
- package/README.md +1 -1
- package/design/TRACKING.md +82 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/tracking/ui/ExperimentCard.d.ts +55 -0
- package/dist/tracking/ui/ExperimentCard.d.ts.map +1 -0
- package/dist/tracking/ui/ExperimentCard.js +396 -0
- package/dist/tracking/ui/ExperimentCard.js.map +1 -0
- package/dist/tracking/ui/ExperimentsOverlay.d.ts +59 -0
- package/dist/tracking/ui/ExperimentsOverlay.d.ts.map +1 -0
- package/dist/tracking/ui/ExperimentsOverlay.js +359 -0
- package/dist/tracking/ui/ExperimentsOverlay.js.map +1 -0
- package/dist/tracking/ui/anchor.d.ts +107 -0
- package/dist/tracking/ui/anchor.d.ts.map +1 -0
- package/dist/tracking/ui/anchor.js +108 -0
- package/dist/tracking/ui/anchor.js.map +1 -0
- package/dist/tracking/ui/card.d.ts +121 -0
- package/dist/tracking/ui/card.d.ts.map +1 -0
- package/dist/tracking/ui/card.js +417 -0
- package/dist/tracking/ui/card.js.map +1 -0
- package/dist/tracking/ui/client.d.ts +42 -0
- package/dist/tracking/ui/client.d.ts.map +1 -0
- package/dist/tracking/ui/client.js +123 -0
- package/dist/tracking/ui/client.js.map +1 -0
- package/dist/tracking/ui/index.d.ts +20 -0
- package/dist/tracking/ui/index.d.ts.map +1 -0
- package/dist/tracking/ui/index.js +60 -0
- package/dist/tracking/ui/index.js.map +1 -0
- package/dist/tracking/ui/types.d.ts +120 -0
- package/dist/tracking/ui/types.d.ts.map +1 -0
- package/dist/tracking/ui/types.js +8 -0
- package/dist/tracking/ui/types.js.map +1 -0
- package/dist/tracking/ui/useExperimentsAdmin.d.ts +31 -0
- package/dist/tracking/ui/useExperimentsAdmin.d.ts.map +1 -0
- package/dist/tracking/ui/useExperimentsAdmin.js +136 -0
- package/dist/tracking/ui/useExperimentsAdmin.js.map +1 -0
- package/package.json +3 -3
- package/src/index.tsx +3 -1
- package/src/stories/tracking.tsx +206 -0
- package/src/tracking/ui/ExperimentCard.stories.tsx +296 -0
- package/src/tracking/ui/ExperimentCard.tsx +667 -0
- package/src/tracking/ui/ExperimentsOverlay.stories.tsx +155 -0
- package/src/tracking/ui/ExperimentsOverlay.tsx +437 -0
- package/src/tracking/ui/anchor.ts +180 -0
- package/src/tracking/ui/card.ts +464 -0
- package/src/tracking/ui/client.ts +117 -0
- package/src/tracking/ui/index.ts +77 -0
- package/src/tracking/ui/types.ts +110 -0
- package/src/tracking/ui/useExperimentsAdmin.ts +103 -0
- package/src/tracking/react/Experiment.stories.tsx +0 -174
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The A/B test UI: `<ExperimentCard>` (one test, driven by props), `<ExperimentsOverlay>` (every
|
|
3
|
+
* `<Experiment>` on the page outlined, with its card anchored to it), and the optional
|
|
4
|
+
* `useExperimentsAdmin` / `createExperimentsAdminClient` for the qorus-api routes. Built on Reqore;
|
|
5
|
+
* the engine (`../index`) does not import this layer. How to embed it: design/TRACKING.md, §8.
|
|
6
|
+
*/
|
|
7
|
+
export { ExperimentCard } from './ExperimentCard';
|
|
8
|
+
export type { IExperimentCardProps } from './ExperimentCard';
|
|
9
|
+
export { boxOf, ExperimentsOverlay, OVERLAY_ATTRIBUTE } from './ExperimentsOverlay';
|
|
10
|
+
export type { IExperimentsOverlayProps } from './ExperimentsOverlay';
|
|
11
|
+
export { useExperimentsAdmin } from './useExperimentsAdmin';
|
|
12
|
+
export type { IUseExperimentsAdmin, IUseExperimentsAdminOptions } from './useExperimentsAdmin';
|
|
13
|
+
export { createExperimentsAdminClient, ExperimentsAdminError } from './client';
|
|
14
|
+
export type { IExperimentsAdminClient, IExperimentsAdminOptions, TExperimentsFetch, } from './client';
|
|
15
|
+
export { activatesRow, CARD_MAX_WIDTH, CARD_MIN_HEIGHT, cardReducer, CLOSED, openCard, OUTSIDE_IGNORE, placeCard, } from './anchor';
|
|
16
|
+
export type { IBox, ICardState, IPlaceInput, IPlacement, TCardEvent } from './anchor';
|
|
17
|
+
export { actionOutcome, barValues, canAccept, canPause, canRemove, canStart, canStop, confirmCopy, count, DEFAULT_MIN_SAMPLE, helpSections, leadingVersion, NOT_SET_UP_TEXT, sortVersionRows, UNREACHABLE_TEXT, lifecycleCopy, numbersLine, percent, phaseNote, primaryMetric, STATUS_LABEL, statusTag, verdictIntent, verdictLine, versionName, versionRows, versionsMaxHeight, VISIBLE_VERSIONS, winnerNote, } from './card';
|
|
18
|
+
export type { IConfirmCopy, IHelpSection, IVersionRow, TStatusIntent } from './card';
|
|
19
|
+
export type { IAdminExperiment, IAdminExperimentEvent, IAdminExperimentPhase, IAdminVariant, IExperimentEntry, IExperimentResults, INewExperiment, IResultsMetric, IResultsVariant, TExperimentAction, TExperimentActionResult, TExperimentStatus, TLifecycleAction, TVersionAction, } from './types';
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tracking/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACpF,YAAY,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAC/F,OAAO,EAAE,4BAA4B,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAC/E,YAAY,EACV,uBAAuB,EACvB,wBAAwB,EACxB,iBAAiB,GAClB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,YAAY,EACZ,cAAc,EACd,eAAe,EACf,WAAW,EACX,MAAM,EACN,QAAQ,EACR,cAAc,EACd,SAAS,GACV,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtF,OAAO,EACL,aAAa,EACb,SAAS,EACT,SAAS,EACT,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,OAAO,EACP,WAAW,EACX,KAAK,EACL,kBAAkB,EAClB,YAAY,EACZ,cAAc,EACd,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,OAAO,EACP,SAAS,EACT,aAAa,EACb,YAAY,EACZ,SAAS,EACT,aAAa,EACb,WAAW,EACX,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,GACX,MAAM,QAAQ,CAAC;AAChB,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACrF,YAAY,EACV,gBAAgB,EAChB,qBAAqB,EACrB,qBAAqB,EACrB,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,GACf,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.winnerNote = exports.VISIBLE_VERSIONS = exports.versionsMaxHeight = exports.versionRows = exports.versionName = exports.verdictLine = exports.verdictIntent = exports.statusTag = exports.STATUS_LABEL = exports.primaryMetric = exports.phaseNote = exports.percent = exports.numbersLine = exports.lifecycleCopy = exports.UNREACHABLE_TEXT = exports.sortVersionRows = exports.NOT_SET_UP_TEXT = exports.leadingVersion = exports.helpSections = exports.DEFAULT_MIN_SAMPLE = exports.count = exports.confirmCopy = exports.canStop = exports.canStart = exports.canRemove = exports.canPause = exports.canAccept = exports.barValues = exports.actionOutcome = exports.placeCard = exports.OUTSIDE_IGNORE = exports.openCard = exports.CLOSED = exports.cardReducer = exports.CARD_MIN_HEIGHT = exports.CARD_MAX_WIDTH = exports.activatesRow = exports.ExperimentsAdminError = exports.createExperimentsAdminClient = exports.useExperimentsAdmin = exports.OVERLAY_ATTRIBUTE = exports.ExperimentsOverlay = exports.boxOf = exports.ExperimentCard = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The A/B test UI: `<ExperimentCard>` (one test, driven by props), `<ExperimentsOverlay>` (every
|
|
6
|
+
* `<Experiment>` on the page outlined, with its card anchored to it), and the optional
|
|
7
|
+
* `useExperimentsAdmin` / `createExperimentsAdminClient` for the qorus-api routes. Built on Reqore;
|
|
8
|
+
* the engine (`../index`) does not import this layer. How to embed it: design/TRACKING.md, §8.
|
|
9
|
+
*/
|
|
10
|
+
var ExperimentCard_1 = require("./ExperimentCard");
|
|
11
|
+
Object.defineProperty(exports, "ExperimentCard", { enumerable: true, get: function () { return ExperimentCard_1.ExperimentCard; } });
|
|
12
|
+
var ExperimentsOverlay_1 = require("./ExperimentsOverlay");
|
|
13
|
+
Object.defineProperty(exports, "boxOf", { enumerable: true, get: function () { return ExperimentsOverlay_1.boxOf; } });
|
|
14
|
+
Object.defineProperty(exports, "ExperimentsOverlay", { enumerable: true, get: function () { return ExperimentsOverlay_1.ExperimentsOverlay; } });
|
|
15
|
+
Object.defineProperty(exports, "OVERLAY_ATTRIBUTE", { enumerable: true, get: function () { return ExperimentsOverlay_1.OVERLAY_ATTRIBUTE; } });
|
|
16
|
+
var useExperimentsAdmin_1 = require("./useExperimentsAdmin");
|
|
17
|
+
Object.defineProperty(exports, "useExperimentsAdmin", { enumerable: true, get: function () { return useExperimentsAdmin_1.useExperimentsAdmin; } });
|
|
18
|
+
var client_1 = require("./client");
|
|
19
|
+
Object.defineProperty(exports, "createExperimentsAdminClient", { enumerable: true, get: function () { return client_1.createExperimentsAdminClient; } });
|
|
20
|
+
Object.defineProperty(exports, "ExperimentsAdminError", { enumerable: true, get: function () { return client_1.ExperimentsAdminError; } });
|
|
21
|
+
var anchor_1 = require("./anchor");
|
|
22
|
+
Object.defineProperty(exports, "activatesRow", { enumerable: true, get: function () { return anchor_1.activatesRow; } });
|
|
23
|
+
Object.defineProperty(exports, "CARD_MAX_WIDTH", { enumerable: true, get: function () { return anchor_1.CARD_MAX_WIDTH; } });
|
|
24
|
+
Object.defineProperty(exports, "CARD_MIN_HEIGHT", { enumerable: true, get: function () { return anchor_1.CARD_MIN_HEIGHT; } });
|
|
25
|
+
Object.defineProperty(exports, "cardReducer", { enumerable: true, get: function () { return anchor_1.cardReducer; } });
|
|
26
|
+
Object.defineProperty(exports, "CLOSED", { enumerable: true, get: function () { return anchor_1.CLOSED; } });
|
|
27
|
+
Object.defineProperty(exports, "openCard", { enumerable: true, get: function () { return anchor_1.openCard; } });
|
|
28
|
+
Object.defineProperty(exports, "OUTSIDE_IGNORE", { enumerable: true, get: function () { return anchor_1.OUTSIDE_IGNORE; } });
|
|
29
|
+
Object.defineProperty(exports, "placeCard", { enumerable: true, get: function () { return anchor_1.placeCard; } });
|
|
30
|
+
var card_1 = require("./card");
|
|
31
|
+
Object.defineProperty(exports, "actionOutcome", { enumerable: true, get: function () { return card_1.actionOutcome; } });
|
|
32
|
+
Object.defineProperty(exports, "barValues", { enumerable: true, get: function () { return card_1.barValues; } });
|
|
33
|
+
Object.defineProperty(exports, "canAccept", { enumerable: true, get: function () { return card_1.canAccept; } });
|
|
34
|
+
Object.defineProperty(exports, "canPause", { enumerable: true, get: function () { return card_1.canPause; } });
|
|
35
|
+
Object.defineProperty(exports, "canRemove", { enumerable: true, get: function () { return card_1.canRemove; } });
|
|
36
|
+
Object.defineProperty(exports, "canStart", { enumerable: true, get: function () { return card_1.canStart; } });
|
|
37
|
+
Object.defineProperty(exports, "canStop", { enumerable: true, get: function () { return card_1.canStop; } });
|
|
38
|
+
Object.defineProperty(exports, "confirmCopy", { enumerable: true, get: function () { return card_1.confirmCopy; } });
|
|
39
|
+
Object.defineProperty(exports, "count", { enumerable: true, get: function () { return card_1.count; } });
|
|
40
|
+
Object.defineProperty(exports, "DEFAULT_MIN_SAMPLE", { enumerable: true, get: function () { return card_1.DEFAULT_MIN_SAMPLE; } });
|
|
41
|
+
Object.defineProperty(exports, "helpSections", { enumerable: true, get: function () { return card_1.helpSections; } });
|
|
42
|
+
Object.defineProperty(exports, "leadingVersion", { enumerable: true, get: function () { return card_1.leadingVersion; } });
|
|
43
|
+
Object.defineProperty(exports, "NOT_SET_UP_TEXT", { enumerable: true, get: function () { return card_1.NOT_SET_UP_TEXT; } });
|
|
44
|
+
Object.defineProperty(exports, "sortVersionRows", { enumerable: true, get: function () { return card_1.sortVersionRows; } });
|
|
45
|
+
Object.defineProperty(exports, "UNREACHABLE_TEXT", { enumerable: true, get: function () { return card_1.UNREACHABLE_TEXT; } });
|
|
46
|
+
Object.defineProperty(exports, "lifecycleCopy", { enumerable: true, get: function () { return card_1.lifecycleCopy; } });
|
|
47
|
+
Object.defineProperty(exports, "numbersLine", { enumerable: true, get: function () { return card_1.numbersLine; } });
|
|
48
|
+
Object.defineProperty(exports, "percent", { enumerable: true, get: function () { return card_1.percent; } });
|
|
49
|
+
Object.defineProperty(exports, "phaseNote", { enumerable: true, get: function () { return card_1.phaseNote; } });
|
|
50
|
+
Object.defineProperty(exports, "primaryMetric", { enumerable: true, get: function () { return card_1.primaryMetric; } });
|
|
51
|
+
Object.defineProperty(exports, "STATUS_LABEL", { enumerable: true, get: function () { return card_1.STATUS_LABEL; } });
|
|
52
|
+
Object.defineProperty(exports, "statusTag", { enumerable: true, get: function () { return card_1.statusTag; } });
|
|
53
|
+
Object.defineProperty(exports, "verdictIntent", { enumerable: true, get: function () { return card_1.verdictIntent; } });
|
|
54
|
+
Object.defineProperty(exports, "verdictLine", { enumerable: true, get: function () { return card_1.verdictLine; } });
|
|
55
|
+
Object.defineProperty(exports, "versionName", { enumerable: true, get: function () { return card_1.versionName; } });
|
|
56
|
+
Object.defineProperty(exports, "versionRows", { enumerable: true, get: function () { return card_1.versionRows; } });
|
|
57
|
+
Object.defineProperty(exports, "versionsMaxHeight", { enumerable: true, get: function () { return card_1.versionsMaxHeight; } });
|
|
58
|
+
Object.defineProperty(exports, "VISIBLE_VERSIONS", { enumerable: true, get: function () { return card_1.VISIBLE_VERSIONS; } });
|
|
59
|
+
Object.defineProperty(exports, "winnerNote", { enumerable: true, get: function () { return card_1.winnerNote; } });
|
|
60
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tracking/ui/index.ts"],"names":[],"mappings":";;;AAAA;;;;;GAKG;AACH,mDAAkD;AAAzC,gHAAA,cAAc,OAAA;AAEvB,2DAAoF;AAA3E,2GAAA,KAAK,OAAA;AAAE,wHAAA,kBAAkB,OAAA;AAAE,uHAAA,iBAAiB,OAAA;AAErD,6DAA4D;AAAnD,0HAAA,mBAAmB,OAAA;AAE5B,mCAA+E;AAAtE,sHAAA,4BAA4B,OAAA;AAAE,+GAAA,qBAAqB,OAAA;AAM5D,mCASkB;AARhB,sGAAA,YAAY,OAAA;AACZ,wGAAA,cAAc,OAAA;AACd,yGAAA,eAAe,OAAA;AACf,qGAAA,WAAW,OAAA;AACX,gGAAA,MAAM,OAAA;AACN,kGAAA,QAAQ,OAAA;AACR,wGAAA,cAAc,OAAA;AACd,mGAAA,SAAS,OAAA;AAGX,+BA8BgB;AA7Bd,qGAAA,aAAa,OAAA;AACb,iGAAA,SAAS,OAAA;AACT,iGAAA,SAAS,OAAA;AACT,gGAAA,QAAQ,OAAA;AACR,iGAAA,SAAS,OAAA;AACT,gGAAA,QAAQ,OAAA;AACR,+FAAA,OAAO,OAAA;AACP,mGAAA,WAAW,OAAA;AACX,6FAAA,KAAK,OAAA;AACL,0GAAA,kBAAkB,OAAA;AAClB,oGAAA,YAAY,OAAA;AACZ,sGAAA,cAAc,OAAA;AACd,uGAAA,eAAe,OAAA;AACf,uGAAA,eAAe,OAAA;AACf,wGAAA,gBAAgB,OAAA;AAChB,qGAAA,aAAa,OAAA;AACb,mGAAA,WAAW,OAAA;AACX,+FAAA,OAAO,OAAA;AACP,iGAAA,SAAS,OAAA;AACT,qGAAA,aAAa,OAAA;AACb,oGAAA,YAAY,OAAA;AACZ,iGAAA,SAAS,OAAA;AACT,qGAAA,aAAa,OAAA;AACb,mGAAA,WAAW,OAAA;AACX,mGAAA,WAAW,OAAA;AACX,mGAAA,WAAW,OAAA;AACX,yGAAA,iBAAiB,OAAA;AACjB,wGAAA,gBAAgB,OAAA;AAChB,kGAAA,UAAU,OAAA"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The experiment and results objects of the qorus-api admin and editor routes (§5, §7, §9.5 of
|
|
3
|
+
* `design/analytics-and-experiments-design.md`), with the fields the A/B test UI reads.
|
|
4
|
+
* Change the contract there first, then here.
|
|
5
|
+
*/
|
|
6
|
+
export type TExperimentStatus = 'draft' | 'running' | 'paused' | 'stopped' | 'concluded' | 'archived';
|
|
7
|
+
export interface IAdminVariant {
|
|
8
|
+
key: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
weight: number;
|
|
11
|
+
control?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface IAdminExperimentPhase {
|
|
14
|
+
phase: number;
|
|
15
|
+
started_at: string;
|
|
16
|
+
ended_at: string | null;
|
|
17
|
+
variants: IAdminVariant[];
|
|
18
|
+
}
|
|
19
|
+
export interface IAdminExperimentEvent {
|
|
20
|
+
at: string;
|
|
21
|
+
action: string;
|
|
22
|
+
variant?: string | null;
|
|
23
|
+
/** Who did it (`admin`, or the product's editor, e.g. `landing-editor`). */
|
|
24
|
+
actor: string;
|
|
25
|
+
user?: string;
|
|
26
|
+
}
|
|
27
|
+
/** The admin experiment object. */
|
|
28
|
+
export interface IAdminExperiment {
|
|
29
|
+
key: string;
|
|
30
|
+
name?: string;
|
|
31
|
+
property?: string;
|
|
32
|
+
status: TExperimentStatus;
|
|
33
|
+
variants: IAdminVariant[];
|
|
34
|
+
winner?: string | null;
|
|
35
|
+
/** The current phase: a removed version starts a new one, and the numbers count from zero. */
|
|
36
|
+
phase?: number;
|
|
37
|
+
/** Earlier phases and the current one (last, `ended_at: null` while it runs). */
|
|
38
|
+
phases?: IAdminExperimentPhase[];
|
|
39
|
+
/** Every state change, oldest first. */
|
|
40
|
+
history?: IAdminExperimentEvent[];
|
|
41
|
+
metrics?: {
|
|
42
|
+
primary?: string;
|
|
43
|
+
guardrails?: string[];
|
|
44
|
+
secondary?: string[];
|
|
45
|
+
};
|
|
46
|
+
/** Share of visitors in the test, 0..1. */
|
|
47
|
+
traffic?: number;
|
|
48
|
+
/** The too-early gate: every version needs `per_variant` visitors and `days` must pass (default 200, 7). */
|
|
49
|
+
min_sample?: {
|
|
50
|
+
per_variant: number;
|
|
51
|
+
days: number;
|
|
52
|
+
};
|
|
53
|
+
started_at?: string | null;
|
|
54
|
+
ended_at?: string | null;
|
|
55
|
+
}
|
|
56
|
+
export interface IResultsVariant {
|
|
57
|
+
variant: string;
|
|
58
|
+
exposed: number;
|
|
59
|
+
converters: number;
|
|
60
|
+
rate: number | null;
|
|
61
|
+
uplift?: number | null;
|
|
62
|
+
chance_to_beat: number | null;
|
|
63
|
+
}
|
|
64
|
+
export interface IResultsMetric {
|
|
65
|
+
key: string;
|
|
66
|
+
name?: string;
|
|
67
|
+
role: 'primary' | 'guardrail' | 'secondary';
|
|
68
|
+
verdict?: string;
|
|
69
|
+
verdict_text?: string;
|
|
70
|
+
variants: IResultsVariant[];
|
|
71
|
+
unavailable?: string;
|
|
72
|
+
}
|
|
73
|
+
/** The admin results object. */
|
|
74
|
+
export interface IExperimentResults {
|
|
75
|
+
verdict?: string;
|
|
76
|
+
/** The verdict in plain words, written by the server. */
|
|
77
|
+
verdict_text?: string;
|
|
78
|
+
winner_variant?: string | null;
|
|
79
|
+
phase?: number;
|
|
80
|
+
metrics: IResultsMetric[];
|
|
81
|
+
}
|
|
82
|
+
/** One entry of the editor list: the experiment and its results (null for a draft). */
|
|
83
|
+
export interface IExperimentEntry {
|
|
84
|
+
experiment: IAdminExperiment;
|
|
85
|
+
results: IExperimentResults | null;
|
|
86
|
+
}
|
|
87
|
+
/** The experiment an action returns; `auto_accepted` when removing left one version. */
|
|
88
|
+
export type TExperimentActionResult = IAdminExperiment & {
|
|
89
|
+
auto_accepted?: boolean;
|
|
90
|
+
};
|
|
91
|
+
export type TLifecycleAction = 'start' | 'pause' | 'stop';
|
|
92
|
+
export type TVersionAction = 'accept' | 'remove';
|
|
93
|
+
export type TExperimentAction = TVersionAction | TLifecycleAction;
|
|
94
|
+
/** A new experiment definition (admin shape); the server always creates it as a draft. */
|
|
95
|
+
export interface INewExperiment {
|
|
96
|
+
key: string;
|
|
97
|
+
name: string;
|
|
98
|
+
hypothesis?: string;
|
|
99
|
+
property?: string;
|
|
100
|
+
variants: IAdminVariant[];
|
|
101
|
+
traffic?: number;
|
|
102
|
+
targeting?: {
|
|
103
|
+
paths?: string[] | null;
|
|
104
|
+
device?: string | null;
|
|
105
|
+
lang?: string | null;
|
|
106
|
+
};
|
|
107
|
+
trigger?: {
|
|
108
|
+
section?: string | null;
|
|
109
|
+
};
|
|
110
|
+
metrics: {
|
|
111
|
+
primary: string;
|
|
112
|
+
guardrails?: string[];
|
|
113
|
+
secondary?: string[];
|
|
114
|
+
};
|
|
115
|
+
min_sample?: {
|
|
116
|
+
per_variant: number;
|
|
117
|
+
days: number;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/tracking/ui/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The experiment and results objects of the qorus-api admin and editor routes (§5, §7, §9.5 of
|
|
4
|
+
* `design/analytics-and-experiments-design.md`), with the fields the A/B test UI reads.
|
|
5
|
+
* Change the contract there first, then here.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/tracking/ui/types.ts"],"names":[],"mappings":";AAAA;;;;GAIG"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type IExperimentsAdminClient, type IExperimentsAdminOptions } from './client';
|
|
2
|
+
import type { IExperimentEntry, TExperimentActionResult } from './types';
|
|
3
|
+
export interface IUseExperimentsAdminOptions extends IExperimentsAdminOptions {
|
|
4
|
+
/** Reload the list this often (someone else may accept a test meanwhile). Default 60 s; 0 turns it off. */
|
|
5
|
+
refreshMs?: number;
|
|
6
|
+
/** Load nothing while false (e.g. while the overlay is hidden). Default true. */
|
|
7
|
+
enabled?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface IUseExperimentsAdmin {
|
|
10
|
+
/** The list, or null before the first answer. */
|
|
11
|
+
tests: IExperimentEntry[] | null;
|
|
12
|
+
/** The last load's error message, or null. */
|
|
13
|
+
error: string | null;
|
|
14
|
+
reload: () => Promise<void>;
|
|
15
|
+
/** Each action reloads the list when it resolves, and rejects with the server's words. */
|
|
16
|
+
accept: (key: string, variant: string) => Promise<TExperimentActionResult>;
|
|
17
|
+
remove: (key: string, variant: string) => Promise<TExperimentActionResult>;
|
|
18
|
+
start: (key: string) => Promise<unknown>;
|
|
19
|
+
pause: (key: string) => Promise<unknown>;
|
|
20
|
+
stop: (key: string) => Promise<unknown>;
|
|
21
|
+
client: IExperimentsAdminClient;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The A/B test list and its actions from the qorus-api routes, for `<ExperimentsOverlay>` or
|
|
25
|
+
* `<ExperimentCard>`. Optional and replaceable: both components take plain data and callbacks.
|
|
26
|
+
*
|
|
27
|
+
* const admin = useExperimentsAdmin({ listUrl: '/qorus-cloud/editor/experiments?property=landing' });
|
|
28
|
+
* <ExperimentsOverlay tests={admin.tests} loadError={admin.error} onAccept={admin.accept} … />
|
|
29
|
+
*/
|
|
30
|
+
export declare const useExperimentsAdmin: (options: IUseExperimentsAdminOptions) => IUseExperimentsAdmin;
|
|
31
|
+
//# sourceMappingURL=useExperimentsAdmin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useExperimentsAdmin.d.ts","sourceRoot":"","sources":["../../../src/tracking/ui/useExperimentsAdmin.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC9B,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,gBAAgB,EAAE,uBAAuB,EAAoB,MAAM,SAAS,CAAC;AAE3F,MAAM,WAAW,2BAA4B,SAAQ,wBAAwB;IAC3E,2GAA2G;IAC3G,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,iDAAiD;IACjD,KAAK,EAAE,gBAAgB,EAAE,GAAG,IAAI,CAAC;IACjC,8CAA8C;IAC9C,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,0FAA0F;IAC1F,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC3E,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC3E,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxC,MAAM,EAAE,uBAAuB,CAAC;CACjC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,YAAa,2BAA2B,KAAG,oBAiE1E,CAAC"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
14
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
15
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
16
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
17
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
18
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
19
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
23
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
24
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
25
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
26
|
+
function step(op) {
|
|
27
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
28
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
29
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
30
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
31
|
+
switch (op[0]) {
|
|
32
|
+
case 0: case 1: t = op; break;
|
|
33
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
34
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
35
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
36
|
+
default:
|
|
37
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
38
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
39
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
40
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
41
|
+
if (t[2]) _.ops.pop();
|
|
42
|
+
_.trys.pop(); continue;
|
|
43
|
+
}
|
|
44
|
+
op = body.call(thisArg, _);
|
|
45
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
46
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
exports.useExperimentsAdmin = void 0;
|
|
51
|
+
var react_1 = require("react");
|
|
52
|
+
var client_1 = require("./client");
|
|
53
|
+
/**
|
|
54
|
+
* The A/B test list and its actions from the qorus-api routes, for `<ExperimentsOverlay>` or
|
|
55
|
+
* `<ExperimentCard>`. Optional and replaceable: both components take plain data and callbacks.
|
|
56
|
+
*
|
|
57
|
+
* const admin = useExperimentsAdmin({ listUrl: '/qorus-cloud/editor/experiments?property=landing' });
|
|
58
|
+
* <ExperimentsOverlay tests={admin.tests} loadError={admin.error} onAccept={admin.accept} … />
|
|
59
|
+
*/
|
|
60
|
+
var useExperimentsAdmin = function (options) {
|
|
61
|
+
var _a;
|
|
62
|
+
var _b = options.refreshMs, refreshMs = _b === void 0 ? 60000 : _b, _c = options.enabled, enabled = _c === void 0 ? true : _c;
|
|
63
|
+
// The options object is usually a fresh literal: the client follows its values, not its identity.
|
|
64
|
+
var latest = (0, react_1.useRef)(options);
|
|
65
|
+
latest.current = options;
|
|
66
|
+
var headersKey = JSON.stringify((_a = options.headers) !== null && _a !== void 0 ? _a : {});
|
|
67
|
+
var client = (0, react_1.useMemo)(function () {
|
|
68
|
+
return (0, client_1.createExperimentsAdminClient)(__assign(__assign({}, latest.current), { fetch: function (input, init) { var _a; return ((_a = latest.current.fetch) !== null && _a !== void 0 ? _a : fetch)(input, init); } }));
|
|
69
|
+
}, [options.listUrl, options.createUrl, options.credentials, headersKey]);
|
|
70
|
+
var _d = (0, react_1.useState)(null), tests = _d[0], setTests = _d[1];
|
|
71
|
+
var _e = (0, react_1.useState)(null), error = _e[0], setError = _e[1];
|
|
72
|
+
var alive = (0, react_1.useRef)(true);
|
|
73
|
+
var reload = (0, react_1.useCallback)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
74
|
+
var list, err_1;
|
|
75
|
+
return __generator(this, function (_a) {
|
|
76
|
+
switch (_a.label) {
|
|
77
|
+
case 0:
|
|
78
|
+
_a.trys.push([0, 2, , 3]);
|
|
79
|
+
return [4 /*yield*/, client.list()];
|
|
80
|
+
case 1:
|
|
81
|
+
list = _a.sent();
|
|
82
|
+
if (!alive.current)
|
|
83
|
+
return [2 /*return*/];
|
|
84
|
+
setTests(list);
|
|
85
|
+
setError(null);
|
|
86
|
+
return [3 /*break*/, 3];
|
|
87
|
+
case 2:
|
|
88
|
+
err_1 = _a.sent();
|
|
89
|
+
if (alive.current)
|
|
90
|
+
setError(err_1.message);
|
|
91
|
+
return [3 /*break*/, 3];
|
|
92
|
+
case 3: return [2 /*return*/];
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}); }, [client]);
|
|
96
|
+
(0, react_1.useEffect)(function () {
|
|
97
|
+
alive.current = true;
|
|
98
|
+
if (!enabled)
|
|
99
|
+
return function () { return undefined; };
|
|
100
|
+
void reload();
|
|
101
|
+
var id = refreshMs > 0 ? setInterval(function () { return void reload(); }, refreshMs) : null;
|
|
102
|
+
return function () {
|
|
103
|
+
alive.current = false;
|
|
104
|
+
if (id)
|
|
105
|
+
clearInterval(id);
|
|
106
|
+
};
|
|
107
|
+
}, [reload, enabled, refreshMs]);
|
|
108
|
+
var then = (0, react_1.useCallback)(function (promise) { return __awaiter(void 0, void 0, void 0, function () {
|
|
109
|
+
var result;
|
|
110
|
+
return __generator(this, function (_a) {
|
|
111
|
+
switch (_a.label) {
|
|
112
|
+
case 0: return [4 /*yield*/, promise];
|
|
113
|
+
case 1:
|
|
114
|
+
result = _a.sent();
|
|
115
|
+
return [4 /*yield*/, reload()];
|
|
116
|
+
case 2:
|
|
117
|
+
_a.sent();
|
|
118
|
+
return [2 /*return*/, result];
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}); }, [reload]);
|
|
122
|
+
var lifecycle = (0, react_1.useCallback)(function (key, action) { return then(client.lifecycle(key, action)); }, [client, then]);
|
|
123
|
+
return {
|
|
124
|
+
tests: tests,
|
|
125
|
+
error: error,
|
|
126
|
+
reload: reload,
|
|
127
|
+
accept: (0, react_1.useCallback)(function (key, variant) { return then(client.accept(key, variant)); }, [client, then]),
|
|
128
|
+
remove: (0, react_1.useCallback)(function (key, variant) { return then(client.remove(key, variant)); }, [client, then]),
|
|
129
|
+
start: (0, react_1.useCallback)(function (key) { return lifecycle(key, 'start'); }, [lifecycle]),
|
|
130
|
+
pause: (0, react_1.useCallback)(function (key) { return lifecycle(key, 'pause'); }, [lifecycle]),
|
|
131
|
+
stop: (0, react_1.useCallback)(function (key) { return lifecycle(key, 'stop'); }, [lifecycle]),
|
|
132
|
+
client: client,
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
exports.useExperimentsAdmin = useExperimentsAdmin;
|
|
136
|
+
//# sourceMappingURL=useExperimentsAdmin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useExperimentsAdmin.js","sourceRoot":"","sources":["../../../src/tracking/ui/useExperimentsAdmin.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+BAA0E;AAC1E,mCAIkB;AAyBlB;;;;;;GAMG;AACI,IAAM,mBAAmB,GAAG,UAAC,OAAoC;;IAC9D,IAAA,KAAuC,OAAO,UAA5B,EAAlB,SAAS,mBAAG,KAAM,KAAA,EAAE,KAAmB,OAAO,QAAZ,EAAd,OAAO,mBAAG,IAAI,KAAA,CAAa;IACvD,kGAAkG;IAClG,IAAM,MAAM,GAAG,IAAA,cAAM,EAAC,OAAO,CAAC,CAAC;IAC/B,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,IAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,MAAA,OAAO,CAAC,OAAO,mCAAI,EAAE,CAAC,CAAC;IACzD,IAAM,MAAM,GAAG,IAAA,eAAO,EACpB;QACE,OAAA,IAAA,qCAA4B,wBACvB,MAAM,CAAC,OAAO,KACjB,KAAK,EAAE,UAAC,KAAK,EAAE,IAAI,YAAK,OAAA,CAAC,MAAA,MAAM,CAAC,OAAO,CAAC,KAAK,mCAAI,KAAK,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA,EAAA,IACpE;IAHF,CAGE,EACJ,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,WAAW,EAAE,UAAU,CAAC,CACtE,CAAC;IACI,IAAA,KAAoB,IAAA,gBAAQ,EAA4B,IAAI,CAAC,EAA5D,KAAK,QAAA,EAAE,QAAQ,QAA6C,CAAC;IAC9D,IAAA,KAAoB,IAAA,gBAAQ,EAAgB,IAAI,CAAC,EAAhD,KAAK,QAAA,EAAE,QAAQ,QAAiC,CAAC;IACxD,IAAM,KAAK,GAAG,IAAA,cAAM,EAAC,IAAI,CAAC,CAAC;IAE3B,IAAM,MAAM,GAAG,IAAA,mBAAW,EAAC;;;;;;oBAEV,qBAAM,MAAM,CAAC,IAAI,EAAE,EAAA;;oBAA1B,IAAI,GAAG,SAAmB;oBAChC,IAAI,CAAC,KAAK,CAAC,OAAO;wBAAE,sBAAO;oBAC3B,QAAQ,CAAC,IAAI,CAAC,CAAC;oBACf,QAAQ,CAAC,IAAI,CAAC,CAAC;;;;oBAEf,IAAI,KAAK,CAAC,OAAO;wBAAE,QAAQ,CAAE,KAAa,CAAC,OAAO,CAAC,CAAC;;;;;SAEvD,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,IAAA,iBAAS,EAAC;QACR,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,OAAO;YAAE,OAAO,cAAM,OAAA,SAAS,EAAT,CAAS,CAAC;QACrC,KAAK,MAAM,EAAE,CAAC;QACd,IAAM,EAAE,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,cAAM,OAAA,KAAK,MAAM,EAAE,EAAb,CAAa,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC9E,OAAO;YACL,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC;YACtB,IAAI,EAAE;gBAAE,aAAa,CAAC,EAAE,CAAC,CAAC;QAC5B,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;IAEjC,IAAM,IAAI,GAAG,IAAA,mBAAW,EACtB,UAAU,OAAmB;;;;wBACZ,qBAAM,OAAO,EAAA;;oBAAtB,MAAM,GAAG,SAAa;oBAC5B,qBAAM,MAAM,EAAE,EAAA;;oBAAd,SAAc,CAAC;oBACf,sBAAO,MAAM,EAAC;;;SACf,EACD,CAAC,MAAM,CAAC,CACT,CAAC;IAEF,IAAM,SAAS,GAAG,IAAA,mBAAW,EAC3B,UAAC,GAAW,EAAE,MAAwB,IAAK,OAAA,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,EAAnC,CAAmC,EAC9E,CAAC,MAAM,EAAE,IAAI,CAAC,CACf,CAAC;IAEF,OAAO;QACL,KAAK,OAAA;QACL,KAAK,OAAA;QACL,MAAM,QAAA;QACN,MAAM,EAAE,IAAA,mBAAW,EAAC,UAAC,GAAG,EAAE,OAAO,IAAK,OAAA,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,EAAjC,CAAiC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACxF,MAAM,EAAE,IAAA,mBAAW,EAAC,UAAC,GAAG,EAAE,OAAO,IAAK,OAAA,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,EAAjC,CAAiC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACxF,KAAK,EAAE,IAAA,mBAAW,EAAC,UAAC,GAAG,IAAK,OAAA,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,EAAvB,CAAuB,EAAE,CAAC,SAAS,CAAC,CAAC;QACjE,KAAK,EAAE,IAAA,mBAAW,EAAC,UAAC,GAAG,IAAK,OAAA,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,EAAvB,CAAuB,EAAE,CAAC,SAAS,CAAC,CAAC;QACjE,IAAI,EAAE,IAAA,mBAAW,EAAC,UAAC,GAAG,IAAK,OAAA,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,EAAtB,CAAsB,EAAE,CAAC,SAAS,CAAC,CAAC;QAC/D,MAAM,QAAA;KACP,CAAC;AACJ,CAAC,CAAC;AAjEW,QAAA,mBAAmB,uBAiE9B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qoretechnologies/reqraft",
|
|
3
|
-
"version": "0.10.57
|
|
3
|
+
"version": "0.10.57",
|
|
4
4
|
"description": "ReQraft is a collection of React components and hooks that are used across Qore Technologies' products made using the ReQore component library from Qore.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@chromatic-com/storybook": "^5.2.1",
|
|
58
58
|
"@netsells/storybook-mockdate": "^0.3.3",
|
|
59
|
-
"@qoretechnologies/qlip": "^0.1.3-beta.
|
|
60
|
-
"@qoretechnologies/reqore": "^0.
|
|
59
|
+
"@qoretechnologies/qlip": "^0.1.3-beta.20260929120101",
|
|
60
|
+
"@qoretechnologies/reqore": "^0.77.2",
|
|
61
61
|
"@storybook/addon-docs": "^10.4.6",
|
|
62
62
|
"@storybook/addon-links": "^10.4.6",
|
|
63
63
|
"@storybook/addon-vitest": "^10.4.6",
|
package/src/index.tsx
CHANGED
|
@@ -78,5 +78,7 @@ export * from './components/ticketReferences';
|
|
|
78
78
|
export * from './components/searchFilterBar';
|
|
79
79
|
export * from './components/codeSize';
|
|
80
80
|
// First-party tracking, consent and A/B testing (design/TRACKING.md). Also importable
|
|
81
|
-
// on its own, without the rest of Reqraft: `@qoretechnologies/reqraft/dist/tracking
|
|
81
|
+
// on its own, without the rest of Reqraft: `@qoretechnologies/reqraft/dist/tracking`
|
|
82
|
+
// (the engine, React only) and `@qoretechnologies/reqraft/dist/tracking/ui` (the A/B test UI).
|
|
82
83
|
export * from './tracking';
|
|
84
|
+
export * from './tracking/ui';
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Story helpers for the A/B test UI: fixtures in the qorus-api shape, and the dark / light /
|
|
3
|
+
* phone variants every visual story ships in.
|
|
4
|
+
*/
|
|
5
|
+
import { ReqoreContent, ReqoreUIProvider } from '@qoretechnologies/reqore';
|
|
6
|
+
import type { ReactNode } from 'react';
|
|
7
|
+
import type { IAdminVariant, IExperimentEntry, IExperimentResults } from '../tracking/ui/types';
|
|
8
|
+
|
|
9
|
+
export const PHONE_VIEWPORT = { width: 390, height: 844 };
|
|
10
|
+
|
|
11
|
+
/** The page a light-mode product uses: Reqore derives every surface from this `main`. */
|
|
12
|
+
export const LIGHT_MAIN = '#f6f6f6';
|
|
13
|
+
|
|
14
|
+
/** Wraps a story in a light Reqore theme (the preview's own theme is dark). */
|
|
15
|
+
export const LightTheme = ({ children }: { children: ReactNode }) => (
|
|
16
|
+
<ReqoreUIProvider
|
|
17
|
+
theme={{ main: LIGHT_MAIN }}
|
|
18
|
+
options={{ animations: { buttons: false, dialogs: false } }}
|
|
19
|
+
>
|
|
20
|
+
<ReqoreContent style={{ padding: 20, minHeight: '100vh' }}>{children}</ReqoreContent>
|
|
21
|
+
</ReqoreUIProvider>
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
type TVariant = 'Dark' | 'Light' | 'Phone';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A story in one of the three variants: the description gets the variant's words, Light wraps
|
|
28
|
+
* the story in a light theme, Phone captures at 390 px.
|
|
29
|
+
*/
|
|
30
|
+
export const variant = <S extends object>(story: S, description: string, mode: TVariant): S => {
|
|
31
|
+
const base = story as S & { parameters?: Record<string, any>; decorators?: any };
|
|
32
|
+
const light = (Story: () => JSX.Element) => (
|
|
33
|
+
<LightTheme>
|
|
34
|
+
<Story />
|
|
35
|
+
</LightTheme>
|
|
36
|
+
);
|
|
37
|
+
return {
|
|
38
|
+
...story,
|
|
39
|
+
decorators: [...((base.decorators as any[]) ?? []), ...(mode === 'Light' ? [light] : [])],
|
|
40
|
+
parameters: {
|
|
41
|
+
...base.parameters,
|
|
42
|
+
...(mode === 'Phone' ? { qlip: { viewport: PHONE_VIEWPORT } } : {}),
|
|
43
|
+
docs: {
|
|
44
|
+
description: {
|
|
45
|
+
story:
|
|
46
|
+
mode === 'Dark' ? description
|
|
47
|
+
: mode === 'Light' ? `${description} On a light theme.`
|
|
48
|
+
: `${description} At phone width (390 px).`,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const v = (key: string, name: string, weight: number, control = false): IAdminVariant => ({
|
|
56
|
+
key,
|
|
57
|
+
name,
|
|
58
|
+
weight,
|
|
59
|
+
...(control ? { control } : {}),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const results = (
|
|
63
|
+
numbers: [string, number, number, number | null][],
|
|
64
|
+
patch: Partial<IExperimentResults> = {}
|
|
65
|
+
): IExperimentResults => ({
|
|
66
|
+
verdict: 'too-early',
|
|
67
|
+
metrics: [
|
|
68
|
+
{
|
|
69
|
+
key: 'signup-started',
|
|
70
|
+
name: 'Started a sign-up',
|
|
71
|
+
role: 'primary',
|
|
72
|
+
variants: numbers.map(([variant, exposed, converters, chance]) => ({
|
|
73
|
+
variant,
|
|
74
|
+
exposed,
|
|
75
|
+
converters,
|
|
76
|
+
rate: exposed ? converters / exposed : null,
|
|
77
|
+
chance_to_beat: chance,
|
|
78
|
+
})),
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
...patch,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
export const EXPERIMENT_KEY = 'signup-cta';
|
|
85
|
+
|
|
86
|
+
const TWO = [v('a', 'Current button', 0.5, true), v('b', 'Short copy', 0.5)];
|
|
87
|
+
|
|
88
|
+
export const DRAFT: IExperimentEntry = {
|
|
89
|
+
experiment: {
|
|
90
|
+
key: EXPERIMENT_KEY,
|
|
91
|
+
name: 'Sign-up button copy',
|
|
92
|
+
status: 'draft',
|
|
93
|
+
phase: 1,
|
|
94
|
+
variants: TWO,
|
|
95
|
+
},
|
|
96
|
+
results: null,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const RUNNING_TWO: IExperimentEntry = {
|
|
100
|
+
experiment: {
|
|
101
|
+
key: EXPERIMENT_KEY,
|
|
102
|
+
name: 'Sign-up button copy',
|
|
103
|
+
status: 'running',
|
|
104
|
+
phase: 1,
|
|
105
|
+
variants: TWO,
|
|
106
|
+
},
|
|
107
|
+
results: results(
|
|
108
|
+
[
|
|
109
|
+
['a', 1240, 112, null],
|
|
110
|
+
['b', 1228, 139, 0.91],
|
|
111
|
+
],
|
|
112
|
+
{
|
|
113
|
+
verdict: 'likely-winner',
|
|
114
|
+
winner_variant: 'b',
|
|
115
|
+
verdict_text:
|
|
116
|
+
'Short copy is likely better: 91% chance it beats Current button on “Started a sign-up”.',
|
|
117
|
+
}
|
|
118
|
+
),
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const RUNNING_FIVE: IExperimentEntry = {
|
|
122
|
+
experiment: {
|
|
123
|
+
key: EXPERIMENT_KEY,
|
|
124
|
+
name: 'Pricing page headline',
|
|
125
|
+
status: 'running',
|
|
126
|
+
phase: 1,
|
|
127
|
+
variants: [
|
|
128
|
+
v('a', 'Current headline', 0.2, true),
|
|
129
|
+
v('b', 'Price first', 0.2),
|
|
130
|
+
v('c', 'Customer quote', 0.2),
|
|
131
|
+
v('d', 'Question', 0.2),
|
|
132
|
+
v('e', 'Numbers', 0.2),
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
results: results([
|
|
136
|
+
['a', 604, 41, null],
|
|
137
|
+
['b', 598, 47, 0.72],
|
|
138
|
+
['c', 611, 38, 0.36],
|
|
139
|
+
['d', 590, 44, 0.61],
|
|
140
|
+
['e', 602, 30, 0.12],
|
|
141
|
+
]),
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const PAUSED: IExperimentEntry = {
|
|
145
|
+
experiment: {
|
|
146
|
+
key: EXPERIMENT_KEY,
|
|
147
|
+
name: 'Sign-up button copy',
|
|
148
|
+
status: 'paused',
|
|
149
|
+
phase: 2,
|
|
150
|
+
variants: TWO,
|
|
151
|
+
phases: [
|
|
152
|
+
{
|
|
153
|
+
phase: 1,
|
|
154
|
+
started_at: '2026-09-10T08:00:00Z',
|
|
155
|
+
ended_at: '2026-09-20T10:00:00Z',
|
|
156
|
+
variants: [...TWO, v('c', 'Emoji', 0.33)],
|
|
157
|
+
},
|
|
158
|
+
{ phase: 2, started_at: '2026-09-20T10:00:00Z', ended_at: null, variants: TWO },
|
|
159
|
+
],
|
|
160
|
+
history: [
|
|
161
|
+
{ at: '2026-09-20T10:00:00Z', action: 'remove-variant', variant: 'c', actor: 'admin' },
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
results: results(
|
|
165
|
+
[
|
|
166
|
+
['a', 380, 31, null],
|
|
167
|
+
['b', 371, 33, 0.64],
|
|
168
|
+
],
|
|
169
|
+
{ verdict: 'no-clear-difference' }
|
|
170
|
+
),
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export const CONCLUDED: IExperimentEntry = {
|
|
174
|
+
experiment: {
|
|
175
|
+
key: EXPERIMENT_KEY,
|
|
176
|
+
name: 'Sign-up button copy',
|
|
177
|
+
status: 'concluded',
|
|
178
|
+
phase: 1,
|
|
179
|
+
winner: 'b',
|
|
180
|
+
variants: TWO,
|
|
181
|
+
},
|
|
182
|
+
results: RUNNING_TWO.results,
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export const NO_DATA_YET: IExperimentEntry = {
|
|
186
|
+
experiment: {
|
|
187
|
+
key: EXPERIMENT_KEY,
|
|
188
|
+
name: 'Sign-up button copy',
|
|
189
|
+
status: 'running',
|
|
190
|
+
phase: 1,
|
|
191
|
+
variants: TWO,
|
|
192
|
+
},
|
|
193
|
+
results: results([
|
|
194
|
+
['a', 0, 0, null],
|
|
195
|
+
['b', 0, 0, null],
|
|
196
|
+
]),
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/** Runs the story as a test but takes no Qlip snapshot (a visually empty state). */
|
|
200
|
+
export const noSnapshot = <S extends object>(story: S): S => {
|
|
201
|
+
const base = story as S & { parameters?: Record<string, any> };
|
|
202
|
+
return {
|
|
203
|
+
...story,
|
|
204
|
+
parameters: { ...base.parameters, qlip: { ...base.parameters?.qlip, skip: true } },
|
|
205
|
+
};
|
|
206
|
+
};
|