@startsimpli/funnels 0.4.6 → 0.4.7
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/package.json +1 -1
- package/src/api/client.test.ts +76 -76
- package/src/api/client.ts +16 -16
- package/src/components/FilterRuleEditor/FilterRuleEditor.stories.tsx +24 -24
- package/src/components/FilterRuleEditor/FilterRuleEditor.test.tsx +39 -39
- package/src/components/FilterRuleEditor/FilterRuleEditor.tsx +1 -1
- package/src/components/FilterRuleEditor/RuleRow.tsx +8 -8
- package/src/components/FunnelCard/FunnelCard.stories.tsx +118 -118
- package/src/components/FunnelCard/FunnelCard.test.ts +48 -48
- package/src/components/FunnelCard/FunnelCard.test.tsx +40 -40
- package/src/components/FunnelCard/FunnelCard.tsx +3 -3
- package/src/components/FunnelPreview/FunnelPreview.test.tsx +63 -63
- package/src/components/FunnelPreview/FunnelPreview.tsx +11 -11
- package/src/components/FunnelPreview/StageBreakdown.tsx +5 -5
- package/src/components/FunnelPreview/example.tsx +43 -43
- package/src/components/FunnelRunHistory/FunnelRunHistory.stories.tsx +70 -70
- package/src/components/FunnelRunHistory/FunnelRunHistory.test.tsx +63 -63
- package/src/components/FunnelRunHistory/FunnelRunHistory.tsx +12 -12
- package/src/components/FunnelRunHistory/RunDetailsModal.tsx +5 -5
- package/src/components/FunnelRunHistory/RunFilters.tsx +8 -8
- package/src/components/FunnelRunHistory/RunRow.tsx +7 -7
- package/src/components/FunnelRunHistory/StageBreakdownList.tsx +9 -9
- package/src/components/FunnelRunHistory/types.ts +6 -6
- package/src/components/FunnelStageBuilder/FunnelStageBuilder.stories.tsx +41 -41
- package/src/components/FunnelStageBuilder/FunnelStageBuilder.test.tsx +6 -6
- package/src/components/FunnelStageBuilder/FunnelStageBuilder.tsx +5 -5
- package/src/components/FunnelStageBuilder/StageActions.test.tsx +7 -7
- package/src/components/FunnelStageBuilder/StageActions.tsx +4 -4
- package/src/components/FunnelStageBuilder/StageCard.tsx +3 -3
- package/src/components/FunnelStageBuilder/StageForm.tsx +14 -14
- package/src/components/FunnelVisualFlow/FunnelVisualFlow.stories.tsx +42 -42
- package/src/components/FunnelVisualFlow/FunnelVisualFlow.test.tsx +31 -31
- package/src/components/FunnelVisualFlow/FunnelVisualFlow.tsx +4 -4
- package/src/components/FunnelVisualFlow/StageNode.tsx +8 -8
- package/src/components/FunnelVisualFlow/example.tsx +7 -7
- package/src/core/engine.test.ts +271 -271
- package/src/core/engine.ts +60 -60
- package/src/core/evaluator.example.ts +31 -31
- package/src/core/evaluator.test.ts +92 -92
- package/src/core/evaluator.ts +23 -23
- package/src/core/field-resolver.example.ts +1 -1
- package/src/core/field-resolver.test.ts +4 -4
- package/src/store/create-funnel-store.test.ts +32 -32
- package/src/store/create-funnel-store.ts +3 -3
- package/src/store/types.ts +2 -2
- package/src/types/index.ts +64 -64
package/src/types/index.ts
CHANGED
|
@@ -74,17 +74,17 @@ export type FieldType =
|
|
|
74
74
|
* A single filter rule
|
|
75
75
|
*
|
|
76
76
|
* Examples:
|
|
77
|
-
* - {
|
|
78
|
-
* - {
|
|
79
|
-
* - {
|
|
80
|
-
* - {
|
|
77
|
+
* - { fieldPath: 'firm.stage', operator: 'eq', value: 'Series A' }
|
|
78
|
+
* - { fieldPath: 'recipe.cuisine', operator: 'in', value: ['Italian', 'French'] }
|
|
79
|
+
* - { fieldPath: 'contact.email', operator: 'isnotnull', value: null }
|
|
80
|
+
* - { fieldPath: 'organization.tags', operator: 'has_tag', value: 'enterprise' }
|
|
81
81
|
*/
|
|
82
82
|
export interface FilterRule {
|
|
83
83
|
/**
|
|
84
84
|
* Dot-notation path to field
|
|
85
85
|
* Examples: 'name', 'firm.stage', 'profile.linkedin_url', 'tags', 'metrics.arr_usd'
|
|
86
86
|
*/
|
|
87
|
-
|
|
87
|
+
fieldPath: string;
|
|
88
88
|
|
|
89
89
|
/** Comparison operator */
|
|
90
90
|
operator: Operator;
|
|
@@ -156,22 +156,22 @@ export interface FunnelStage<TEntity = any> {
|
|
|
156
156
|
* - AND: All rules must match
|
|
157
157
|
* - OR: At least one rule must match
|
|
158
158
|
*/
|
|
159
|
-
|
|
159
|
+
filterLogic: FilterLogic;
|
|
160
160
|
|
|
161
161
|
/** Filter rules to evaluate */
|
|
162
162
|
rules: FilterRule[];
|
|
163
163
|
|
|
164
164
|
/** Action when rules match */
|
|
165
|
-
|
|
165
|
+
matchAction: MatchAction;
|
|
166
166
|
|
|
167
167
|
/** Action when rules don't match */
|
|
168
|
-
|
|
168
|
+
noMatchAction: NoMatchAction;
|
|
169
169
|
|
|
170
170
|
/** Tags to add when rules match */
|
|
171
|
-
|
|
171
|
+
matchTags?: string[];
|
|
172
172
|
|
|
173
173
|
/** Tags to add when rules don't match */
|
|
174
|
-
|
|
174
|
+
noMatchTags?: string[];
|
|
175
175
|
|
|
176
176
|
/**
|
|
177
177
|
* Context to add to entity when rules match
|
|
@@ -180,13 +180,13 @@ export interface FunnelStage<TEntity = any> {
|
|
|
180
180
|
* - { stage: 'qualified_leads', reason: 'high_fit_score' }
|
|
181
181
|
* - { tier: 'premium', discount: 0.2 }
|
|
182
182
|
*/
|
|
183
|
-
|
|
183
|
+
matchContext?: Record<string, any>;
|
|
184
184
|
|
|
185
185
|
/**
|
|
186
186
|
* Optional: Custom evaluation function
|
|
187
187
|
* For complex logic that can't be expressed with rules
|
|
188
188
|
*/
|
|
189
|
-
|
|
189
|
+
customEvaluator?: (entity: TEntity) => boolean;
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
// ============================================================================
|
|
@@ -225,28 +225,28 @@ export interface Funnel<TEntity = any> {
|
|
|
225
225
|
* Type of entities this funnel processes
|
|
226
226
|
* Used for field registry lookup
|
|
227
227
|
*/
|
|
228
|
-
|
|
228
|
+
inputType: InputType;
|
|
229
229
|
|
|
230
230
|
/** Ordered stages */
|
|
231
231
|
stages: FunnelStage<TEntity>[];
|
|
232
232
|
|
|
233
233
|
/** Created timestamp */
|
|
234
|
-
|
|
234
|
+
createdAt: Date | string;
|
|
235
235
|
|
|
236
236
|
/** Last updated timestamp */
|
|
237
|
-
|
|
237
|
+
updatedAt: Date | string;
|
|
238
238
|
|
|
239
239
|
/** Owner user ID */
|
|
240
|
-
|
|
240
|
+
ownerId?: string;
|
|
241
241
|
|
|
242
242
|
/** Team/company ID for multi-tenancy */
|
|
243
|
-
|
|
243
|
+
teamId?: string;
|
|
244
244
|
|
|
245
245
|
/**
|
|
246
246
|
* Optional: Tags to automatically add to all entities
|
|
247
247
|
* that complete the funnel
|
|
248
248
|
*/
|
|
249
|
-
|
|
249
|
+
completionTags?: string[];
|
|
250
250
|
|
|
251
251
|
/**
|
|
252
252
|
* Optional: Metadata for custom integrations
|
|
@@ -282,34 +282,34 @@ export type TriggerType =
|
|
|
282
282
|
*/
|
|
283
283
|
export interface StageStats {
|
|
284
284
|
/** Stage ID */
|
|
285
|
-
|
|
285
|
+
stageId: string;
|
|
286
286
|
|
|
287
287
|
/** Stage name */
|
|
288
|
-
|
|
288
|
+
stageName: string;
|
|
289
289
|
|
|
290
290
|
/** Entities that entered this stage */
|
|
291
|
-
|
|
291
|
+
inputCount: number;
|
|
292
292
|
|
|
293
293
|
/** Entities that matched rules */
|
|
294
|
-
|
|
294
|
+
matchedCount: number;
|
|
295
295
|
|
|
296
296
|
/** Entities that didn't match rules */
|
|
297
|
-
|
|
297
|
+
notMatchedCount: number;
|
|
298
298
|
|
|
299
299
|
/** Entities excluded at this stage */
|
|
300
|
-
|
|
300
|
+
excludedCount: number;
|
|
301
301
|
|
|
302
302
|
/** Entities tagged at this stage */
|
|
303
|
-
|
|
303
|
+
taggedCount: number;
|
|
304
304
|
|
|
305
305
|
/** Entities that continued to next stage */
|
|
306
|
-
|
|
306
|
+
continuedCount: number;
|
|
307
307
|
|
|
308
308
|
/** Execution time in milliseconds */
|
|
309
|
-
|
|
309
|
+
durationMs?: number;
|
|
310
310
|
|
|
311
311
|
/** Error count */
|
|
312
|
-
|
|
312
|
+
errorCount?: number;
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
/**
|
|
@@ -320,7 +320,7 @@ export interface FunnelRun {
|
|
|
320
320
|
id: string;
|
|
321
321
|
|
|
322
322
|
/** Funnel ID */
|
|
323
|
-
|
|
323
|
+
funnelId: string;
|
|
324
324
|
|
|
325
325
|
/** Funnel snapshot (copy of funnel at run time) */
|
|
326
326
|
funnel?: Funnel;
|
|
@@ -329,40 +329,40 @@ export interface FunnelRun {
|
|
|
329
329
|
status: FunnelRunStatus;
|
|
330
330
|
|
|
331
331
|
/** How run was triggered */
|
|
332
|
-
|
|
332
|
+
triggerType: TriggerType;
|
|
333
333
|
|
|
334
334
|
/** User who triggered (if manual) */
|
|
335
|
-
|
|
335
|
+
triggeredBy?: string;
|
|
336
336
|
|
|
337
337
|
/** Started timestamp */
|
|
338
|
-
|
|
338
|
+
startedAt: Date | string;
|
|
339
339
|
|
|
340
340
|
/** Completed timestamp */
|
|
341
|
-
|
|
341
|
+
completedAt?: Date | string;
|
|
342
342
|
|
|
343
343
|
/** Duration in milliseconds */
|
|
344
|
-
|
|
344
|
+
durationMs?: number;
|
|
345
345
|
|
|
346
346
|
/** Total entities input */
|
|
347
|
-
|
|
347
|
+
totalInput: number;
|
|
348
348
|
|
|
349
349
|
/** Total entities matched (in output) */
|
|
350
|
-
|
|
350
|
+
totalMatched: number;
|
|
351
351
|
|
|
352
352
|
/** Total entities excluded */
|
|
353
|
-
|
|
353
|
+
totalExcluded: number;
|
|
354
354
|
|
|
355
355
|
/** Total entities tagged */
|
|
356
|
-
|
|
356
|
+
totalTagged: number;
|
|
357
357
|
|
|
358
358
|
/** Per-stage statistics */
|
|
359
|
-
|
|
359
|
+
stageStats: Record<string, StageStats>;
|
|
360
360
|
|
|
361
361
|
/** Error message (if failed) */
|
|
362
362
|
error?: string;
|
|
363
363
|
|
|
364
364
|
/** Stack trace (if failed) */
|
|
365
|
-
|
|
365
|
+
stackTrace?: string;
|
|
366
366
|
|
|
367
367
|
/** Metadata */
|
|
368
368
|
metadata?: Record<string, any>;
|
|
@@ -379,16 +379,16 @@ export interface FunnelResult<TEntity = any> {
|
|
|
379
379
|
matched: boolean;
|
|
380
380
|
|
|
381
381
|
/** Stage where entity was excluded (if excluded) */
|
|
382
|
-
|
|
382
|
+
excludedAtStage?: string;
|
|
383
383
|
|
|
384
384
|
/** Tags accumulated during processing */
|
|
385
|
-
|
|
385
|
+
accumulatedTags: string[];
|
|
386
386
|
|
|
387
387
|
/** Context accumulated during processing */
|
|
388
388
|
context: Record<string, any>;
|
|
389
389
|
|
|
390
390
|
/** Stage-by-stage results */
|
|
391
|
-
|
|
391
|
+
stageResults?: StageResult[];
|
|
392
392
|
}
|
|
393
393
|
|
|
394
394
|
/**
|
|
@@ -396,25 +396,25 @@ export interface FunnelResult<TEntity = any> {
|
|
|
396
396
|
*/
|
|
397
397
|
export interface StageResult {
|
|
398
398
|
/** Stage ID */
|
|
399
|
-
|
|
399
|
+
stageId: string;
|
|
400
400
|
|
|
401
401
|
/** Stage name */
|
|
402
|
-
|
|
402
|
+
stageName: string;
|
|
403
403
|
|
|
404
404
|
/** Whether rules matched */
|
|
405
405
|
matched: boolean;
|
|
406
406
|
|
|
407
407
|
/** Rule evaluation results */
|
|
408
|
-
|
|
408
|
+
ruleResults?: RuleResult[];
|
|
409
409
|
|
|
410
410
|
/** Action taken */
|
|
411
411
|
action: MatchAction | NoMatchAction;
|
|
412
412
|
|
|
413
413
|
/** Tags added */
|
|
414
|
-
|
|
414
|
+
tagsAdded?: string[];
|
|
415
415
|
|
|
416
416
|
/** Context added */
|
|
417
|
-
|
|
417
|
+
contextAdded?: Record<string, any>;
|
|
418
418
|
|
|
419
419
|
/** Whether entity was excluded */
|
|
420
420
|
excluded: boolean;
|
|
@@ -428,7 +428,7 @@ export interface StageResult {
|
|
|
428
428
|
*/
|
|
429
429
|
export interface RuleResult {
|
|
430
430
|
/** Rule field path */
|
|
431
|
-
|
|
431
|
+
fieldPath: string;
|
|
432
432
|
|
|
433
433
|
/** Rule operator */
|
|
434
434
|
operator: Operator;
|
|
@@ -437,7 +437,7 @@ export interface RuleResult {
|
|
|
437
437
|
value: any;
|
|
438
438
|
|
|
439
439
|
/** Actual value from entity */
|
|
440
|
-
|
|
440
|
+
actualValue: any;
|
|
441
441
|
|
|
442
442
|
/** Whether rule matched */
|
|
443
443
|
matched: boolean;
|
|
@@ -460,10 +460,10 @@ export type ValidOperators = Operator[];
|
|
|
460
460
|
*/
|
|
461
461
|
export interface FieldConstraints {
|
|
462
462
|
/** Minimum value (numbers, dates) */
|
|
463
|
-
|
|
463
|
+
minValue?: number | string;
|
|
464
464
|
|
|
465
465
|
/** Maximum value (numbers, dates) */
|
|
466
|
-
|
|
466
|
+
maxValue?: number | string;
|
|
467
467
|
|
|
468
468
|
/** Allowed values (enums) */
|
|
469
469
|
choices?: any[];
|
|
@@ -513,7 +513,7 @@ export interface FieldDefinition {
|
|
|
513
513
|
examples?: any[];
|
|
514
514
|
|
|
515
515
|
/** Related fields (for UI suggestions) */
|
|
516
|
-
|
|
516
|
+
relatedFields?: string[];
|
|
517
517
|
}
|
|
518
518
|
|
|
519
519
|
/**
|
|
@@ -523,13 +523,13 @@ export interface FieldDefinition {
|
|
|
523
523
|
*/
|
|
524
524
|
export interface FieldRegistry {
|
|
525
525
|
/** Entity type this registry is for */
|
|
526
|
-
|
|
526
|
+
entityType: string;
|
|
527
527
|
|
|
528
528
|
/** Available fields */
|
|
529
529
|
fields: FieldDefinition[];
|
|
530
530
|
|
|
531
531
|
/** Field lookup by name */
|
|
532
|
-
|
|
532
|
+
fieldMap?: Map<string, FieldDefinition>;
|
|
533
533
|
|
|
534
534
|
/** Categories (for UI grouping) */
|
|
535
535
|
categories?: string[];
|
|
@@ -573,7 +573,7 @@ export type FieldPath<T, Prefix extends string = ''> = {
|
|
|
573
573
|
*/
|
|
574
574
|
export type CreateFunnelInput<TEntity = any> = Omit<
|
|
575
575
|
Funnel<TEntity>,
|
|
576
|
-
'id' | '
|
|
576
|
+
'id' | 'createdAt' | 'updatedAt'
|
|
577
577
|
>;
|
|
578
578
|
|
|
579
579
|
/**
|
|
@@ -625,7 +625,7 @@ export function isStage<TEntity = any>(value: unknown): value is FunnelStage<TEn
|
|
|
625
625
|
typeof s.id === 'string' &&
|
|
626
626
|
typeof s.name === 'string' &&
|
|
627
627
|
typeof s.order === 'number' &&
|
|
628
|
-
['AND', 'OR'].includes(s.
|
|
628
|
+
['AND', 'OR'].includes(s.filterLogic) &&
|
|
629
629
|
Array.isArray(s.rules)
|
|
630
630
|
);
|
|
631
631
|
}
|
|
@@ -638,7 +638,7 @@ export function isFilterRule(value: unknown): value is FilterRule {
|
|
|
638
638
|
return (
|
|
639
639
|
typeof r === 'object' &&
|
|
640
640
|
r !== null &&
|
|
641
|
-
typeof r.
|
|
641
|
+
typeof r.fieldPath === 'string' &&
|
|
642
642
|
typeof r.operator === 'string' &&
|
|
643
643
|
r.value !== undefined
|
|
644
644
|
);
|
|
@@ -653,7 +653,7 @@ export function isFunnelRun(value: unknown): value is FunnelRun {
|
|
|
653
653
|
typeof r === 'object' &&
|
|
654
654
|
r !== null &&
|
|
655
655
|
typeof r.id === 'string' &&
|
|
656
|
-
typeof r.
|
|
656
|
+
typeof r.funnelId === 'string' &&
|
|
657
657
|
['pending', 'running', 'completed', 'failed', 'cancelled'].includes(r.status)
|
|
658
658
|
);
|
|
659
659
|
}
|
|
@@ -670,7 +670,7 @@ export function isFunnelResult<TEntity = any>(
|
|
|
670
670
|
r !== null &&
|
|
671
671
|
r.entity !== undefined &&
|
|
672
672
|
typeof r.matched === 'boolean' &&
|
|
673
|
-
Array.isArray(r.
|
|
673
|
+
Array.isArray(r.accumulatedTags)
|
|
674
674
|
);
|
|
675
675
|
}
|
|
676
676
|
|
|
@@ -758,8 +758,8 @@ export function isValidOperator(
|
|
|
758
758
|
export function validateFilterRule(rule: FilterRule): string[] {
|
|
759
759
|
const errors: string[] = [];
|
|
760
760
|
|
|
761
|
-
if (!rule.
|
|
762
|
-
errors.push('
|
|
761
|
+
if (!rule.fieldPath) {
|
|
762
|
+
errors.push('fieldPath is required');
|
|
763
763
|
}
|
|
764
764
|
|
|
765
765
|
if (!rule.operator) {
|
|
@@ -791,8 +791,8 @@ export function validateStage<TEntity = any>(
|
|
|
791
791
|
errors.push('order must be a number');
|
|
792
792
|
}
|
|
793
793
|
|
|
794
|
-
if (!['AND', 'OR'].includes(stage.
|
|
795
|
-
errors.push('
|
|
794
|
+
if (!['AND', 'OR'].includes(stage.filterLogic)) {
|
|
795
|
+
errors.push('filterLogic must be AND or OR');
|
|
796
796
|
}
|
|
797
797
|
|
|
798
798
|
if (!Array.isArray(stage.rules)) {
|