feat: add --preview flag to preview aviator remediations before applying - #1076
feat: add --preview flag to preview aviator remediations before applying#1076dhanwanthp wants to merge 7 commits into
Conversation
…and to display the correct value for __action__
…rics for better readability, removed a redundant field and added tests for code that was not covered before.
| * Applies or previews remediations for each source entry until done or the issue-id filter is exhausted. | ||
| * Caller owns {@code source} lifecycle (try-with-resources). | ||
| */ | ||
| public static ApplyResult apply( |
There was a problem hiding this comment.
As the number of parameters keep growing, maybe better to combine in a single object that implements builder pattern (through Lombok @Builder), i.e.,
apply(ApplyArgs.builder()
.source(source)
.sourceCodeDirectory(sourceCodeDirectory)
...
.build();
Same could potentially be applied to other methods in Aviator code to reduce number of parameters.
| unirest, logger, progressWriter, resolved.artifacts())) { | ||
| ApplyResult applyResult = RemediationsApplyHelper.apply( | ||
| source, sourceCodeDirectory, logger, issueIdFilter, LOG); | ||
| source, sourceCodeDirectory, logger, issueIdFilter, LOG, previewMode); |
There was a problem hiding this comment.
Related to the apply comment elsewhere, what about:
- Move the various options that define method parameter values into a separate Picocli ArgGroup that can be shared between SSC and FoD commands
- Have that ArgGroup implement some interface with getters for each of the option values (through Lombok
@Getterannotations on the options/class) - Have the
RemediationsApplyHelper::applymethod take an instance of that interface
This way, the FoD & SSC apply-remediations commands can simply pass the ArgGroup to the apply method (together with the loggers, which remain separate method parameters), and due to interface abstraction, RemediationsApplyHelper doesn't have a compile-time dependency on CLI-specific code (like the ArgGroup).
| unirest, logger, progressWriter, resolved.artifacts())) { | ||
| ApplyResult applyResult = RemediationsApplyHelper.apply( | ||
| source, sourceCodeDirectory, logger, issueIdFilter, LOG); | ||
| source, sourceCodeDirectory, logger, issueIdFilter, LOG, previewMode); |
There was a problem hiding this comment.
Why are you passing LOG to a different class? Usually, each class has its own Slf4j logger, which also makes it much easier to identify which class generated a particular log message.
| unirest, logger, progressWriter, resolved.artifacts())) { | ||
| ApplyResult applyResult = RemediationsApplyHelper.apply( | ||
| source, sourceCodeDirectory, logger, issueIdFilter, LOG); | ||
| source, sourceCodeDirectory, logger, issueIdFilter, LOG, previewMode); |
There was a problem hiding this comment.
I think there's some overlap between FoD and SSC implementations of the apply-remediations command; please check whether it makes sense to introduce a common AbstractAviatorApplyRemediationsCommand base class.
- Introduce AbstractAviatorApplyRemediationsCommand base class shared by SSC and FoD - Add ApplyRemediationsOptionsMixin and IApplyRemediationsOptions interface - Simplify RemediationsApplyHelper.apply() signature; add @slf4j logger instead of passing LOG - Add @builder to FileChange; replace IllegalArgumentException with AviatorBugException
| @Override | ||
| public final JsonNode getJsonNode() { | ||
| validateSourceSelector(); | ||
| AviatorApplyRemediationsCliSupport.requireSourceDir(applyOptions.getSourceCodeDirectory()); |
There was a problem hiding this comment.
Exceptions thrown by AviatorApplyRemediationsCliSupport reference explicit option names, whereas that class doesn't 'know' from which command class it's being invoked, and what options are provided by that command. The requiresSourceDir method can be easily moved to this abstract base class, as this base class declares the option for specifying source code directory. For requireIssueIdsCacheOnly, it's more difficult as this abstract base class doesn't declare the --from-cache option. I guess --from-cache is currently declared through picocli arg group as being exclusive to product-specific options, hence we can't move --from-cache to ApplyRemediationsOptionsMixin without loosing the picocli exclusivity. Given that this option is shared between SSC & FoD, maybe it's still worth declaring this option on this abstract base class, and manually check for exclusivity.
Effectively, I think it would be good to get rid of the AviatorApplyRemediationsCliSupport class, moving functionality to this abstract base class.
|
|
||
| protected abstract boolean isFromCacheSelected(); | ||
|
|
||
| protected abstract JsonNode processFromCache(AviatorLoggerImpl logger, Set<String> issueIdFilter); |
There was a problem hiding this comment.
I haven't checked the details, but wouldn't applying from cache be very similar between SSC & FoD? Or, possibly even better, can't we get rid of separate methods for cache vs online, and instead have something like an IFpr[s]Supplier interface that, based on given options, provides FPR files from either cache, SSC, or FoD, with actual FPR processing logic shared between all three use cases?
| @@ -40,7 +40,18 @@ private AviatorRemediationMetricsHelper() {} | |||
| public static RemediationMetric aggregateMetrics(Set<String> requestedIssueIds, Collection<RemediationMetric> metrics) { | |||
There was a problem hiding this comment.
This seems a fairly long method; can we improve this through self-describing sub-methods, and/or through utility (builder) methods on RemediationMetric?
| * @param fuzzyMatched Whether fuzzy matching was used | ||
| */ | ||
| @Reflectable | ||
| public record ChangeDetail( |
There was a problem hiding this comment.
Given the many record constructor arguments, can we use Lombok @Builder to improve code readability and avoiding constructor arguments potentially being passed in wrong order?
- Remove AviatorApplyRemediationsCliSupport class and moved its functionality to AbstractAviatorApplyRemediationsCommand - Replace separate methods for cache vs online with openFprSource/buildResultNode/isCacheMode to leverage IRemediationsFprSource - Split aggregateMetrics into aggregateUnfiltered/aggregateFiltered sub-methods - Use Lombok @builder in ChangeDetail to improve code readability
| public final boolean isSingular() { return true; } | ||
|
|
||
| @Override | ||
| public JsonNode transformRecord(JsonNode record) { return record; } |
There was a problem hiding this comment.
As this is a no-op, better to remove this method and the corresponding IRecordTransformer interface from the class definition.
| @Override | ||
| public JsonNode transformRecord(JsonNode record) { return record; } | ||
|
|
||
| private void requireSourceDir() { |
There was a problem hiding this comment.
Given the default value for --source-dir, I don't think the null check is necessary? Or better yet, just use StringUtils::isBlank, which handles both null and blank values. Shouldn't we also validate that the path exists/is accessible? Given that exception explicitly mentions option name, it would be better to move this validation to the mixin that declares this option. This could be done either by an explicit validation method on the mixin, or directly in getSourceCodeDirectory (i.e., checks are done when value is retrieved, although pre-flight validation may be better).
| "--source-dir must specify a valid directory path"); | ||
| } | ||
|
|
||
| private void requireIssueIdsCacheOnly() { |
There was a problem hiding this comment.
Similar to the above; as exception references option names, better to move this validation to the mixin.
| @Override | ||
| public JsonNode transformRecord(JsonNode record) { | ||
| return record; | ||
| return AviatorFoDApplyRemediationsHelper.buildOnlineResultNode(resolvedRelease, result); |
There was a problem hiding this comment.
Why is this done through IRecordTransformer instead of directly in the buildResultNode method?
| Set<String> issueIdFilter = AviatorIssueIdFilterUtils.normalizeIssueIds(applyOptions.getIssueIds()); | ||
| try (IProgressWriter progressWriter = progressWriterFactoryMixin.create()) { | ||
| AviatorLoggerImpl logger = new AviatorLoggerImpl(progressWriter); | ||
| try (IRemediationsFprSource source = openFprSource(logger, progressWriter)) { |
There was a problem hiding this comment.
source can be easily mistaken for 'source file/dir'; can we consistently rename this variable to fprSource (also in for example RemediationsApplyHelper if applicable)
| import lombok.Getter; | ||
| import picocli.CommandLine.Mixin; | ||
|
|
||
| public abstract class AbstractAviatorApplyRemediationsCommand extends AbstractOutputCommand |
There was a problem hiding this comment.
Although much better than before, I think definitions and responsibilities between abstract base class, concrete sub-classes, and options/mixins can be further improved.
Maybe something like the following?
- Rename
ApplyRemediationsOptionsMixintoAbstractApplyRemediationsOptionsMixin - Add
validatemethod to this abstract mixin and its interface, calling individual validation methods for validating source selection, source dir,--issue-ids/--from-cacheinterdependency, ... - Have both SSC & FoD remediation mixins extend the abstract mixin
- Declare remediation mixin in concrete FoD/SSC command class, accessible to abstract command class through getter
So, basically, generic & command-specific options are provided through single mixin on FoD/SSC command classes, and those mixins provide the necessary validation logic. This allows the abstract base class to focus on process, instead of handling both process and data (validation).
| private String sourceCodeDirectory = System.getProperty("user.dir"); | ||
| @Option(names = {"--issue-ids"}, split = ",") | ||
| private List<String> issueIds; | ||
| private ResolvedOnlineArtifacts resolvedOnline; |
There was a problem hiding this comment.
As per fcli convention, command classes ideally shouldn't store state in instance fields
This MR adds a new
--previewflag to the fcli aviator ssc apply-remediations and fcli fod aviator apply-remediations commands, enabling users to preview what changes would be applied to their source code without actually modifying files.Features:
Usage:
Output format:-