Skip to content

RHIDP-14173: Implement scalar aggregation KPI types#3571

Draft
imykhno wants to merge 5 commits into
redhat-developer:mainfrom
imykhno:feat/scorecard-scalar-aggregation-types
Draft

RHIDP-14173: Implement scalar aggregation KPI types#3571
imykhno wants to merge 5 commits into
redhat-developer:mainfrom
imykhno:feat/scorecard-scalar-aggregation-types

Conversation

@imykhno

@imykhno imykhno commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Hey, I just made a Pull Request!

Implemented logic to support the following aggregate metric types: average, sum, max, min, and count for Scorecard aggregated KPI cards.

This PR for:

✔️ Checklist

  • A changeset describing the change and affected packages. (more info)
  • Added or Updated documentation
  • Tests for new functionality and regression tests for bug fixes
  • Screenshots attached (for UI changes)

How to test

Preparing

  • If using app-config.local.yaml add new aggregation types under scorecard.aggregationKPIs (example added to the app-config.yaml):
scorecard:
  aggregationKPIs:
    totalOpenBugs:
      title: Total Open Bugs
      description: Sum of open issues across owned entities
      type: sum
      metricId: jira.open_issues
      options:
        thresholds:
          rules:
            - key: success
              expression: '>=80'
              color: '#6bb300' # green
            - key: warning
              expression: '<80'
              color: 'rgb(224, 189, 108)' # light orange
    avgOpenPrs:
      title: Average Open PRs
      description: Mean open PR count per entity
      type: average
      metricId: github.open_prs
    entitiesWithOpenIssues:
      title: Entities with Open Issues
      description: Count of entities with a stored open-issues value
      type: count
      metricId: jira.open_issues
    maxOpenPrs:
      title: Maximum Open PRs
      description: Maximum open PR count per entity
      type: max
      metricId: github.open_prs
    minOpenPrs:
      title: Minimum Open PRs
      description: Minimum open PR count per entity
      type: min
      metricId: github.open_prs
  • Set environment variables for convenience
export BACKSTAGE_URL="http://localhost:7007"
export TOKEN="your-auth-token-here"

Testing

Get aggregation result when sum aggregation type was used (totalOpenBugs):

curl -X GET "${BACKSTAGE_URL}/api/scorecard/aggregations/totalOpenBugs" \
  -H "Authorization: Bearer ${TOKEN}" | jq

Get aggregation result when average aggregation type was used (avgOpenPrs):

curl -X GET "${BACKSTAGE_URL}/api/scorecard/aggregations/avgOpenPrs" \
  -H "Authorization: Bearer ${TOKEN}" | jq

Get aggregation result when count aggregation type was used (entitiesWithOpenIssues):

curl -X GET "${BACKSTAGE_URL}/api/scorecard/aggregations/entitiesWithOpenIssues" \
  -H "Authorization: Bearer ${TOKEN}" | jq

Get aggregation result when max aggregation type was used (maxOpenPrs):

curl -X GET "${BACKSTAGE_URL}/api/scorecard/aggregations/maxOpenPrs" \
  -H "Authorization: Bearer ${TOKEN}" | jq

Get aggregation result when min aggregation type was used (minOpenPrs):

curl -X GET "${BACKSTAGE_URL}/api/scorecard/aggregations/minOpenPrs" \
  -H "Authorization: Bearer ${TOKEN}" | jq

Get aggregation results when some entities lack values due to an error

The environment must contain a mix of entities: some with valid values for aggregation, and others without values to simulate an error state.

Use one of the proposed requests below to confirm that:

  • Aggregation only occurs for entities with valid values.
  • The total count of entities with errors is correctly populated in the calculationErrorCount attribute.

Get aggregation results when all entities lack values due to an error

The environment must contain a all of entities without values to simulate an error state.

Use one of the proposed requests below to confirm that:

  • The total count of entities with errors is correctly populated in the calculationErrorCount attribute.

When the threshold is incorrectly configured for new aggregation types

Configure a threshold with incorrect ranges for a new aggregation type. The application is expected to throw an error upon startup.

For example:

    wrongThresholds:
      title: Wrong Thresholds
      description: Wrong thresholds
      type: sum
      metricId: jira.open_issues
      options:
        thresholds:
          rules:
            - key: success
              expression: '>=80'
              color: '#6bb300' # green
            - key: warning
              expression: '<20'
              color: 'rgb(224, 189, 108)' # light orange

When the threshold is correctly configured for new aggregation types

A correctly configured threshold should be present in the response under result.thresholds. If no threshold is configured under app-config.yaml, the default threshold should be used.

