Skip to content

Commit

Permalink
feat: improved API Score status handling
Browse files Browse the repository at this point in the history
  • Loading branch information
JedrzejJanasiak committed Jan 2, 2025
1 parent f4d6e11 commit 72e8aea
Show file tree
Hide file tree
Showing 16 changed files with 202 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ <h1 class="page-title">API Score</h1>
<ng-container matColumnDef="score">
<th mat-header-cell *matHeaderCellDef id="score">Score</th>
<td mat-cell *matCellDef="let element">
@if (element.score == null) {
@if (element.score == null || element.score === -1) {
<span class="gio-badge-neutral">Not available</span>
} @else if (element.score >= 0.8) {
<span class="gio-badge-success"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@
<mat-card-title>
{{ api.name }}

@if (apiScoring.summary.score == null) {
<span></span>
@if (apiScoring.additionalStatuses.neverEvaluated ||
apiScoring.additionalStatuses.noScoreableAssets ||
(apiScoring.additionalStatuses.containsEvaluationErrors && !apiScoring.additionalStatuses.containsDiagnostics)
) {
<span class="gio-badge-neutral">Not available</span>
} @else if (apiScoring.summary.score >= 0.8) {
<span class="gio-badge-success"
><mat-icon class="gio-left" svgIcon="gio:shield-check"></mat-icon> {{ apiScoring.summary.score * 100 | number: '1.0-0' }}%
Expand All @@ -41,6 +44,9 @@
><mat-icon class="gio-left" svgIcon="gio:shield-check"></mat-icon> {{ apiScoring.summary.score * 100 | number: '1.0-0' }}%
</span>
}
@if (apiScoring.additionalStatuses.containsEvaluationErrors) {
<span class="gio-badge-error"><mat-icon svgIcon="gio:shield-alert"></mat-icon></span>
}

<!-- Uncomment when BE ready -->
<!-- <span class="gio-badge-success"><mat-icon class="gio-left" svgIcon="gio:trending-up"></mat-icon>+0.7</span>-->
Expand Down Expand Up @@ -94,12 +100,24 @@
}

<section class="api-scores-lists">
@if (apiScoring.summary.all === 0) {
@if (apiScoring.additionalStatuses.neverEvaluated) {
<gio-card-empty-state
icon="search"
title="This API has never been scored before"
[subtitle]="'Click on the Evaluate button to get the first score.'"
></gio-card-empty-state>
} @else if (apiScoring.summary.all === 0 && apiScoring.summary.score === 1) {
<gio-card-empty-state
icon="rocket"
title="All clear"
[subtitle]="'There is no recommendations or issues for your API. \n Everything looks great!'"
></gio-card-empty-state>
} @else if (apiScoring.additionalStatuses.noScoreableAssets) {
<gio-card-empty-state
icon="shield-minus"
title="No scorable assets"
[subtitle]="'This API\'s assets did not match any rulesets.'"
></gio-card-empty-state>
}
@for (asset of apiScoring.assets; track asset.name) {
<app-api-scoring-list [asset]="asset"></app-api-scoring-list>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface ApiScoring {
createdAt: Date;
summary: ApiScoringSummary;
assets: ScoringAsset[];
additionalStatuses: AdditionalStatuses;
}

export interface ApiScoringSummary {
Expand Down Expand Up @@ -72,3 +73,10 @@ export interface ScoringError {
code: string;
path: string[];
}

export interface AdditionalStatuses {
containsEvaluationErrors?: boolean;
containsDiagnostics?: boolean;
neverEvaluated?: boolean;
noScoreableAssets?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ public ScoringEnvironmentSummary getScoringEnvironmentSummary(String environment
var result = jdbcTemplate.query(
"select " +
"environment_id, " +
"AVG(score) AS averageScore," +
"AVG(CASE WHEN score != -1 THEN score ELSE NULL END) AS averageScore," +
"SUM(errors) AS totalErrors," +
"SUM(warnings) AS totalWarnings," +
"SUM(infos) AS totalInfos," +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static com.mongodb.client.model.Accumulators.sum;
import static com.mongodb.client.model.Aggregates.*;
import static com.mongodb.client.model.Filters.eq;
import static com.mongodb.client.model.Filters.ne;
import static java.util.Map.entry;
import static java.util.Map.ofEntries;

Expand All @@ -36,7 +37,6 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -129,6 +129,7 @@ public Page<ScoringEnvironmentApi> findEnvironmentLatestReports(String environme
public ScoringEnvironmentSummary getScoringEnvironmentSummary(String environmentId) {
List<Bson> aggregations = new ArrayList<>();
aggregations.add(match(eq("environmentId", environmentId)));
aggregations.add(match(ne("summary.score", -1)));
aggregations.add(
group(
"$environmentId",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,11 @@
import io.gravitee.common.http.MediaType;
import io.gravitee.rest.api.management.v2.rest.mapper.ScoringReportMapper;
import io.gravitee.rest.api.management.v2.rest.model.ApiScoring;
import io.gravitee.rest.api.management.v2.rest.model.ApiScoringAsset;
import io.gravitee.rest.api.management.v2.rest.model.ApiScoringAssetType;
import io.gravitee.rest.api.management.v2.rest.model.ApiScoringDiagnostic;
import io.gravitee.rest.api.management.v2.rest.model.ApiScoringDiagnosticRange;
import io.gravitee.rest.api.management.v2.rest.model.ApiScoringPosition;
import io.gravitee.rest.api.management.v2.rest.model.ApiScoringSeverity;
import io.gravitee.rest.api.management.v2.rest.model.ApiScoringAdditionalStatuses;
import io.gravitee.rest.api.management.v2.rest.model.ApiScoringSummary;
import io.gravitee.rest.api.management.v2.rest.model.ApiScoringTriggerResponse;
import io.gravitee.rest.api.management.v2.rest.model.ScoringStatus;
import io.gravitee.rest.api.management.v2.rest.resource.AbstractResource;
import io.gravitee.rest.api.service.common.GraviteeContext;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
Expand All @@ -48,6 +42,7 @@ public class ApiScoringResource extends AbstractResource {
.builder()
.summary(ApiScoringSummary.builder().all(0).errors(0).hints(0).infos(0).warnings(0).build())
.assets(List.of())
.additionalStatuses(ApiScoringAdditionalStatuses.builder().neverEvaluated(true).build())
.build();

@Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7220,6 +7220,8 @@ components:
type: array
items:
$ref: "#/components/schemas/ApiScoringAsset"
additionalStatuses:
$ref: "#/components/schemas/ApiScoringAdditionalStatuses"
ApiScoringSummary:
type: object
properties:
Expand Down Expand Up @@ -7328,7 +7330,25 @@ components:
type: integer
description: An integer that represents the character position within the line where the issue starts or ends. The value is zero indexed.
example: 12

ApiScoringAdditionalStatuses:
type: object
properties:
containsEvaluationErrors:
type: boolean
description: Status triggered when the API contains any evaluation errors.
example: true
containsDiagnostics:
type: boolean
description: Status triggered when the API contains diagnostics.
example: true
neverEvaluated:
type: boolean
description: Status triggered when the API has never been evaluated.
example: true
noScoreableAssets:
type: boolean
description: Status triggered when the API has no scoreable assets.
example: true
# Async Jobs
AsyncJob:
type: object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import inmemory.PageCrudServiceInMemory;
import inmemory.ScoringReportQueryServiceInMemory;
import io.gravitee.rest.api.management.v2.rest.model.ApiScoring;
import io.gravitee.rest.api.management.v2.rest.model.ApiScoringAdditionalStatuses;
import io.gravitee.rest.api.management.v2.rest.model.ApiScoringAsset;
import io.gravitee.rest.api.management.v2.rest.model.ApiScoringAssetType;
import io.gravitee.rest.api.management.v2.rest.model.ApiScoringDiagnostic;
Expand Down Expand Up @@ -181,6 +182,15 @@ void should_return_200_response_with_diagnostics_asset() {
.build()
)
)
.additionalStatuses(
ApiScoringAdditionalStatuses
.builder()
.containsEvaluationErrors(false)
.neverEvaluated(false)
.noScoreableAssets(false)
.containsDiagnostics(true)
.build()
)
.build()
);
}
Expand Down Expand Up @@ -221,6 +231,46 @@ void should_return_200_response_with_errors_asset() {
.build()
)
)
.additionalStatuses(
ApiScoringAdditionalStatuses
.builder()
.containsEvaluationErrors(true)
.neverEvaluated(false)
.noScoreableAssets(false)
.containsDiagnostics(false)
.build()
)
.build()
);
}

@Test
void should_return_200_response_with_no_scoreable_asset_info() {
apiCrudService.initWith(List.of(ApiFixtures.aFederatedApi().toBuilder().id(API).build()));
pageCrudService.initWith(List.of(PageFixtures.aPage().toBuilder().referenceId(API).build()));
scoringReportQueryService.initWith(List.of(ScoringReportFixture.aScoringReportWithNoAssets().toBuilder().apiId(API).build()));

final Response response = latestReportTarget.request().get();

MAPIAssertions
.assertThat(response)
.hasStatus(OK_200)
.asEntity(ApiScoring.class)
.isEqualTo(
ApiScoring
.builder()
.summary(ApiScoringSummary.builder().score(-1.0).all(0).errors(0).hints(0).infos(0).warnings(0).build())
.createdAt(Instant.parse("2020-02-03T20:22:02.00Z").atOffset(ZoneOffset.UTC))
.assets(List.of())
.additionalStatuses(
ApiScoringAdditionalStatuses
.builder()
.containsEvaluationErrors(false)
.neverEvaluated(false)
.noScoreableAssets(true)
.containsDiagnostics(false)
.build()
)
.build()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,14 @@
import lombok.Builder;

@Builder(toBuilder = true)
public record ScoringReportView(String id, String apiId, ZonedDateTime createdAt, List<AssetView> assets, Summary summary) {
public record ScoringReportView(
String id,
String apiId,
ZonedDateTime createdAt,
List<AssetView> assets,
Summary summary,
AdditionalStatuses additionalStatuses
) {
public record Summary(Double score, Long all, Long errors, Long warnings, Long infos, Long hints) {
public Summary(Double score, Long errors, Long warnings, Long infos, Long hints) {
this(score, errors + warnings + infos + hints, errors, warnings, infos, hints);
Expand All @@ -32,4 +39,10 @@ public record AssetView(
List<ScoringReport.Diagnostic> diagnostics,
List<ScoringReport.ScoringError> errors
) {}
public record AdditionalStatuses(
boolean containsEvaluationErrors,
boolean containsDiagnostics,
boolean neverEvaluated,
boolean noScoreableAssets
) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import io.gravitee.apim.core.documentation.model.Page;
import io.gravitee.apim.core.scoring.model.ScoringReportView;
import io.gravitee.apim.core.scoring.query_service.ScoringReportQueryService;
import java.util.Objects;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
Expand All @@ -45,6 +46,12 @@ public Output execute(Input input) {
})
.toList();

var containsEvaluationErrors = report.assets().stream().anyMatch(asset -> !asset.errors().isEmpty());
var containsDiagnostics = report.assets().stream().anyMatch(asset -> !asset.diagnostics().isEmpty());
var SCORE_NOT_AVAILABLE = -1.0;
var noScoreableAsset =
!containsDiagnostics && !containsEvaluationErrors && Objects.equals(report.summary().score(), SCORE_NOT_AVAILABLE);

return new Output(
new ScoringReportView(
report.id(),
Expand All @@ -57,7 +64,8 @@ public Output execute(Input input) {
report.summary().warnings(),
report.summary().infos(),
report.summary().hints()
)
),
new ScoringReportView.AdditionalStatuses(containsEvaluationErrors, containsDiagnostics, false, noScoreableAsset)
)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -97,14 +96,15 @@ private ScoringReport.Summary processSummary(List<ScoringReport.Asset> assets) {
);
});

var SCORING_NOT_AVAILABLE = -1.0;
var averageScore = BigDecimal
.valueOf(
assetsSummary
.stream()
.filter(summary -> summary.score() != null)
.mapToDouble(ScoringReport.Summary::score)
.average()
.orElse(0.0)
.orElse(SCORING_NOT_AVAILABLE)
)
.setScale(2, RoundingMode.HALF_EVEN);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,6 @@ public Completable execute(Input input) {
)
)
.flatMapCompletable(request -> {
if (request.assets().isEmpty()) {
return Completable.complete();
}
var job = newScoringJob(request.jobId(), input.auditInfo, input.apiId);
return scoringProvider.requestScore(request).doOnComplete(() -> asyncJobCrudService.create(job));
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,8 @@ public static ScoringReport anErrorAssertScoringReport() {
)
.build();
}

public static ScoringReport aScoringReportWithNoAssets() {
return BASE.get().summary(new ScoringReport.Summary(-1.0, 0L, 0L, 0L, 0L)).assets(List.of()).build();
}
}
Loading

0 comments on commit 72e8aea

Please sign in to comment.