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; }