fix: context-free attachment steps (#20) and no fixed home column widths (#21) #22

Merged
KofTwentyTwo merged 1 commits from fix/issues-20-21 into develop 2026-10-14 19:59:19 +00:00
4 changed files with 80 additions and 12 deletions

No files matched your search

@@ -200,7 +200,7 @@ public final class CollaborationHome
row.put("records", tag.getValueInteger("usageCount") == null ? 0 : tag.getValueInteger("usageCount"));
rows.add(row);
}
return (table("Tags", NO_TAGS, rows, column("Label", "label", "3fr", "left"), column("Table", "table", "2fr", "left"), column("Records", "records", "1fr", "right")));
return (table("Tags", NO_TAGS, rows, column("Label", "label", "left"), column("Table", "table", "left"), column("Records", "records", "right")));
}
@@ -224,8 +224,8 @@ public final class CollaborationHome
row.put("indexState", friendly(definition.getValueString("indexState")));
rows.add(row);
}
return (table("Custom fields", NO_FIELDS, rows, column("Name", "name", "3fr", "left"), column("Table", "table", "2fr", "left"),
column("Status", "status", "1fr", "left"), column("Index state", "indexState", "1fr", "left")));
return (table("Custom fields", NO_FIELDS, rows, column("Name", "name", "left"), column("Table", "table", "left"),
column("Status", "status", "left"), column("Index state", "indexState", "left")));
}
@@ -249,7 +249,7 @@ public final class CollaborationHome
rows.add(row);
});
}
return (table("Who's here", admin ? NOBODY_HERE : PRESENCE_ADMIN_ONLY, rows, column("Table", "table", "3fr", "left"), column("People", "viewers", "1fr", "right")));
return (table("Who's here", admin ? NOBODY_HERE : PRESENCE_ADMIN_ONLY, rows, column("Table", "table", "left"), column("People", "viewers", "right")));
}
@@ -267,9 +267,12 @@ public final class CollaborationHome
/*******************************************************************************
** A plain column; the frontend styles it.
*******************************************************************************/
private static TableData.Column column(String header, String accessor, String width, String align)
private static TableData.Column column(String header, String accessor, String align)
{
return (new TableData.Column("default", header, accessor, width, align));
//////////////////////////////////////////////////////////////////
// no width: frontends size the columns to fit narrow (390 px) cards //
//////////////////////////////////////////////////////////////////
return (new TableData.Column("default", header, accessor, null, align));
}
@@ -92,7 +92,7 @@ public final class DeleteAttachmentsProcess
*******************************************************************************/
public static class Review extends AbstractTransformStep implements ProcessSummaryProviderInterface
{
private final AttachmentsService service;
private AttachmentsService service;
private final ProcessSummaryLine selected = line(Status.OK, "will be deleted", "deleted");
private final ProcessSummaryLine skipped = line(Status.WARNING, "cannot be deleted (skipped)", "skipped");
@@ -101,7 +101,11 @@ public final class DeleteAttachmentsProcess
*******************************************************************************/
public Review()
{
this(new AttachmentsService());
//////////////////////////////////////////////////////////////////////////
// no QContext here: QQQ constructs steps during context-free instance //
// validation, so the service is created on first use (in a run) //
//////////////////////////////////////////////////////////////////////////
this(null);
}
/*******************************************************************************
@@ -112,6 +116,20 @@ public final class DeleteAttachmentsProcess
this.service = service;
}
/*******************************************************************************
** The attachments service, created on first use inside a run (QContext exists).
*******************************************************************************/
private AttachmentsService service()
{
if(service == null)
{
service = new AttachmentsService();
}
return (service);
}
/*******************************************************************************
** Dry run only; no object or row mutations.
*******************************************************************************/
@@ -122,7 +140,7 @@ public final class DeleteAttachmentsProcess
{
try
{
QRecord checked = service.requireMutable(row.getValue("id"));
QRecord checked = service().requireMutable(row.getValue("id"));
output.getRecords().add(checked);
selected.incrementCountAndAddPrimaryKey(checked.getValue("id"));
}
@@ -151,7 +169,7 @@ public final class DeleteAttachmentsProcess
*******************************************************************************/
public static class Delete extends AbstractLoadStep implements ProcessSummaryProviderInterface
{
private final AttachmentsService service;
private AttachmentsService service;
private final ProcessSummaryLine deleted = line(Status.OK, "will be deleted", "deleted");
private final ProcessSummaryLine failed = line(Status.ERROR, "could not be deleted", "could not be deleted (retry required)");
@@ -160,7 +178,11 @@ public final class DeleteAttachmentsProcess
*******************************************************************************/
public Delete()
{
this(new AttachmentsService());
//////////////////////////////////////////////////////////////////////////
// no QContext here: QQQ constructs steps during context-free instance //
// validation, so the service is created on first use (in a run) //
//////////////////////////////////////////////////////////////////////////
this(null);
}
/*******************************************************************************
@@ -171,6 +193,20 @@ public final class DeleteAttachmentsProcess
this.service = service;
}
/*******************************************************************************
** The attachments service, created on first use inside a run (QContext exists).
*******************************************************************************/
private AttachmentsService service()
{
if(service == null)
{
service = new AttachmentsService();
}
return (service);
}
/*******************************************************************************
** Count success only after both storage and row deletion succeed.
*******************************************************************************/
@@ -181,7 +217,7 @@ public final class DeleteAttachmentsProcess
{
try
{
service.delete(List.of(row.getValue("id")));
service().delete(List.of(row.getValue("id")));
deleted.incrementCountAndAddPrimaryKey(row.getValue("id"));
}
catch(QException | RuntimeException expected)
@@ -228,6 +228,21 @@ class CollaborationTest
/*******************************************************************************
** Home tables leave column widths to the frontend, so narrow cards wrap
** instead of scrolling sideways (issue #21).
*******************************************************************************/
@Test
void homeTablesHaveNoFixedColumnWidths() throws Exception
{
for(TableData table : java.util.List.of(CollaborationHome.tags(), CollaborationHome.fields(), CollaborationHome.presence()))
{
assertThat(table.getColumns()).isNotEmpty().allSatisfy(column -> assertThat(column.getWidth()).isNull());
}
}
/*******************************************************************************
** Empty states explain what has not been populated.
*******************************************************************************/
@@ -102,6 +102,20 @@ public class AttachmentsServiceTest extends PersonalServiceFixture
/*******************************************************************************
** Upload, metadata, newest-first list, signed download and deletion round trip.
*******************************************************************************/
/*******************************************************************************
** The action steps can be constructed without a QContext, as QQQ does during
** instance validation (issue #20); the service is created on first use.
*******************************************************************************/
@Test
void stepsConstructWithoutContext()
{
QContext.clear();
assertThat(new DeleteAttachmentsProcess.Review()).isNotNull();
assertThat(new DeleteAttachmentsProcess.Delete()).isNotNull();
}
@Test
void roundTrip() throws Exception
{