Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public static FPRAuditResult auditFPR(AuditFprOptions options)
Map<String, AuditResponse> auditResponses = new ConcurrentHashMap<>();
AuditOutcome auditOutcome = performAviatorAudit(
parsedData, options.getLogger(), options.getToken(), options.getAppVersion(), options.getUrl(), options.getSscAppName(), options.getSscAppVersion(),
auditResponses, filterSelection, options.getFprHandle()
auditResponses, filterSelection, options.getFprHandle(), options.getFolderPriorityOrder()
);

// --- STAGE 4: FINALIZATION ---
Expand Down Expand Up @@ -107,11 +107,18 @@ private static TagMappingConfig loadTagMappingConfig(String tagMappingFilePath)
private static AuditOutcome performAviatorAudit(
ParsedFprData parsedData, IAviatorLogger logger,
String token, String appVersion, String url, String sscAppName, String sscAppVersion,
Map<String, AuditResponse> auditResponsesToFill, FilterSelection filterSelection, FprHandle fprHandle) {
Map<String, AuditResponse> auditResponsesToFill, FilterSelection filterSelection, FprHandle fprHandle, List<String> folderPriorityOrder) {

IssueAuditor issueAuditor = new IssueAuditor(
parsedData.vulnerabilities, parsedData.auditProcessor, parsedData.auditIssueMap,
parsedData.fprInfo, sscAppName, sscAppVersion, filterSelection, logger
parsedData.vulnerabilities,
parsedData.auditProcessor,
parsedData.auditIssueMap,
parsedData.fprInfo,
sscAppName,
sscAppVersion,
filterSelection,
logger,
folderPriorityOrder
);
return issueAuditor.performAudit(
auditResponsesToFill, token, appVersion, parsedData.fprInfo.getBuildId(), url, fprHandle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,16 @@ public class IssueAuditor {
private final FPRInfo fprInfo;
private final FilterSelection filterSelection;


private final TagDefinition analysisTag;
private TagDefinition humanAuditTag;
private TagDefinition aviatorStatusTag;

private final IAviatorLogger logger;
private final List<String> customPriorityOrder;

public IssueAuditor(List<Vulnerability> vulnerabilities, AuditProcessor auditProcessor, Map<String, AuditIssue> auditIssueMap, FPRInfo fprInfo, String SSCApplicationName, String SSCApplicationVersion, FilterSelection filterSelection , IAviatorLogger logger) {
public IssueAuditor(List<Vulnerability> vulnerabilities, AuditProcessor auditProcessor, Map<String, AuditIssue> auditIssueMap, FPRInfo fprInfo, String SSCApplicationName, String SSCApplicationVersion, FilterSelection filterSelection , IAviatorLogger logger, List<String> customPriorityOrder) {
this.logger = logger;
this.customPriorityOrder = customPriorityOrder;
this.MAX_PER_CATEGORY = Constants.MAX_PER_CATEGORY;
this.MAX_TOTAL = Constants.MAX_TOTAL;
this.MAX_PER_CATEGORY_EXCEEDED = Constants.MAX_PER_CATEGORY_EXCEEDED;
Expand Down Expand Up @@ -155,7 +156,7 @@ public AuditOutcome performAudit(Map<String, AuditResponse> auditResponses, Stri
} else {
try (AviatorGrpcClient client = AviatorGrpcClientHelper.createClient(url, logger, DEFAULT_PING_INTERVAL_SECONDS)) {
CompletableFuture<Map<String, AuditResponse>> future =
client.processBatchRequests(promptsToAudit, projectName, fprInfo.getBuildId(), SSCApplicationName, SSCApplicationVersion, token, fprHandle);
client.processBatchRequests(promptsToAudit, projectName, fprInfo.getBuildId(), SSCApplicationName, SSCApplicationVersion, token, fprHandle, customPriorityOrder);
Map<String, AuditResponse> responses = future.get(500, TimeUnit.MINUTES);
responses.forEach((requestId, response) -> auditResponses.put(response.getIssueId(), response));
logger.progress("Audit completed");
Expand Down Expand Up @@ -204,10 +205,11 @@ private ConcurrentLinkedDeque<UserPrompt> prepareAndFilterPrompts() {
.collect(Collectors.toList());

// Apply secondary checks (like 'isAudited')
prompts = prompts.stream()
.filter(this::shouldInclude)
.collect(Collectors.toList());

return prompts.stream()
.filter(this::shouldInclude).collect(Collectors.toCollection(ConcurrentLinkedDeque::new));

return prompts.stream().collect(Collectors.toCollection(ConcurrentLinkedDeque::new));
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
* Copyright 2021-2026 Open Text.
*
* The only warranties for products and services of Open Text
* and its affiliates and licensors ("Open Text") are as may
* be set forth in the express warranty statements accompanying
* such products and services. Nothing herein should be construed
* as constituting an additional warranty. Open Text shall not be
* liable for technical or editorial errors or omissions contained
* herein. The information contained herein is subject to change
* without notice.
*/
package com.fortify.cli.aviator.audit;

import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.fortify.cli.aviator.audit.model.UserPrompt;
import com.fortify.cli.aviator.util.StringUtil;

/**
* Utility class for filtering UserPrompts based on quota constraints.
* Supports priority-based filtering with default or custom priority ordering.
*/
public class QuotaBasedFilter {

/**
* Default priority ordering: Critical > High > Medium > Low
*/
private static final Map<String, Integer> DEFAULT_PRIORITY_ORDER = Map.of(
"Critical", 4,
"High", 3,
"Medium", 2,
"Low", 1
);

/**
* Filters UserPrompts to fit within the specified quota based on priority.
* Uses default priority ordering (Critical > High > Medium > Low).
*
* @param prompts List of UserPrompts to filter
* @param quota Maximum number of prompts to retain
* @return Filtered list of UserPrompts, sorted by priority (highest first)
*/
public static List<UserPrompt> filterByQuota(List<UserPrompt> prompts, long quota) {
return filterByQuota(prompts, quota, null);
}

/**
* Filters UserPrompts to fit within the specified quota based on priority.
* Supports custom priority ordering provided by the client.
*
* @param prompts List of UserPrompts to filter
* @param quota Maximum number of prompts to retain
* @param customPriorityOrder Custom priority ordering map (priority name -> rank).
* Higher rank values indicate higher priority.
* If null, uses default ordering.
* When custom priority order is provided, prompts with priorities
* not in the custom order are excluded from filtering.
* This may result in fewer prompts than the quota limit.
* @return Filtered list of UserPrompts, sorted by priority (highest first)
*/
public static List<UserPrompt> filterByQuota(List<UserPrompt> prompts, long quota,
Map<String, Integer> customPriorityOrder) {
if (prompts == null || prompts.isEmpty()) {
return List.of();
}

if (quota <= 0) {
return List.of();
}

boolean useCustomPriorityOrder = (customPriorityOrder != null && !customPriorityOrder.isEmpty());

Map<String, Integer> priorityOrder = useCustomPriorityOrder
? customPriorityOrder
: DEFAULT_PRIORITY_ORDER;

// When custom priority order is provided, filter out prompts with unknown priorities
List<UserPrompt> eligiblePrompts = prompts;
if (useCustomPriorityOrder) {
eligiblePrompts = prompts.stream()
.filter(prompt -> {
String priority = extractPriority(prompt);
return priorityOrder.containsKey(priority);
})
.collect(Collectors.toList());
}

if (eligiblePrompts.size() <= quota) {
return eligiblePrompts;
}

Comparator<UserPrompt> priorityComparator = createPriorityComparator(priorityOrder);

return eligiblePrompts.stream()
.sorted(priorityComparator)
.limit(quota)
.collect(Collectors.toList());
}

/**
* Creates a comparator for UserPrompts based on priority ordering.
* Prompts with unknown priorities are ranked lowest.
*
* @param priorityOrder Map of priority names to rank values
* @return Comparator that sorts UserPrompts by priority (descending)
*/
private static Comparator<UserPrompt> createPriorityComparator(Map<String, Integer> priorityOrder) {
return (a, b) -> {
String aPriority = extractPriority(a);
String bPriority = extractPriority(b);

int aRank = priorityOrder.getOrDefault(aPriority, 0);
int bRank = priorityOrder.getOrDefault(bPriority, 0);

// Sort in descending order (highest priority first)
int comparison = Integer.compare(bRank, aRank);

// If priorities are equal, maintain stable sort by instanceID
if (comparison == 0) {
String aId = a.getIssueData().getInstanceID();
String bId = b.getIssueData().getInstanceID();
if (aId != null && bId != null) {
return aId.compareTo(bId);
}
}

return comparison;
};
}

/**
* Extracts priority from UserPrompt.
* Handles null or empty priority values gracefully.
*
* @param prompt UserPrompt to extract priority from
* @return Priority string, or "Unknown" if not available
*/
private static String extractPriority(UserPrompt prompt) {
if (prompt == null || prompt.getIssueData() == null) {
return "Unknown";
}

String priority = prompt.getIssueData().getPriority();
return StringUtil.isEmpty(priority) ? "Unknown" : priority;
}

/**
* Builds a custom priority order map from a list of priority names.
* The order in the list determines the rank (first = highest priority).
*
* @param orderedPriorities List of priority names in descending order of importance
* @return Map of priority names to rank values
*/
public static Map<String, Integer> buildCustomPriorityOrder(List<String> orderedPriorities) {
if (orderedPriorities == null || orderedPriorities.isEmpty()) {
return new HashMap<>();
}

Map<String, Integer> priorityOrder = new HashMap<>();
int rank = orderedPriorities.size();

for (String priority : orderedPriorities) {
if (!StringUtil.isEmpty(priority)) {
priorityOrder.put(priority, rank);
rank--;
}
}

return priorityOrder;
}

/**
* Returns the default priority ordering used by the filter.
*
* @return Map of default priority names to rank values
*/
public static Map<String, Integer> getDefaultPriorityOrder() {
return new HashMap<>(DEFAULT_PRIORITY_ORDER);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ public class AuditFprOptions {
private final String filterSetNameOrId;
private final boolean noFilterSet;
private final List<String> folderNames;
}
private final List<String> folderPriorityOrder;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright 2021-2026 Open Text.
*
* The only warranties for products and services of Open Text
* and its affiliates and licensors ("Open Text") are as may
* be set forth in the express warranty statements accompanying
* such products and services. Nothing herein should be construed
* as constituting an additional warranty. Open Text shall not be
* liable for technical or editorial errors or omissions contained
* herein. The information contained herein is subject to change
* without notice.
*/
package com.fortify.cli.aviator.audit.model;

import java.util.List;
import java.util.Map;

import com.formkiq.graalvm.annotations.Reflectable;

import lombok.Builder;
import lombok.Getter;

/**
* Configuration for quota-based filtering of issues.
*/
@Getter
@Builder
@Reflectable
public class QuotaConfig {
/**
* Available quota for processing issues. If null or <= 0, no quota limit is applied.
*/
private final Long availableQuota;

/**
* Custom priority ordering for filtering. Map of priority name to rank.
* Higher rank values indicate higher priority.
* If null or empty, default ordering (Critical > High > Medium > Low) is used.
*/
private final Map<String, Integer> customPriorityOrder;

/**
* Ordered list of priority names (alternative to customPriorityOrder).
* First element has highest priority. If provided, this will be converted
* to a priority order map internally.
*/
private final List<String> orderedPriorities;

/**
* Checks if quota filtering should be applied.
*
* @return true if quota is set and greater than 0
*/
public boolean hasQuota() {
return availableQuota != null && availableQuota > 0;
}

/**
* Checks if custom priority ordering is configured.
*
* @return true if custom priority order or ordered priorities are set
*/
public boolean hasCustomPriorityOrder() {
return (customPriorityOrder != null && !customPriorityOrder.isEmpty()) ||
(orderedPriorities != null && !orderedPriorities.isEmpty());
}

/**
* Creates a QuotaConfig with no quota limit.
*
* @return QuotaConfig with unlimited quota
*/
public static QuotaConfig noQuota() {
return QuotaConfig.builder().build();
}

/**
* Creates a QuotaConfig with the specified quota and default priority ordering.
*
* @param quota Available quota
* @return QuotaConfig with specified quota
*/
public static QuotaConfig withQuota(long quota) {
return QuotaConfig.builder().availableQuota(quota).build();
}

/**
* Creates a QuotaConfig with the specified quota and custom priority ordering.
*
* @param quota Available quota
* @param customPriorityOrder Custom priority order map
* @return QuotaConfig with quota and custom ordering
*/
public static QuotaConfig withCustomOrder(long quota, Map<String, Integer> customPriorityOrder) {
return QuotaConfig.builder()
.availableQuota(quota)
.customPriorityOrder(customPriorityOrder)
.build();
}

/**
* Creates a QuotaConfig with the specified quota and ordered priority list.
*
* @param quota Available quota
* @param orderedPriorities Ordered list of priority names (highest first)
* @return QuotaConfig with quota and ordered priorities
*/
public static QuotaConfig withOrderedPriorities(long quota, List<String> orderedPriorities) {
return QuotaConfig.builder()
.availableQuota(quota)
.orderedPriorities(orderedPriorities)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ public AviatorGrpcClient(ManagedChannel channel, long defaultTimeoutSeconds, IAv
this(channel, defaultTimeoutSeconds, logger, 30);
}

public CompletableFuture<Map<String, AuditResponse>> processBatchRequests(Queue<UserPrompt> requests, String projectName, String FPRBuildId, String SSCApplicationName, String SSCApplicationVersion, String token, FprHandle fprHandle) {
public CompletableFuture<Map<String, AuditResponse>> processBatchRequests(Queue<UserPrompt> requests, String projectName, String FPRBuildId, String SSCApplicationName, String SSCApplicationVersion, String token, FprHandle fprHandle, List<String> customPriorityOrder) {
AviatorStreamProcessor processor = new AviatorStreamProcessor(this, logger, asyncStub, processingExecutor, pingScheduler, pingIntervalSeconds, defaultTimeoutSeconds, fprHandle);
CompletableFuture<Map<String, AuditResponse>> future = processor.processBatchRequests(requests, projectName, FPRBuildId, SSCApplicationName, SSCApplicationVersion, token);
CompletableFuture<Map<String, AuditResponse>> future = processor.processBatchRequests(requests, projectName, FPRBuildId, SSCApplicationName, SSCApplicationVersion, token, customPriorityOrder);
future.whenComplete((res, th) -> processor.close());
return future.exceptionally(ex -> {
Throwable cause = (ex instanceof CompletionException || ex instanceof ExecutionException) && ex.getCause() != null ? ex.getCause() : ex;
Expand Down
Loading
Loading