R-241 part 5: escalating reminders, and operator levers for a running countdown
REMINDERS (SEC 2.3). The offer epoch now stamps when it began, and the undecided reminder escalates in EMPHASIS at 1, 3, 7 and 14 days. THE READING IS STATED BECAUSE THE SPEC IS AMBIGUOUS, and it is written into the code where it can be corrected. For an ABANDONING box, 5/3/1 are unambiguously days REMAINING before a deletion. An undecided box has no deadline - nothing counts down to anything, because SEC 7.5 deliberately does NOT auto-abandon - so 14/7/3/1 cannot be "remaining" and are taken as days ELAPSED, with the wording firming up rather than the bar appearing and disappearing. If the operator meant something else, one function changes. The stamp is re-set on every entry into the offered state, so a box that settles and is later rebuilt starts its ladder again instead of inheriting an old one. OPERATOR LEVERS (SEC 7.5). --abandon-status, --abandon-extend=N and --abandon-stop on the controller CLI, beside the existing operator subcommands. They exist because the path that ACTUALLY happens is the customer telephoning, and support needs something to press. They live on the CLI and not in the customer UI deliberately: extending a deletion the customer asked for is an operator judgement, and a customer who wants it stopped already has the self-service route - they recover with their code, which cancels it. BOTH REFUSE RATHER THAN NO-OP, in two situations: when no countdown is running, and when the store has already been deleted. A silent success is the thing an operator most easily mistakes for "handled" - they would tell the customer their data was safe when it is gone. Pinned by two tests. --abandon-extend counts from NOW, not from the old due date, and a test proves the old date passes without deleting anything. Green: go build, go vet, go test ./... all pass; controller gates OK.
This commit is contained in:
@@ -62,7 +62,7 @@ func (s *Server) recoveryOfferEpoch() settings.RecoveryOfferView {
|
||||
if s.settings == nil {
|
||||
return settings.RecoveryOfferView{}
|
||||
}
|
||||
v, err := s.settings.SyncRecoveryOfferEpoch(s.recoveryOffer())
|
||||
v, err := s.settings.SyncRecoveryOfferEpoch(s.recoveryOffer(), s.recoveryNow())
|
||||
if err != nil {
|
||||
s.logger.Printf("[WARN] [web] recovery: could not persist the offer epoch: %v", err)
|
||||
}
|
||||
@@ -607,6 +607,17 @@ func (s *Server) addRecoveryBanner(data map[string]interface{}, r *http.Request)
|
||||
if data["CSRFField"] == nil {
|
||||
data["CSRFField"] = s.csrfField(r)
|
||||
}
|
||||
// §2.3 — THE UNDECIDED LADDER. A customer who never decides is reminded with escalating
|
||||
// EMPHASIS as the situation ages: marks at 1, 3, 7 and 14 days since this epoch began.
|
||||
//
|
||||
// ⚠ THE READING IS STATED BECAUSE THE SPEC IS AMBIGUOUS. For an ABANDONING box, 5/3/1 are
|
||||
// unambiguously days REMAINING before a deletion. An undecided box has no deadline — nothing is
|
||||
// counting down to anything, because §7.5 deliberately does NOT auto-abandon — so 14/7/3/1 cannot
|
||||
// be "remaining" and are taken as days ELAPSED, with the wording escalating rather than the bar
|
||||
// appearing and disappearing. If the operator meant something else, this is the line to change.
|
||||
dw := s.recoveryDaysWaiting()
|
||||
data["RecoveryDaysWaiting"] = dw
|
||||
data["RecoveryReminderTier"] = RecoveryReminderTier(dw)
|
||||
// While a countdown runs the bar counts it down instead of asking the same question — and the
|
||||
// reminder opt-out is deliberately NOT offered there: a deletion date is not something to silence.
|
||||
if s.backupMgr != nil {
|
||||
@@ -626,3 +637,36 @@ func (s *Server) addRecoveryBanner(data map[string]interface{}, r *http.Request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// recoveryDaysWaiting returns whole days since the current offered epoch began (0 when unknown).
|
||||
// It drives the escalating emphasis of the undecided reminder — see addRecoveryBanner.
|
||||
func (s *Server) recoveryDaysWaiting() int {
|
||||
if s.settings == nil {
|
||||
return 0
|
||||
}
|
||||
v := s.settings.GetRecoveryOfferView()
|
||||
if v.Since == "" {
|
||||
return 0
|
||||
}
|
||||
since, err := time.Parse(time.RFC3339, v.Since)
|
||||
if err != nil {
|
||||
return 0 // an unparseable stamp means "we do not know", never "it has been ages"
|
||||
}
|
||||
d := int(s.recoveryNow().Sub(since) / (24 * time.Hour))
|
||||
if d < 0 {
|
||||
return 0
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// RecoveryReminderTier maps days-waiting to the escalation marks in §2.3. It returns the HIGHEST
|
||||
// mark reached, so the copy can firm up without the bar flickering in and out.
|
||||
func RecoveryReminderTier(daysWaiting int) int {
|
||||
tier := 0
|
||||
for _, mark := range []int{1, 3, 7, 14} {
|
||||
if daysWaiting >= mark {
|
||||
tier = mark
|
||||
}
|
||||
}
|
||||
return tier
|
||||
}
|
||||
|
||||
@@ -141,6 +141,12 @@
|
||||
<span class="alert-message">
|
||||
{{if .RecoveryAbandonDays}}
|
||||
A korábbi távoli mentéseidet <strong>{{.RecoveryAbandonDays}} nap múlva</strong> ({{.RecoveryAbandonDate}}) véglegesen töröljük, a kérésed szerint. Addig még visszaszerezheted őket a helyreállítási kóddal.
|
||||
{{else if ge .RecoveryReminderTier 14}}
|
||||
<strong>Két hete</strong> várnak rád a korábbi távoli mentéseid, és még nem adtad meg a helyreállítási kódodat. Amíg nem teszed, ezekhez a mentésekhez nem férsz hozzá.
|
||||
{{else if ge .RecoveryReminderTier 7}}
|
||||
Már <strong>egy hete</strong> megvannak a korábbi távoli mentéseid, de a helyreállítási kódod nélkül nem tudjuk megnyitni őket.
|
||||
{{else if ge .RecoveryReminderTier 3}}
|
||||
A korábbi távoli mentéseid megvannak — a megnyitásukhoz a helyreállítási kódod szükséges.
|
||||
{{else}}
|
||||
A korábbi távoli mentéseid megvannak, de ehhez a géphez a helyreállítási kódod szükséges.
|
||||
{{end}}
|
||||
|
||||
Reference in New Issue
Block a user