When a new aggregation type is configured for a boolean provider

When a metricId for a boolean metric provider is supplied, an error should be thrown when the application starts up.

For example:

    licenseFileExistsKpi:
      title: License File Exists KPI
      type: sum
      description: This KPI is provide information about whether the license file exists in the repository.
      metricId: filecheck.license

@github-actions

Copy link
Copy Markdown
Contributor

This pull request adds a new top-level directory under workspaces/. Please follow Submitting a Pull Request for a New Workspace in CONTRIBUTING.md.

@rhdh-gh-app

rhdh-gh-app Bot commented Jun 24, 2026

Copy link
Copy Markdown

Important

This PR includes changes that affect public-facing API. Please ensure you are adding/updating documentation for new features or behavior.

Changed Packages

Package Name Package Path Changeset Bump Current Version
app-legacy workspaces/scorecard/packages/app-legacy none v0.0.0
@red-hat-developer-hub/backstage-plugin-scorecard-backend workspaces/scorecard/plugins/scorecard-backend major v2.7.9
@red-hat-developer-hub/backstage-plugin-scorecard-common workspaces/scorecard/plugins/scorecard-common major v2.7.9
@red-hat-developer-hub/backstage-plugin-scorecard workspaces/scorecard/plugins/scorecard major v2.7.9

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 24, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:02 PM UTC · Completed 5:19 PM UTC
Commit: a50b883 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review

Findings

Low

  • [Documentation / code mismatch] workspaces/scorecard/plugins/scorecard/README.md — The README (as modified by this PR) states the built-in card renders statusGrouped, weightedStatusScore, and scalar types. However, the AggregatedMetricCard component only handles statusGrouped and weightedStatusScore; all scalar types fall through to UnsupportedAggregationType. The documentation overstates current frontend capability.
    Remediation: Adjust the README sentence to clarify that scalar types are supported by the backend but not yet rendered by the built-in frontend card, or add a note that scalar card rendering is pending.

  • [Test integrity / assertion removal] workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/strategies/statusGroupedAggregationStrategy.test.ts — The test "delegates empty entityRefs to loader" had its assertion on result.result.values removed. The remaining assertions only check total, entitiesConsidered, and calculationErrorCount. While the first test in the file covers values mapping in detail, consider restoring an assertion on the values array shape for this empty-entityRefs case.

  • [Truthiness check on numeric value] workspaces/scorecard/plugins/scorecard-backend/src/database/DatabaseMetricValues.ts — In readScalarAggregationByLatestIdsSubquery, const value = aggregateRow?.value ? Number(aggregateRow.value) : 0 uses a truthiness check. If the SQL aggregate returns 0, the truthiness check treats it as falsy and falls through to 0 — which is correct by coincidence. A != null check would express the intent more clearly and be resilient to future changes.

Previous run

Review

Findings

Medium

  • [Documentation/code mismatch] workspaces/scorecard/plugins/scorecard/README.md:9 — The README states "The built-in card renders statusGrouped (multi-slice pie), weightedStatusScore (weighted health donut), and scalar types (sum, average, max, min, count)." However, the AggregatedMetricCard component only handles statusGrouped and weightedStatusScore via explicit type guards; all scalar types fall through to UnsupportedAggregationType (an error panel). The README overstates the frontend's current rendering capability.
    Remediation: Update the README to clarify that scalar types are supported by the backend API but do not yet have a dedicated frontend card component, or remove the scalar types from the frontend README's feature bullet.

Low

  • [Test coverage reduction] workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/strategies/statusGroupedAggregationStrategy.test.ts:123 — The "delegates empty entityRefs to loader" test previously asserted that result.result.values contained per-threshold-rule entries with count 0 (error, warning, success). This assertion was removed without replacement, reducing test coverage for the StatusGroupedAggregationStrategy's behavior with empty inputs.

  • [Falsy check on numeric value] workspaces/scorecard/plugins/scorecard-backend/src/database/DatabaseMetricValues.ts — In readScalarAggregationByLatestIdsSubquery, the expression aggregateRow?.value ? Number(aggregateRow.value) : 0 uses a truthiness check on a numeric value. When the SQL aggregate returns 0 (e.g., SUM of all-zero values), the check is falsy and falls to the default 0 — numerically correct by coincidence but fragile. A stricter null/undefined check like aggregateRow?.value != null ? Number(aggregateRow.value) : 0 would be more robust.

Previous run (2)

Review

Findings

