QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Failing after 7s
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / ci-result (push) Failing after 5s
QRun-IO/ci-policy: native / native-result (push) Successful in 6s
Checkpoint implementation at user-requested stop; document known failures and incomplete release qualification. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: GPT-6 Codex <noreply@openai.com>
148 lines
7.4 KiB
Java
148 lines
7.4 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.documentgeneration;
|
|
|
|
|
|
import java.io.Serializable;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionEnforcement;
|
|
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
|
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
|
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
|
import com.kingsrook.qqq.backend.core.exceptions.QBadRequestException;
|
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
|
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
|
import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput;
|
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
|
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
|
|
|
|
|
/***************************************************************************
|
|
** Preview selection reads the configured source through current native authority.
|
|
***************************************************************************/
|
|
public class GenerationPreviewSources implements QCustomPossibleValueProvider<Serializable>
|
|
{
|
|
/***************************************************************************
|
|
** A bare ID has no source identity; exact refresh uses the named search source.
|
|
***************************************************************************/
|
|
@Override
|
|
public QPossibleValue<Serializable> getPossibleValue(Serializable id) throws QException
|
|
{
|
|
throw new QBadRequestException("Resolve preview records through their source-specific selector.");
|
|
}
|
|
|
|
/***************************************************************************
|
|
** One bounded page, with enforced query and exact read checks; never raw totals.
|
|
***************************************************************************/
|
|
@Override
|
|
public List<QPossibleValue<Serializable>> search(SearchPossibleValueSourceInput input) throws QException
|
|
{
|
|
GenerationSecurity.require("configure");
|
|
GenerationSecurity.require("generate");
|
|
var instance = QContext.getQInstance();
|
|
var source = instance.getPossibleValueSource(input.getPossibleValueSourceName());
|
|
var config = DocumentGenerationQBitConfig.from(instance);
|
|
String sourceTable = config.getSourceTables().stream()
|
|
.filter(name -> GenerationAuthoringProcesses.sourceRecordChoices(name).equals(input.getPossibleValueSourceName()))
|
|
.findFirst().orElse(null);
|
|
if(GenerationAuthoringProcesses.PREVIEW_SOURCE.equals(input.getPossibleValueSourceName()))
|
|
{
|
|
// The standard process route is keyed by the static field; the form says which source table is selected.
|
|
// Only a configured table is accepted, and every record below is still read under the caller's own authority.
|
|
Object selected = input.getOtherValues() == null ? null : input.getOtherValues().get("sourceTable");
|
|
sourceTable = selected instanceof String name && config.getSourceTables().contains(name) ? name : null;
|
|
}
|
|
if(source == null || sourceTable == null)
|
|
{
|
|
throw new QBadRequestException("Unknown preview source.");
|
|
}
|
|
var table = instance.getTable(sourceTable);
|
|
String term = Objects.requireNonNullElse(input.getSearchTerm(), "");
|
|
int skip = Objects.requireNonNullElse(input.getSkip(), 0);
|
|
if(term.length() > 128 || skip < 0 || skip > 10000 || input.getIdList() != null && input.getIdList().size() > 50)
|
|
{
|
|
throw new QBadRequestException("Narrow the preview record search.");
|
|
}
|
|
var filter = new QQueryFilter().withLimit(Math.max(1, Math.min(50, Objects.requireNonNullElse(input.getLimit(), 50))))
|
|
.withSkip(skip).withOrderBy(new QFilterOrderBy(table.getPrimaryKeyField()));
|
|
List<String> labels = Objects.requireNonNullElse(table.getRecordLabelFields(), List.of());
|
|
List<String> safeLabels = labels.stream().filter(name -> table.getField(name) != null && !Boolean.TRUE.equals(table.getField(name).getIsHidden())
|
|
&& table.getField(name).getType() == QFieldType.STRING).toList();
|
|
if(input.getIdList() != null)
|
|
{
|
|
filter.withCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, input.getIdList());
|
|
}
|
|
else if(!term.isBlank())
|
|
{
|
|
if(safeLabels.isEmpty())
|
|
{
|
|
// Without a text label the only searchable key is the primary key; a term that is not a valid key matches nothing.
|
|
try
|
|
{
|
|
filter.withCriteria(table.getPrimaryKeyField(), QCriteriaOperator.EQUALS,
|
|
com.kingsrook.qqq.backend.core.utils.ValueUtils.getValueAsFieldType(table.getField(table.getPrimaryKeyField()).getType(), term));
|
|
}
|
|
catch(Exception notAKey)
|
|
{
|
|
return (new ArrayList<>());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var search = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.OR);
|
|
safeLabels.forEach(name -> search.withCriteria(name, QCriteriaOperator.CONTAINS, term));
|
|
filter.withSubFilter(search);
|
|
}
|
|
}
|
|
QueryInput query = new QueryInput(table.getName()).withFilter(filter).withShouldFetchHeavyFields(false);
|
|
query.setPermissionEnforcement(PermissionEnforcement.ENFORCE);
|
|
List<QPossibleValue<Serializable>> result = new ArrayList<>();
|
|
for(var candidate : new QueryAction().execute(query).getRecords())
|
|
{
|
|
Serializable id = candidate.getValue(table.getPrimaryKeyField());
|
|
if(id == null)
|
|
{
|
|
continue;
|
|
}
|
|
try
|
|
{
|
|
var record = GenerationSecurity.source(new GenerationPorts.Source(table.getName(), id.toString()));
|
|
String label = labels.isEmpty() || !safeLabels.containsAll(labels) || !record.getValues().keySet().containsAll(labels)
|
|
? id.toString() : QValueFormatter.formatRecordLabel(table, record);
|
|
result.add(new QPossibleValue<>(id, label));
|
|
}
|
|
catch(QNotFoundException denied)
|
|
{
|
|
// Exact Get customizers may narrow Query results; denied labels are omitted.
|
|
}
|
|
}
|
|
return (result);
|
|
}
|
|
}
|