From a43b79b15aff0d89efbd508dec8e9d4de0c0a4db Mon Sep 17 00:00:00 2001 From: "slominskir-coding-agent[bot]" <335168828+slominskir-coding-agent[bot]@users.noreply.github.com> Date: Thu, 12 Nov 2026 08:29:37 -0500 Subject: [PATCH] Sort the directory by Ops Impact, highest first The directory, the app's first page, listed software by name, so its first page showed whatever starts with "a" and not the software that matters most to operations. List assessed software by Ops Impact from 5 Critical down, then by Attention from Severe down, then by name. Software nobody has assessed follows, by name: its Ops Impact is still the placeholder default of 3, which would otherwise mix it in with software graded Medium. The software ID ends the order, so pages stay fixed when two repositories hold software of the same name. Show Ops Impact as a column, left of SQA Level, since the order would otherwise follow a value the page does not show. The Sync page keeps its order by name, which its comparison of local and remote lists reads by. Co-Authored-By: Claude Fable 5.2 --- .../java/org/jlab/sim/SoftwareIT.java | 66 ++++++++++++++++++- .../sim/business/service/SoftwareService.java | 16 ++++- .../enumeration/SoftwareOrder.java | 12 ++++ .../presentation/controller/Directory.java | 1 + .../sim/presentation/controller/Sync.java | 2 + .../webapp/WEB-INF/tags/directory-page.tag | 9 ++- src/main/webapp/resources/css/directory.css | 3 +- 7 files changed, 102 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/jlab/sim/persistence/enumeration/SoftwareOrder.java diff --git a/src/integration/java/org/jlab/sim/SoftwareIT.java b/src/integration/java/org/jlab/sim/SoftwareIT.java index 5f68755..ce40999 100644 --- a/src/integration/java/org/jlab/sim/SoftwareIT.java +++ b/src/integration/java/org/jlab/sim/SoftwareIT.java @@ -85,6 +85,7 @@ void editorAddsEditsScoresAndRemovesSoftware() throws Exception { assertEquals("N", row.attr("data-archived")); assertEquals("3", row.attr("data-impact"), "new software scores Medium"); assertEquals("", row.attr("data-assessed-date"), "new software is not assessed"); + assertEquals("Not assessed", impactCell(row).text()); assertEquals("Not assessed", levelCell(row).text()); assertEquals("Not assessed", attentionCell(row).text()); @@ -142,6 +143,7 @@ void editorAddsEditsScoresAndRemovesSoftware() throws Exception { assertEquals("4", row.attr("data-attention"), "Impact 5 and Fragility 5 is Severe"); assertEquals(Sim.EDITOR, row.attr("data-assessed-by")); assertFalse(row.attr("data-assessed-date").isEmpty(), "assessed date"); + assertEquals("5 Critical", impactCell(row).text()); assertEquals("2 Significant*", levelCell(row).text(), "the override, marked"); assertEquals("Severe", attentionCell(row).text()); @@ -304,6 +306,42 @@ void directoryFiltersByLevelAttentionFragilityAndAssessment() throws Exception { assertTrue(message.contains("Not Assessed"), message); } + /** + * The directory lists the most important software first: assessed software by Ops Impact, then by + * Attention, then by name, and after it the software nobody has assessed. + */ + @Test + void directoryListsHighestOpsImpactFirst() throws Exception { + String token = Sim.token(Sim.EDITOR); + String prefix = newName(); + String unassessed = addNamed(token, prefix + "-a"); + String mediumRobust = addNamed(token, prefix + "-b"); + String critical = addNamed(token, prefix + "-c"); + String mediumFragile = addNamed(token, prefix + "-d"); + String criticalToo = addNamed(token, prefix + "-e"); + + // Impact, complexity and the other factors: attention is Low for 3 with 1, High for 3 with 5, + // and Moderate for 5 with 1 + scoreNamed(token, mediumRobust, 3, 1); + scoreNamed(token, critical, 5, 1); + scoreNamed(token, mediumFragile, 3, 5); + scoreNamed(token, criticalToo, 5, 1); + + String filter = "/directory?softwareName=" + Sim.encodeParam(prefix + "-*"); + + assertEquals( + List.of(critical, criticalToo, mediumFragile, mediumRobust, unassessed), + Sim.page(filter, null).select("tbody tr[data-name]").eachAttr("data-name"), + "Ops Impact, then Attention, then name, then unassessed"); + + assertEquals( + List.of(mediumFragile, mediumRobust), + Sim.page(filter + "&max=2&offset=2", null) + .select("tbody tr[data-name]") + .eachAttr("data-name"), + "the second page of two carries on in the same order"); + } + @Test void archivedSoftwareIsListedOnlyWhenIncluded() throws Exception { String name = newName(); @@ -356,6 +394,26 @@ private String newName() { return name; } + /** Adds an app of this name, which the test's cleanup removes, and returns the name. */ + private String addNamed(String token, String name) throws Exception { + added.add(name); + assertOk(add(token, name, "APP", "N")); + return name; + } + + /** Scores the software of this name: Code Complexity 1, and the other four factors the same. */ + private static void scoreNamed(String token, String name, int impact, int others) + throws Exception { + assertOk( + score( + token, + row(name).attr("data-id"), + impact, + 1, + others, + List.of(Map.entry("levelOverride", ""), Map.entry("levelNote", "")))); + } + private HttpResponse add( String token, String name, String type, String archived, String... topics) throws Exception { List> form = new ArrayList<>(); @@ -396,12 +454,16 @@ private static HttpResponse score( return Sim.post("/ajax/edit-software-risk", token, form); } + private static Element impactCell(Element row) { + return row.selectFirst("td.ops-impact"); + } + private static Element levelCell(Element row) { - return row.select("td").get(5); + return row.selectFirst("td.sqa-level"); } private static Element attentionCell(Element row) { - return row.select("td").get(6); + return row.selectFirst("td.attention"); } /** The software's row in the directory, or null if the directory doesn't list it. */ diff --git a/src/main/java/org/jlab/sim/business/service/SoftwareService.java b/src/main/java/org/jlab/sim/business/service/SoftwareService.java index 253788f..80eb30c 100644 --- a/src/main/java/org/jlab/sim/business/service/SoftwareService.java +++ b/src/main/java/org/jlab/sim/business/service/SoftwareService.java @@ -225,6 +225,7 @@ public List filterList( Attention attention, boolean fragile, Boolean assessed, + SoftwareOrder order, int offset, int max) { CriteriaBuilder cb = getEntityManager().getCriteriaBuilder(); @@ -253,9 +254,18 @@ public List filterList( } List orders = new ArrayList<>(); - Path p0 = root.get("name"); - Order o0 = cb.asc(p0); - orders.add(o0); + if (order == SoftwareOrder.IMPACT) { + // Unassessed software shows no impact or attention, so it comes after all that is assessed. + // Its inputs are still the defaults, which leaves it in name order. + orders.add( + cb.asc( + cb.selectCase().when(cb.isNull(root.get(ASSESSED_DATE)), 1).otherwise(0))); + orders.add(cb.desc(root.get("impact"))); + orders.add(cb.desc(root.get("attention"))); + } + orders.add(cb.asc(root.get("name"))); + // Two repositories may hold software of the same name, and pages need a fixed order + orders.add(cb.asc(root.get("softwareId"))); cq.orderBy(orders); return getEntityManager() .createQuery(cq) diff --git a/src/main/java/org/jlab/sim/persistence/enumeration/SoftwareOrder.java b/src/main/java/org/jlab/sim/persistence/enumeration/SoftwareOrder.java new file mode 100644 index 0000000..0d3edcc --- /dev/null +++ b/src/main/java/org/jlab/sim/persistence/enumeration/SoftwareOrder.java @@ -0,0 +1,12 @@ +package org.jlab.sim.persistence.enumeration; + +/** The order of a list of software. */ +public enum SoftwareOrder { + /** By name, A to Z */ + NAME, + /** + * The most important first: assessed software by Ops Impact from Critical down, then by Attention + * from Severe down, then by name; then the software nobody has assessed, by name + */ + IMPACT +} diff --git a/src/main/java/org/jlab/sim/presentation/controller/Directory.java b/src/main/java/org/jlab/sim/presentation/controller/Directory.java index ea563b7..08a3473 100644 --- a/src/main/java/org/jlab/sim/presentation/controller/Directory.java +++ b/src/main/java/org/jlab/sim/presentation/controller/Directory.java @@ -120,6 +120,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) attention, fragile, assessed, + SoftwareOrder.IMPACT, offset, maxPerPage); diff --git a/src/main/java/org/jlab/sim/presentation/controller/Sync.java b/src/main/java/org/jlab/sim/presentation/controller/Sync.java index 510cb2d..d54228e 100644 --- a/src/main/java/org/jlab/sim/presentation/controller/Sync.java +++ b/src/main/java/org/jlab/sim/presentation/controller/Sync.java @@ -20,6 +20,7 @@ import org.jlab.sim.persistence.entity.Repository; import org.jlab.sim.persistence.entity.Software; import org.jlab.sim.persistence.enumeration.Include; +import org.jlab.sim.persistence.enumeration.SoftwareOrder; import org.jlab.sim.persistence.view.SoftwareDiff; import org.jlab.smoothness.business.exception.UserFriendlyException; import org.jlab.smoothness.presentation.util.ParamConverter; @@ -100,6 +101,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) null, false, null, + SoftwareOrder.NAME, 0, Integer.MAX_VALUE); diff --git a/src/main/webapp/WEB-INF/tags/directory-page.tag b/src/main/webapp/WEB-INF/tags/directory-page.tag index a1b3a0c..e3425bc 100644 --- a/src/main/webapp/WEB-INF/tags/directory-page.tag +++ b/src/main/webapp/WEB-INF/tags/directory-page.tag @@ -160,6 +160,7 @@ Description Repo Maintainers + Ops Impact SQA Level Attention @@ -214,13 +215,19 @@
+ + + + Not assessed + + * Not assessed - + Not assessed diff --git a/src/main/webapp/resources/css/directory.css b/src/main/webapp/resources/css/directory.css index f1e9a3c..77157af 100644 --- a/src/main/webapp/resources/css/directory.css +++ b/src/main/webapp/resources/css/directory.css @@ -13,7 +13,8 @@ td:nth-child(3) { td.maintainers { white-space: nowrap; } -/* Keep "2 Significant" on one line; "Not assessed" may wrap, as under Attention */ +/* Keep "5 Critical" and "2 Significant" on one line; "Not assessed" may wrap, as under Attention */ +td.ops-impact .risk-dialog-opener:not(.not-assessed), td.sqa-level .risk-dialog-opener:not(.not-assessed) { white-space: nowrap; }