Medium

  • [consumer-completeness] plugins/scorecard/src/components/AggregatedMetricCards/AggregatedMetricCard.tsx — The new scalar aggregation types (sum, average, max, min, count) are registered in the backend strategy registry and returned via the API, but the frontend AggregatedMetricCard component only handles statusGrouped and weightedStatusScore. Scalar types fall through to UnsupportedAggregationType, rendering an error panel. The app-config.yaml in this PR adds scalar KPI entries (totalOpenBugs type:sum, avgOpenPrs type:average, etc.) that would trigger this error on the homepage.
    Remediation: Either add a scalar card renderer in the frontend, remove the scalar KPI entries from app-config.yaml, or document that scalar types are backend-only and homepage rendering is planned for a follow-up.

Low

  • [edge-case] plugins/scorecard-backend/src/database/DatabaseMetricValues.ts — In readScalarAggregationByLatestIdsSubquery, using truthiness check (value ? Number(value) : 0) means a legitimate computed value of 0 will also be treated as 0, which is coincidentally correct but fragile. If a future change uses a different sentinel, this could break.

  • [test-adequacy] plugins/scorecard-backend/src/service/aggregations/strategies/statusGroupedAggregationStrategy.test.ts — The test for empty entityRefs removed the assertion that verified the values array contains zeroed counts per threshold rule. Coverage for this specific invariant is reduced. Consider restoring the removed assertion.


Labels: PR implements new scalar aggregation KPI types as a feature enhancement to the scorecard workspace.

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment enhancement New feature or request labels Jun 24, 2026
@imykhno imykhno force-pushed the feat/scorecard-scalar-aggregation-types branch from 22368af to 5a7cfb0 Compare June 24, 2026 17:28
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 24, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 5:31 PM UTC · Ended 5:44 PM UTC
Commit: 6c9cfa5 · View workflow run →

@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.93617% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.26%. Comparing base (381f36a) to head (53fdb59).
⚠️ Report is 38 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3571      +/-   ##
==========================================
+ Coverage   54.19%   54.26%   +0.06%     
==========================================
  Files        2303     2309       +6     
  Lines       88305    88434     +129     
  Branches    24590    24630      +40     
==========================================
+ Hits        47858    47989     +131     
+ Misses      40238    40236       -2     
  Partials      209      209              
Flag Coverage Δ *Carryforward flag
adoption-insights 83.70% <ø> (ø) Carriedforward from 132f773
ai-integrations 67.95% <ø> (ø) Carriedforward from 132f773
app-defaults 69.79% <ø> (ø) Carriedforward from 132f773
augment 46.39% <ø> (ø) Carriedforward from 132f773
boost 74.05% <ø> (ø) Carriedforward from 132f773
bulk-import 72.46% <ø> (ø) Carriedforward from 132f773
cost-management 14.10% <ø> (ø) Carriedforward from 132f773
dcm 61.79% <ø> (ø) Carriedforward from 132f773
extensions 61.53% <ø> (ø) Carriedforward from 132f773
global-floating-action-button 71.18% <ø> (ø) Carriedforward from 132f773
global-header 59.71% <ø> (ø) Carriedforward from 132f773
homepage 49.84% <ø> (ø) Carriedforward from 132f773
install-dynamic-plugins 56.77% <ø> (ø) Carriedforward from 132f773
konflux 91.49% <ø> (ø) Carriedforward from 132f773
lightspeed 68.55% <ø> (ø) Carriedforward from 132f773
mcp-integrations 85.46% <ø> (ø) Carriedforward from 132f773
orchestrator 38.30% <ø> (ø) Carriedforward from 132f773
quickstart 63.76% <ø> (ø) Carriedforward from 132f773
sandbox 79.56% <ø> (ø) Carriedforward from 132f773
scorecard 84.63% <98.93%> (+0.67%) ⬆️
theme 61.26% <ø> (ø) Carriedforward from 132f773
translations 7.25% <ø> (ø) Carriedforward from 132f773
x2a 78.68% <ø> (ø) Carriedforward from 132f773

*This pull request uses carry forward flags. Click here to find out more.


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 381f36a...53fdb59. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment and removed requires-manual-review Review requires human judgment labels Jun 24, 2026
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:31 PM UTC · Completed 5:44 PM UTC
Commit: 6c9cfa5 · View workflow run →

imykhno added 5 commits June 25, 2026 14:48
…dStatusScore`

Signed-off-by: Ihor Mykhno <imykhno@redhat.com>
… safety and improved structure

Signed-off-by: Ihor Mykhno <imykhno@redhat.com>
…dKpi` across tests and configurations

