Files
qbit-edi/src/main/java/com/kingsrook/qbits/edi/processes/InstallSampleMapsProcess.java
T
james.maesandClaude Opus 5.5 a1914f7553 feat: table help, Setup status, Install sample maps, tables guardrail
Every edi_* table and the app get help bundle text saying how rows get
created and the setup order. The home gets a Setup status section. A new
Install sample maps table action adds the bundled synthetic maps as
inactive, unbound drafts (insert-only by name).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:17:41 -05:00

211 lines
11 KiB
Java

/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.edi.processes;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qbits.edi.map.SampleMaps;
import com.kingsrook.qbits.edi.support.SystemWrites;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine;
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLineInterface;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.processes.Status;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.PermissionLevel;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.AbstractExtractStep;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.AbstractLoadStep;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.AbstractTransformStep;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ProcessSummaryProviderInterface;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
/*******************************************************************************
** "Install sample maps": a table action on edi_map that adds the bundled
** synthetic sample maps whose name does not exist yet. Standard streamed ETL:
** Review lists what will be added, Result what was added. Insert-only by map
** name, so existing maps and edits are never touched. Installed maps are
** drafts with no partner and no agreement document, so nothing changes how a
** document is processed until an administrator activates and binds one.
*******************************************************************************/
public final class InstallSampleMapsProcess
{
public static final String NAME = "edi_map_installSamples";
/*******************************************************************************
** Utility class.
*******************************************************************************/
private InstallSampleMapsProcess()
{
}
/*******************************************************************************
** The process: a table-level action (no records need to be selected).
*******************************************************************************/
public static QProcessMetaData process()
{
Map<String, Serializable> values = new LinkedHashMap<>();
values.put(StreamedETLWithFrontendProcess.FIELD_SOURCE_TABLE, "edi_map");
values.put(StreamedETLWithFrontendProcess.FIELD_DESTINATION_TABLE, "edi_map");
values.put(StreamedETLWithFrontendProcess.FIELD_SUPPORTS_FULL_VALIDATION, false);
values.put(StreamedETLWithFrontendProcess.FIELD_PREVIEW_MESSAGE,
"These sample maps will be added as drafts, with no partner and no agreement document. Maps that already exist are not changed.");
QProcessMetaData process = StreamedETLWithFrontendProcess.defineProcessMetaData(Extract.class, Transform.class, Load.class, values);
process.setName(NAME);
process.setLabel("Install sample maps");
process.setIcon(new QIcon("playlist_add"));
process.setTableName("edi_map");
process.setIsHidden(true);
process.setMinInputRecords(0);
process.setPermissionRules(new QPermissionRules().withPermissionBaseName("edi.admin").withLevel(PermissionLevel.HAS_ACCESS_PERMISSION));
return (process);
}
/*******************************************************************************
** The samples still to install, as the processing user.
*******************************************************************************/
static List<String> missing() throws QException
{
return (SystemWrites.call(SampleMaps::missing));
}
/*******************************************************************************
** A summary line with the standard four wordings.
*******************************************************************************/
private static ProcessSummaryLine line(Status status, String future, String futurePlural, String past, String pastPlural)
{
ProcessSummaryLine line = new ProcessSummaryLine(status, 0, null);
line.setSingularFutureMessage(future);
line.setPluralFutureMessage(futurePlural);
line.setSingularPastMessage(past);
line.setPluralPastMessage(pastPlural);
return (line);
}
/*******************************************************************************
** Extract: the missing samples, computed (no query of selected records).
*******************************************************************************/
public static class Extract extends AbstractExtractStep
{
/*******************************************************************************
** How many samples will be added.
*******************************************************************************/
@Override
public Integer doCount(RunBackendStepInput input) throws QException
{
return (missing().size());
}
/*******************************************************************************
** One record per missing sample.
*******************************************************************************/
@Override
public void run(RunBackendStepInput input, RunBackendStepOutput output) throws QException
{
for(String resource : missing())
{
QRecord map = SystemWrites.call(() -> SampleMaps.mapRecord(resource));
getRecordPipe().addRecord(map.withValue("sample", resource));
}
}
}
/*******************************************************************************
** Transform: forward the records and count them by standard and direction.
*******************************************************************************/
public static class Transform extends AbstractTransformStep implements ProcessSummaryProviderInterface
{
private final Map<String, ProcessSummaryLine> byKind = new LinkedHashMap<>();
/*******************************************************************************
** Count by standard and direction.
*******************************************************************************/
@Override
public void runOnePage(RunBackendStepInput input, RunBackendStepOutput output) throws QException
{
for(QRecord record : input.getRecords())
{
String kind = record.getValueString("standard") + " " + record.getValueString("direction").toLowerCase();
byKind.computeIfAbsent(kind, k -> line(Status.OK, k + " sample map will be added as a draft", k + " sample maps will be added as drafts",
k + " sample map was added as a draft", k + " sample maps were added as drafts")).incrementCount();
output.getRecords().add(record);
}
}
/*******************************************************************************
** Standard Review summary.
*******************************************************************************/
@Override
public ArrayList<ProcessSummaryLineInterface> getProcessSummary(RunBackendStepOutput output, boolean isForResultScreen)
{
ArrayList<ProcessSummaryLineInterface> lines = new ArrayList<>();
byKind.values().forEach(line -> line.addSelfToListIfAnyCount(lines));
if(lines.isEmpty())
{
lines.add(new ProcessSummaryLine(Status.INFO, null, "Every sample map is already installed."));
}
return (lines);
}
}
/*******************************************************************************
** Load: insert each map and its rules as the processing user.
*******************************************************************************/
public static class Load extends AbstractLoadStep implements ProcessSummaryProviderInterface
{
private final ProcessSummaryLine added = line(Status.OK, "map will be added", "maps will be added", "map was added as a draft", "maps were added as drafts");
/*******************************************************************************
** One transaction per map, so a map never lands without its rules.
*******************************************************************************/
@Override
public void runOnePage(RunBackendStepInput input, RunBackendStepOutput output) throws QException
{
for(QRecord record : input.getRecords())
{
Integer id = SystemWrites.call(() -> SystemWrites.transaction(() -> SampleMaps.install(record.getValueString("sample"))));
if(id != null)
{
added.incrementCountAndAddPrimaryKey(id);
output.getRecords().add(new QRecord().withValue("id", id));
}
}
}
/*******************************************************************************
** Standard Result summary.
*******************************************************************************/
@Override
public ArrayList<ProcessSummaryLineInterface> getProcessSummary(RunBackendStepOutput output, boolean isForResultScreen)
{
ArrayList<ProcessSummaryLineInterface> lines = new ArrayList<>();
added.addSelfToListIfAnyCount(lines);
return (lines);
}
}
}