diff --git a/src/main/java/com/kingsrook/qbits/backups/app/BackupsHomeView.java b/src/main/java/com/kingsrook/qbits/backups/app/BackupsHomeView.java index cddfac4..796c814 100644 --- a/src/main/java/com/kingsrook/qbits/backups/app/BackupsHomeView.java +++ b/src/main/java/com/kingsrook/qbits/backups/app/BackupsHomeView.java @@ -69,10 +69,11 @@ public final class BackupsHomeView public static String render(BackupsHomeData data, String pathPrefix) { StringBuilder html = new StringBuilder("
"); + List setupItems = BackupsSetupStatus.load(data.plans()); planStatus(html, data, pathPrefix); - setup(html, data, pathPrefix); + setup(html, setupItems, pathPrefix); html.append("
"); - needsAttention(html, data, pathPrefix); + needsAttention(html, data, setupItems, pathPrefix); latestTestRestore(html, data, pathPrefix); recentActivity(html, data, pathPrefix); trend(html, data); @@ -144,9 +145,8 @@ public final class BackupsHomeView ** The Setup card: what a working backup needs, whether each part is in place, ** and the next step, linked to its table. *******************************************************************************/ - private static void setup(StringBuilder html, BackupsHomeData data, String prefix) + private static void setup(StringBuilder html, List items, String prefix) { - List items = BackupsSetupStatus.load(data.plans()); open(html, "Setup"); long todo = items.stream().filter(i -> i.state() == BackupsSetupStatus.State.TODO).count(); html.append("

") @@ -185,23 +185,27 @@ public final class BackupsHomeView /******************************************************************************* - ** Needs attention. + ** Needs attention: every Setup item that needs setup (the same list the Setup + ** card counts), plus plans in a problem state and recent failed runs, so the two + ** cards never disagree. *******************************************************************************/ - private static void needsAttention(StringBuilder html, BackupsHomeData data, String prefix) + private static void needsAttention(StringBuilder html, BackupsHomeData data, List setupItems, String prefix) { open(html, "Needs attention"); - List plans = data.plansNeedingAttention(); - if(data.plans().isEmpty()) + List setupTodo = setupItems.stream().filter(i -> i.state() == BackupsSetupStatus.State.TODO).toList(); + List plans = data.plansNeedingAttention(); + if(setupTodo.isEmpty() && plans.isEmpty() && data.failedRuns().isEmpty()) { - empty(html, "Nothing to check yet: no plans."); - } - else if(plans.isEmpty() && data.failedRuns().isEmpty()) - { - empty(html, "Nothing needs attention."); + empty(html, data.plans().isEmpty() ? "Nothing to check yet: no plans." : "Nothing needs attention."); } else { html.append("

    "); + for(BackupsSetupStatus.Item item : setupTodo) + { + html.append("
  • Setup: ").append(esc(item.label())).append(": ") + .append(esc(item.detail())).append("
  • "); + } for(QRecord plan : plans) { html.append("
  • ").append(recordLink(prefix, BackupsTables.PLAN, plan.getValue("id"), plan.getValueString("name"))) diff --git a/src/test/java/com/kingsrook/qbits/backups/app/BackupsHomeTest.java b/src/test/java/com/kingsrook/qbits/backups/app/BackupsHomeTest.java index 23a9d56..550cff3 100644 --- a/src/test/java/com/kingsrook/qbits/backups/app/BackupsHomeTest.java +++ b/src/test/java/com/kingsrook/qbits/backups/app/BackupsHomeTest.java @@ -88,7 +88,7 @@ class BackupsHomeTest void testEmptyStates() { String html = BackupsHomeView.render(new BackupsHomeData(List.of(), List.of(), List.of(), List.of(), List.of(), Instant.now()), "/app"); - assertThat(html).contains("No backup plans yet").contains("Nothing to check yet").contains("No test restore yet") + assertThat(html).contains("No backup plans yet").contains("Setup: BackupsTestUtils.BACKUPS); + new BackupCatalog().insert(BackupsTables.PLAN, new LinkedHashMap<>(Map.of("name", "Offsite", "planKey", "offsite", "isActive", true, "scopeType", "INSTANCE", + "storageBucketAlias", "backupsAws", "pruneBucketAlias", "backupsAwsPrune"))); + + ////////////////////////////////////////////////////////////////////////////// + // the aliases resolved when the plan was saved; now the bucket is gone // + ////////////////////////////////////////////////////////////////////////////// + BackupsRuntime.getConfig().withStorageResolver(alias -> "backups".equals(alias) ? BackupsTestUtils.BACKUPS : null); + + BackupsHomeData data = BackupsHomeData.load(); + List setupTodo = BackupsSetupStatus.load(data.plans()).stream().filter(i -> i.state() == BackupsSetupStatus.State.TODO).toList(); + assertThat(setupTodo).extracting(BackupsSetupStatus.Item::label).contains("Storage"); + + String html = BackupsHomeView.render(data, "/app"); + String attention = html.substring(html.indexOf(">Needs attention<")); + attention = attention.substring(0, attention.indexOf("All runs")); + assertThat(attention).doesNotContain("Nothing needs attention").contains("backupsAws").contains("backupsAwsPrune"); + + /////////////////////////////////////////////////////////////////////////////// + // every Setup TODO is listed under Needs attention (counts agree: 2 and 2) // + /////////////////////////////////////////////////////////////////////////////// + assertThat(html).contains(setupTodo.size() + " items need attention."); + for(BackupsSetupStatus.Item item : setupTodo) + { + assertThat(attention).contains("Setup: ", -1).length - 1).isEqualTo(setupTodo.size() + data.plansNeedingAttention().size() + data.failedRuns().size()); + } + + + /******************************************************************************* ** The links follow the configured table path prefix. *******************************************************************************/