Signed-off-by: Ihor Mykhno <imykhno@redhat.com>
Signed-off-by: Ihor Mykhno <imykhno@redhat.com>
Signed-off-by: Ihor Mykhno <imykhno@redhat.com>
@imykhno imykhno force-pushed the feat/scorecard-scalar-aggregation-types branch from 5a7cfb0 to 53fdb59 Compare June 25, 2026 13:01
@sonarqubecloud

Copy link
Copy Markdown

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 25, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:04 PM UTC · Completed 1:17 PM UTC
Commit: 381f36a · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot added ready-for-merge All reviewers approved — ready to merge and removed requires-manual-review Review requires human judgment labels Jun 25, 2026
};
if (aggregationConfig.type === aggregationTypes.weightedStatusScore) {
aggregationConfig.options = buildWeightedStatusScoreOptions(config);
} else if (isScalarAggregationType(aggregationConfig.type)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With average changing meaning from weighted-status-score to scalar mean, an un-migrated config that still has type: average + options.statusScores won't fail startup — this branch only copies thresholds, so the leftover statusScores is silently dropped before the strict Zod schema can reject it, and the KPI quietly starts returning a scalar mean with a different result shape. Should we fail startup when a scalar-type KPI still carries options.statusScores, with a "did you mean weightedStatusScore?" hint? Passing the raw options through to the schema would also catch it via strictObject.


- **Entity scorecard tab** — View scorecard metrics on catalog entity pages (components, websites, etc.).
- **Scorecard homepage card** — Show aggregated KPIs on the home page (e.g. GitHub open PRs, Jira open issues). Supports **`statusGrouped`** (multi-slice pie) and **`average`** (weighted health donut) KPI types configured under **`scorecard.aggregationKPIs`**.
- **Scorecard homepage card** — Show aggregated KPIs on the home page (e.g. GitHub open PRs, Jira open issues). The built-in card renders **`statusGrouped`** (multi-slice pie), **`weightedStatusScore`** (weighted health donut), and scalar types (`sum`, `average`, `max`, `min`, `count`).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This says the built-in card renders the scalar types, but AggregatedMetricCard.tsx only handles statusGrouped and weightedStatusScoresum/average/max/min/count all fall through to UnsupportedAggregationType. If the scalar card UI is a follow-up, maybe say scalar KPIs are API-only for now, so users configuring a sum KPI don't file bugs about the error panel on their homepage.

z.object({
key: z.string(),
expression: z.string(),
color: z.string(),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

color is required here, but thresholds.md section 5 says the rules take an optional color, and AggregationThresholdRule in scorecard-common keeps it optional too (Pick<ThresholdRule, ...> where color?). So a scalar KPI rule without a color fails at startup even though config.d.ts and the docs both allow it. Should this be z.string().optional(), or should the docs/type change?

} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';

function parseAggregationConfig(config: unknown): AggregationConfig {
function isScalarAggregationType(type: string): boolean {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this the same helper as src/utils/isScalarAggregationType.ts added in this PR? isScalarAggregationRuntimeConfig repeats the .includes pattern a third time. Could we import the one util everywhere — and make it a real type guard (type is (typeof scalarAggregationTypes)[number]) while at it?

)
.first();

const value = aggregateRow?.value ? Number(aggregateRow.value) : 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The truthiness check conflates "NULL aggregate" with "aggregate equals 0" — it works today because a numeric 0 falls through to the same 0, but aggregateRow?.value != null ? Number(aggregateRow.value) : 0 says what's meant and won't break if the fallback ever changes. Same for total on the next line.

| **`min`** | Minimum latest metric value across owned entities | “Best-case / lowest value in the portfolio.” |
| **`count`** | Number of entities with a non-null latest stored value | “How many entities have data for this metric.” |

**Scalar types** (`sum`, `average`, `max`, `min`, `count`) aggregate the **numeric `value`** from each owned entity’s **latest** stored `metric_values` row for the configured **`metricId`**, instead of bucketing by threshold status. Clients can detect scalar responses by checking **`metadata.aggregationType`** against the scalar type literals (or `scalarAggregationTypes` from scorecard-common).\*\*\*\*

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this paragraph and the one at line 39 both open with "Scalar types (sum, ...)" and partially repeat each other — worth merging into one. There's also a stray escaped \*\*\*\* at the end of this line.

3. **Denominator and percentage:** Let **`maxWeight`** be the maximum of **`options.statusScores[rule.key]`** over each **`rule.key`** in the metric’s **merged threshold rules** (missing map entries are treated as **0** here). **`weightedStatusMaxPossible`** = **`maxWeight × total entities`**. If **`total`** is 0 or **`weightedStatusMaxPossible`** is 0, **`weightedStatusScore`** is **0**; otherwise **`weightedStatusScore`** = **`100 × (weighted sum / weightedStatusMaxPossible)`** rounded to **one decimal place** (the API and UI use this **0–100** headline directly). The value can exceed **100** if **`statusScores`** assigns a weight above **`maxWeight`** to a status that still appears in the aggregated counts; keep **`statusScores`** aligned with your metric rules to avoid that.

**`options.thresholds`:** **number**-style rules (same shape as metric thresholds) evaluated against **`averageScore`** on the **0–100** scale (higher = better for typical setups). The first matching rule supplies **`result.aggregationChartDisplayColor`**. If omitted from **`scorecard.aggregationKPIs`**, it stays unset in the built KPI config; **`AverageAggregationStrategy`** then applies **`DEFAULT_AVERAGE_KPI_RESULT_THRESHOLDS`** from [`src/constants/aggregationKPIs.ts`](../src/constants/aggregationKPIs.ts) when each aggregation runs and logs **info** that the default 0–100% health scale is used: **<30%** = error, **30–79%** = warning, **≥80%** = success. Full detail: [thresholds.md — Aggregation KPI result thresholds](./thresholds.md#4-aggregation-kpi-result-thresholds-average-type) and [backend README — Aggregation KPIs](../README.md#aggregation-kpis-homepage-and-get-aggregations).
**`options.thresholds`:** **number**-style rules (same shape as metric thresholds) evaluated against **`weightedStatusScore`** on the **0–100** scale (higher = better for typical setups). The first matching rule supplies **`result.aggregationChartDisplayColor`**. If omitted from **`scorecard.aggregationKPIs`**, it stays unset in the built KPI config; **`WeightedStatusScoreAggregationStrategy`** then applies **`DEFAULT_WEIGHTED_STATUS_SCORE_KPI_RESULT_THRESHOLDS`** from [`src/constants/aggregationKPIs.ts`](../src/constants/aggregationKPIs.ts) when each aggregation runs and logs **info** that the default 0–100% health scale is used: **<30%** = error, **30–79%** = warning, **≥80%** = success. Full detail: [thresholds.md — Aggregation KPI result thresholds](./thresholds.md#4-aggregation-kpi-result-thresholds-weighted-status-score-type) and [backend README — Aggregation KPIs](../README.md#aggregation-kpis-homepage-and-get-aggregations).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this anchor doesn't match the heading — "### 4. Aggregation KPI result thresholds (weightedStatusScore type)" slugs to #4-aggregation-kpi-result-thresholds-weightedstatusscore-type (the backend README uses the correct form).

| `description` | Display description for this aggregation. |
| `type` | Aggregation algorithm: `statusGrouped` (counts per threshold status), `weightedStatusScore` (normalized weighted score), or scalar types `sum`, `average`, `max`, `min`, `count` (numeric rollup over latest metric values). |
| `metricId` | Metric provider id used to load thresholds and compute counts or values. |
| `options` | Type-specific options. For **`weightedStatusScore`**: **required** **`options.statusScores`** (status key → weight); optional **`options.thresholds`** (see [thresholds.md — Aggregation KPI result thresholds (weightedStatusScore)](./docs/thresholds.md#4-aggregation-kpi-result-thresholds-weightedstatusscore-type)). For **scalar types**: optional **`options.thresholds`** (see [thresholds.md — Aggregation KPI result thresholds (scalar types)](./docs/thresholds.md#5-aggregation-kpi-result-thresholds-scalar-types)). All scalar types require a **number** metric. Scalar responses include **`value`**, **`total`**, **`entitiesConsidered`**, **`calculationErrorCount`**, **`timestamp`**, and **`thresholds`** — see [aggregation.md — Scalar result fields](./docs/aggregation.md#scalar-result-fields). |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: aggregation.md#scalar-result-fields points to a heading that doesn't exist — there's no "Scalar result fields" section in aggregation.md (thresholds.md has the same link in section 5). Either add that section or point at the scalar response-shape bullets.


describe('readScalarAggregatedMetricByEntityRefs', () => {
describe.each(databases.eachSupportedId())(
'should %p raw metric values across latest rows',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this describe.each title renders as "should POSTGRES_13 raw metric values across latest rows", which reads oddly next to the per-it titles that already describe the operation. Also, the avg assertion below (value: 5.666666666666667) relies on exact float equality across pg and SQLite — toBeCloseTo would be safer.

*/

import { scalarAggregationTypes } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';
import { AggregationType } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: two separate imports from the same package — can be merged into one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge/work-in-progress enhancement New feature or request ready-for-merge All reviewers approved — ready to merge workspace/scorecard

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants