@remit/ui 0.0.61 → 0.0.63
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/components/blocked-reason.render.test.ts +44 -0
- package/src/components/blocked-reason.tsx +41 -0
- package/src/components/filter-rule.ts +10 -0
- package/src/components/message-list-pane.tsx +32 -4
- package/src/components/mobile-search-view.stories.tsx +7 -7
- package/src/components/search-results.render.test.ts +4 -3
- package/src/components/search-results.stories.tsx +6 -3
- package/src/components/search-results.tsx +35 -25
- package/src/components/selection-top-bar.stories.tsx +4 -37
- package/src/components/selection-top-bar.tsx +11 -21
- package/src/components/selection-wizard.render.test.ts +104 -12
- package/src/components/selection-wizard.tsx +90 -17
- package/src/index.ts +11 -1
- package/src/lib/search-rule.test.ts +22 -21
- package/src/lib/search-rule.ts +14 -32
- package/src/lib/wizard-steps.test.ts +80 -1
- package/src/lib/wizard-steps.ts +83 -11
package/src/lib/wizard-steps.ts
CHANGED
|
@@ -70,12 +70,21 @@ const VERB_COPY: Record<Verb, VerbCopy> = {
|
|
|
70
70
|
export const verbCopy = (verb: Verb): VerbCopy => VERB_COPY[verb];
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
|
-
*
|
|
74
|
-
* `RuleMatchMode` values; `selected` is not one of them
|
|
75
|
-
* at all — it is the bounded list of ticked message
|
|
76
|
-
* stands in for.
|
|
73
|
+
* The three choices the match step offers a ticked selection. The two widened
|
|
74
|
+
* doors are the shipped `RuleMatchMode` values; `selected` is not one of them
|
|
75
|
+
* and is not a match mode at all — it is the bounded list of ticked message
|
|
76
|
+
* ids, which no predicate stands in for.
|
|
77
77
|
*/
|
|
78
|
-
export type
|
|
78
|
+
export type MatchDoor = "selected" | RuleMatchMode;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* What the action is applied to. Beside the three doors sits `escalated`: the
|
|
82
|
+
* selection the list escalated to a predicate, every message matching the query
|
|
83
|
+
* it is showing rather than the rows on screen. It is offered no doors, because
|
|
84
|
+
* the predicate is already the match and there is nothing left to widen; the
|
|
85
|
+
* step names what it covers instead.
|
|
86
|
+
*/
|
|
87
|
+
export type MatchMode = MatchDoor | "escalated";
|
|
79
88
|
|
|
80
89
|
export type StepId =
|
|
81
90
|
| "match"
|
|
@@ -279,6 +288,12 @@ export const stepBlockedReason = (
|
|
|
279
288
|
* Where the run step lands. Three of these never reach a job: a filter saved
|
|
280
289
|
* with nothing to back-apply, a back-apply whose request never started, and a
|
|
281
290
|
* create that failed outright.
|
|
291
|
+
*
|
|
292
|
+
* `backApplyFailed` and `runStopped` are two different endings and must not be
|
|
293
|
+
* confused. A back-apply that failed was run by the server, which reports how
|
|
294
|
+
* many messages it could not apply the rule to. A run that stopped hit an
|
|
295
|
+
* infrastructure failure part-way: the batches it never reached were never
|
|
296
|
+
* sent, so nothing rejected them and nothing happened to them.
|
|
282
297
|
*/
|
|
283
298
|
export type RunState =
|
|
284
299
|
| "saving"
|
|
@@ -287,6 +302,7 @@ export type RunState =
|
|
|
287
302
|
| "backApplyFailed"
|
|
288
303
|
| "backApplyStartFailed"
|
|
289
304
|
| "filterSaved"
|
|
305
|
+
| "runStopped"
|
|
290
306
|
| "commitFailed";
|
|
291
307
|
|
|
292
308
|
/**
|
|
@@ -358,7 +374,8 @@ export const runCopy = ({
|
|
|
358
374
|
showProgress:
|
|
359
375
|
state === "backApplyRunning" ||
|
|
360
376
|
state === "backApplyComplete" ||
|
|
361
|
-
state === "backApplyFailed"
|
|
377
|
+
state === "backApplyFailed" ||
|
|
378
|
+
state === "runStopped",
|
|
362
379
|
failureListLabel: `Not ${done}`,
|
|
363
380
|
};
|
|
364
381
|
|
|
@@ -399,7 +416,7 @@ export const runCopy = ({
|
|
|
399
416
|
title: standing
|
|
400
417
|
? "Rule saved — some mail stayed put"
|
|
401
418
|
: `Not everything was ${done}`,
|
|
402
|
-
detail: `${applied} of ${matched} ${done} ·
|
|
419
|
+
detail: `${applied} of ${matched} ${done} · the mail server rejected ${failed}.${
|
|
403
420
|
standing
|
|
404
421
|
? " The rule itself is saved and keeps working on new mail."
|
|
405
422
|
: ""
|
|
@@ -409,6 +426,16 @@ export const runCopy = ({
|
|
|
409
426
|
retryLabel: `Retry ${failed}`,
|
|
410
427
|
};
|
|
411
428
|
}
|
|
429
|
+
if (state === "runStopped") {
|
|
430
|
+
return {
|
|
431
|
+
...shared,
|
|
432
|
+
title: `Stopped after ${applied}`,
|
|
433
|
+
detail: `${applied} of ${matched} ${done}. The run stopped before it reached the rest, so nothing was sent for them and nothing has happened to them.`,
|
|
434
|
+
tone: "warning",
|
|
435
|
+
dismissLabel: "Close",
|
|
436
|
+
retryLabel: `Retry ${failed}`,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
412
439
|
if (state === "backApplyStartFailed") {
|
|
413
440
|
return {
|
|
414
441
|
...shared,
|
|
@@ -454,7 +481,7 @@ export const clauseSentence = (
|
|
|
454
481
|
|
|
455
482
|
/** What a match door is called, with the ticked count the widen anchors on. */
|
|
456
483
|
export const matchDoorLabel = (
|
|
457
|
-
mode:
|
|
484
|
+
mode: MatchDoor,
|
|
458
485
|
selectedCount: number,
|
|
459
486
|
): string => {
|
|
460
487
|
if (mode === "selected") return `These ${selectedCount} messages`;
|
|
@@ -463,16 +490,48 @@ export const matchDoorLabel = (
|
|
|
463
490
|
};
|
|
464
491
|
|
|
465
492
|
/** One line saying what a match door actually does, so the choice is never a guess. */
|
|
466
|
-
export const matchDoorHint = (mode:
|
|
493
|
+
export const matchDoorHint = (mode: MatchDoor): string =>
|
|
467
494
|
mode === "selected"
|
|
468
495
|
? "Only the messages ticked in the list."
|
|
469
496
|
: matchModeHint(mode);
|
|
470
497
|
|
|
498
|
+
/**
|
|
499
|
+
* What an escalated predicate covers, in the words the list already used to
|
|
500
|
+
* escalate it — `matching "npm"`. Named where the doors would be, because a
|
|
501
|
+
* predicate that reaches past the loaded rows has nothing left to widen.
|
|
502
|
+
*/
|
|
503
|
+
export const escalatedMatchLabel = (scope: string): string =>
|
|
504
|
+
`Every message ${scope}`;
|
|
505
|
+
|
|
506
|
+
/** The fallback when the list hands over no words for its predicate. */
|
|
507
|
+
export const ESCALATED_SCOPE_FALLBACK = "the list is showing";
|
|
508
|
+
|
|
509
|
+
/** Why the escalated match step states one thing instead of offering three. */
|
|
510
|
+
export const ESCALATED_MATCH_HINT =
|
|
511
|
+
"This reaches every match on the mail server, including the ones the list has not loaded. The search is already the match, so there is nothing to widen.";
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* What the count on an escalated review does and does not promise (#109). The
|
|
515
|
+
* predicate is resolved once for the count and again for the run, so the run
|
|
516
|
+
* covers whatever matches at the moment it runs — which can be more than the
|
|
517
|
+
* number beside it.
|
|
518
|
+
*/
|
|
519
|
+
export const ESCALATED_REVIEW_WARNING =
|
|
520
|
+
"This covers messages not shown in the list, and anything else matching by the time it runs.";
|
|
521
|
+
|
|
471
522
|
export interface MatchDescription {
|
|
472
523
|
mode: MatchMode;
|
|
473
524
|
selectedCount: number;
|
|
474
525
|
clauses: readonly RuleClause[];
|
|
475
526
|
matchOperator: MatchOperator;
|
|
527
|
+
/** What an escalated predicate covers, in the list's own words. */
|
|
528
|
+
escalatedScope?: string;
|
|
529
|
+
/**
|
|
530
|
+
* The server's count of that predicate. Stated with the match rather than
|
|
531
|
+
* left to the sample's footer: an escalated selection is the one match whose
|
|
532
|
+
* size is the whole reason to look before it runs.
|
|
533
|
+
*/
|
|
534
|
+
escalatedCount?: number;
|
|
476
535
|
}
|
|
477
536
|
|
|
478
537
|
/** The match on the review screen's labelled list — short enough for one row. */
|
|
@@ -481,10 +540,15 @@ export const matchSummary = ({
|
|
|
481
540
|
selectedCount,
|
|
482
541
|
clauses,
|
|
483
542
|
matchOperator,
|
|
484
|
-
|
|
485
|
-
|
|
543
|
+
escalatedScope,
|
|
544
|
+
}: MatchDescription): string => {
|
|
545
|
+
if (mode === "escalated") {
|
|
546
|
+
return escalatedMatchLabel(escalatedScope ?? ESCALATED_SCOPE_FALLBACK);
|
|
547
|
+
}
|
|
548
|
+
return mode === "properties"
|
|
486
549
|
? clauseSentence(clauses, matchOperator)
|
|
487
550
|
: matchDoorLabel(mode, selectedCount);
|
|
551
|
+
};
|
|
488
552
|
|
|
489
553
|
/** The match inside the review screen's one sentence, in the object position. */
|
|
490
554
|
export const matchPhrase = ({
|
|
@@ -492,8 +556,16 @@ export const matchPhrase = ({
|
|
|
492
556
|
selectedCount,
|
|
493
557
|
clauses,
|
|
494
558
|
matchOperator,
|
|
559
|
+
escalatedScope,
|
|
560
|
+
escalatedCount,
|
|
495
561
|
}: MatchDescription): string => {
|
|
496
562
|
if (mode === "selected") return `${selectedCount} messages`;
|
|
563
|
+
if (mode === "escalated") {
|
|
564
|
+
const scope = escalatedScope ?? ESCALATED_SCOPE_FALLBACK;
|
|
565
|
+
return escalatedCount === undefined
|
|
566
|
+
? `every message ${scope}`
|
|
567
|
+
: `all ${escalatedCount.toLocaleString()} messages ${scope}`;
|
|
568
|
+
}
|
|
497
569
|
if (mode === "similar") {
|
|
498
570
|
return `mail ${widenChipLabel({ anchorCount: selectedCount }).toLowerCase()}`;
|
|
499
571
|
}
|