fix(app): Backups Setup and Needs attention agree #32
No files matched your search
@@ -69,10 +69,11 @@ public final class BackupsHomeView
|
||||
public static String render(BackupsHomeData data, String pathPrefix)
|
||||
{
|
||||
StringBuilder html = new StringBuilder("<div class=\"backups-home\" style=\"" + ROOT + "\">");
|
||||
List<BackupsSetupStatus.Item> setupItems = BackupsSetupStatus.load(data.plans());
|
||||
planStatus(html, data, pathPrefix);
|
||||
setup(html, data, pathPrefix);
|
||||
setup(html, setupItems, pathPrefix);
|
||||
html.append("<div style=\"").append(GRID).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<BackupsSetupStatus.Item> items, String prefix)
|
||||
{
|
||||
List<BackupsSetupStatus.Item> items = BackupsSetupStatus.load(data.plans());
|
||||
open(html, "Setup");
|
||||
long todo = items.stream().filter(i -> i.state() == BackupsSetupStatus.State.TODO).count();
|
||||
html.append("<p style=\"margin:0 0 .6rem;font-size:.9rem;\">")
|
||||
@@ -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<BackupsSetupStatus.Item> setupItems, String prefix)
|
||||
{
|
||||
open(html, "Needs attention");
|
||||
List<QRecord> plans = data.plansNeedingAttention();
|
||||
if(data.plans().isEmpty())
|
||||
List<BackupsSetupStatus.Item> setupTodo = setupItems.stream().filter(i -> i.state() == BackupsSetupStatus.State.TODO).toList();
|
||||
List<QRecord> 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("<ul style=\"").append(ITEMS).append("\">");
|
||||
for(BackupsSetupStatus.Item item : setupTodo)
|
||||
{
|
||||
html.append("<li>Setup: <a style=\"").append(LINK).append("\" href=\"").append(esc(prefix + "/" + item.tableName())).append("\">").append(esc(item.label())).append("</a>: ")
|
||||
.append(esc(item.detail())).append("</li>");
|
||||
}
|
||||
for(QRecord plan : plans)
|
||||
{
|
||||
html.append("<li>").append(recordLink(prefix, BackupsTables.PLAN, plan.getValue("id"), plan.getValueString("name")))
|
||||
|
||||
@@ -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: <a").contains("No plan: nothing is backed up.").doesNotContain("Nothing needs attention").contains("No test restore yet")
|
||||
.contains("Nothing has run yet").contains("Trends appear after the first successful run").contains("Retention starts once runs exist");
|
||||
assertThat(BackupsHomeView.renderHealthCard(new BackupsHomeData(List.of(), List.of(), List.of(), List.of(), List.of(), Instant.now()), "/app")).contains("No backup plans yet");
|
||||
}
|
||||
@@ -121,6 +121,45 @@ class BackupsHomeTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** The Setup card and the Needs attention card never contradict each other: a plan
|
||||
** whose storage aliases do not resolve is a Setup fault, and it is listed under
|
||||
** Needs attention too, with the same count.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testUnresolvedAliasAppearsInSetupAndNeedsAttention() throws Exception
|
||||
{
|
||||
BackupsRuntime.getConfig().withStorageResolver(alias -> 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<BackupsSetupStatus.Item> 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: <a").contains(item.label());
|
||||
}
|
||||
assertThat(attention.split("<li>", -1).length - 1).isEqualTo(setupTodo.size() + data.plansNeedingAttention().size() + data.failedRuns().size());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** The links follow the configured table path prefix.
|
||||
*******************************************************************************/
|
||||
|
||||
Reference in new issue
Block a user