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
7 changes: 7 additions & 0 deletions workspaces/scorecard/.changeset/orange-items-write.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@red-hat-developer-hub/backstage-plugin-scorecard-backend': minor
---

Added `metricIds` query parameter to the `/metrics` endpoint to filter metrics by metric IDs.

Scorecard read permission are no longer needed to get available metrics for the `/metrics` endpoint.
15 changes: 15 additions & 0 deletions workspaces/scorecard/examples/all-scorecards.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,18 @@ spec:
type: service
owner: user:development/guest
lifecycle: production
---
# Component with both GitHub and Jira Scorecards with not specified owner
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: all-scorecards-service-different-owner
annotations:
github.com/project-slug: redhat-developer/rhdh-plugins
backstage.io/source-location: url:https://github.com/redhat-developer/rhdh-plugins
jira/project-key: RSPT
jira/label: JupiterTeam
spec:
type: service
owner: rhdh-team
lifecycle: production
10 changes: 10 additions & 0 deletions workspaces/scorecard/packages/app/e2e-tests/scorecard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
getTranslations,
} from './utils/translationUtils';
import { runAccessibilityTests } from './utils/accessibility';
import { deleteRBAC } from './utils/rbacDelete';

test.describe.serial('Pre-RBAC Access Tests', () => {
let translations: ScorecardMessages;
Expand Down Expand Up @@ -90,6 +91,15 @@ test.describe.serial('Scorecard Plugin Tests', () => {
scorecardPage = new ScorecardPage(page, translations);
});

test.afterAll(async ({ browser }) => {
const context = await browser.newContext();
const page = await context.newPage();

await deleteRBAC(page);

await context.close();
});

test('Validate scorecard tabs for GitHub PRs and Jira tickets', async ({
page,
}, testInfo) => {
Expand Down
38 changes: 38 additions & 0 deletions workspaces/scorecard/packages/app/e2e-tests/utils/rbacDelete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright Red Hat, Inc.
*
* 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
*
* http://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.
*/
import { Page, expect } from '@playwright/test';

/**
* Deletes RBAC configuration after tests.
*/
export async function deleteRBAC(page: Page) {
await page.goto('/rbac');
const enterButton = page.getByRole('button', { name: 'Enter' });
await expect(enterButton).toBeVisible();
await enterButton.click();

await page.getByTestId('delete-role-role:default/rhdh-testing').click();

await page
.getByRole('textbox', { name: 'Role name' })
.fill('role:default/rhdh-testing');

await page.getByRole('button', { name: 'Delete' }).click();

await expect(
page.getByTestId('delete-role-role:default/rhdh-testing'),
).toBeHidden();
}
99 changes: 92 additions & 7 deletions workspaces/scorecard/plugins/scorecard-backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,19 +99,104 @@ Thresholds are evaluated in order, and the first matching rule determines the ca

For comprehensive threshold configuration guide, examples, and best practices, see [thresholds.md](./docs/thresholds.md).

## Entity Aggregation
## API Endpoints

The Scorecard plugin provides aggregation endpoints that return metrics for all entities owned by the authenticated user. This includes:
### `GET /metrics`

Returns a list of available metrics. Supports filtering by metric IDs or datasource.

#### Query Parameters

| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | -------------------------------------------------------------------------------------------- |
| `metricIds` | string | No | Comma-separated list of metric IDs to filter by (e.g., `github.open_prs,github.open_issues`) |
| `datasource` | string | No | Filter metrics by datasource ID (e.g., `github`, `jira`, `sonar`) |

#### Behavior

- If `metricIds` is provided, returns only the specified metrics
- If `datasource` is provided (and `metricIds` is not), returns all metrics from that datasource
- If neither parameter is provided, returns all available metrics
- **Note**: Providing both `metricIds` and `datasource` will result in a `400 Bad Request` error

#### Example Requests

```bash
# Get all metrics
curl -X GET "{{url}}/api/scorecard/metrics" \
-H "Authorization: Bearer <token>"

# Get specific metrics by IDs
curl -X GET "{{url}}/api/scorecard/metrics?metricIds=github.open_prs,github.open_issues" \
-H "Authorization: Bearer <token>"

# Get all metrics from a specific datasource
curl -X GET "{{url}}/api/scorecard/metrics?datasource=github" \
-H "Authorization: Bearer <token>"
```

### `GET /metrics/catalog/:kind/:namespace/:name`

Returns the latest metric values for a specific catalog entity.

#### Path Parameters

| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | ---------------------------------- |
| `kind` | string | Yes | Entity kind (e.g., `component`) |
| `namespace` | string | Yes | Entity namespace (e.g., `default`) |
| `name` | string | Yes | Entity name |

#### Query Parameters

| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | -------------------------------------------------------------------------------------------- |
| `metricIds` | string | No | Comma-separated list of metric IDs to filter by (e.g., `github.open_prs,github.open_issues`) |

#### Permissions

Requires `scorecard.metric.read` permission and `catalog.entity.read` permission for the specific entity.

#### Example Request

```bash
curl -X GET "{{url}}/api/scorecard/metrics/catalog/component/default/my-service?metricIds=github.open_prs" \
-H "Authorization: Bearer <token>"
```

### `GET /metrics/:metricId/catalog/aggregations`

Returns aggregated metrics for a specific metric across all entities owned by the authenticated user. This endpoint aggregates metrics from:

- Entities directly owned by the user
- Entities owned by groups the user is a direct member of (Only direct parent groups are considered)
- Entities owned by groups the user is a direct member of (only direct parent groups are considered)

#### Path Parameters

### Available Endpoints
| Parameter | Type | Required | Description |
| ---------- | ------ | -------- | --------------------------------- |
| `metricId` | string | Yes | The ID of the metric to aggregate |

- **`GET /metrics/catalog/aggregates`**: Returns aggregated metrics for all available metrics (optionally filtered by `metricIds` query parameter)
- **`GET /metrics/:metricId/catalog/aggregation`**: Returns aggregated metrics for a specific metric, with explicit access validation (returns `403` if the user doesn't have access to the metric)
#### Authentication

Requires user authentication. The endpoint uses the authenticated user's entity reference to determine which entities to aggregate.

#### Permissions

Requires `scorecard.metric.read` permission. Additionally:

- The user must have access to the specific metric (returns `403 Forbidden` if access is denied)
- The user must have `catalog.entity.read` permission for each entity that will be included in the aggregation

#### Example Request

```bash
# Get aggregated metrics for a specific metric
curl -X GET "{{url}}/api/scorecard/metrics/github.open_prs/catalog/aggregations" \
-H "Authorization: Bearer <token>"
```

For comprehensive documentation on how entity aggregation works, API details, examples, and best practices, see [aggregation.md](./docs/aggregation.md).
For comprehensive documentation on how entity aggregation works, including details on transitive parent groups, error handling, and best practices, see [aggregation.md](./docs/aggregation.md).

## Configuration cleanup Job

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Entity Aggregation

The Scorecard plugin provides aggregation endpoints that return metrics aggregated across all entities owned by the authenticated user. This feature allows users to get a consolidated view of metrics across their entire portfolio of owned entities.
The Scorecard plugin provides an aggregation endpoint that returns metrics aggregated across all entities owned by the authenticated user. This feature allows users to get a consolidated view of metrics across their entire portfolio of owned entities.

## Overview

The aggregation endpoints (`/metrics/catalog/aggregates` and `/metrics/:metricId/catalog/aggregation`) aggregate metrics from multiple entities based on entity ownership. They collect metrics from:
The aggregation endpoint (`/metrics/:metricId/catalog/aggregations`) aggregates metrics from multiple entities based on entity ownership. It collects metrics from:

- Entities directly owned by the user
- Entities owned by groups the user is a direct member of
Expand All @@ -28,39 +28,13 @@ In this case:
- ✅ Entities owned by `group:default/developers` are included
- ❌ Entities owned by `group:default/engineering` are **NOT** included

## API Endpoints
**Enabling Transitive Ownership:**

### `GET /metrics/catalog/aggregates`
To include entities from all parent groups in the aggregation (not just direct parent groups), you can enable transitive parent groups. If you're using Red Hat Developer Hub (RHDH), you can enable transitive parent groups by following the [transitive parent group enablement documentation](https://docs.redhat.com/en/documentation/red_hat_developer_hub/1.5/html-single/authorization_in_red_hat_developer_hub/index#enabling-transitive-parent-groups). This will allow the aggregation to traverse nested group hierarchies and include entities from all parent groups in the hierarchy.

Returns aggregated metrics for all entities owned by the authenticated user.
## API Endpoint

#### Query Parameters

| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | -------------------------------------------------------------------------------------------- |
| `metricIds` | string | No | Comma-separated list of metric IDs to filter. If not provided, returns all available metrics |

#### Authentication

Requires user authentication. The endpoint uses the authenticated user's entity reference to determine which entities to aggregate.

#### Permissions

Requires `scorecard.metric.read` permission. Additionally, the user must have `catalog.entity.read` permission for each entity that will be included in the aggregation.

#### Example Request

```bash
# Get all aggregated metrics
curl -X GET "{{url}}/api/scorecard/metrics/catalog/aggregates" \
-H "Authorization: Bearer <token>"

# Get specific metrics
curl -X GET "{{url}}/api/scorecard/metrics/catalog/aggregates?metricIds=github.open_prs,jira.open_issues" \
-H "Authorization: Bearer <token>"
```

### `GET /metrics/:metricId/catalog/aggregation`
### `GET /metrics/:metricId/catalog/aggregations`

Returns aggregated metrics for a specific metric across all entities owned by the authenticated user. This endpoint is useful when you need to check access to a specific metric and get its aggregation without requiring the `metricIds` query parameter.

Expand All @@ -85,14 +59,13 @@ Requires `scorecard.metric.read` permission. Additionally:

```bash
# Get aggregated metrics for a specific metric
curl -X GET "{{url}}/api/scorecard/metrics/github.open_prs/catalog/aggregation" \
curl -X GET "{{url}}/api/scorecard/metrics/github.open_prs/catalog/aggregations" \
-H "Authorization: Bearer <token>"
```

#### Differences from `/metrics/catalog/aggregates`
#### Key Features

- **Metric Access Validation**: This endpoint explicitly validates that the user has access to the specified metric and returns `403 Forbidden` if access is denied
- **Single Metric Only**: Returns aggregation for only the specified metric (no need for `metricIds` query parameter)
- **Empty Results Handling**: Returns an empty array `[]` when the user owns no entities, avoiding errors when filtering by a single metric

## Error Handling
Expand All @@ -101,8 +74,8 @@ curl -X GET "{{url}}/api/scorecard/metrics/github.open_prs/catalog/aggregation"

If the authenticated user doesn't have an entity reference in the catalog:

- **Status Code**: `403 Forbidden`
- **Error**: `NotAllowedError: User entity reference not found`
- **Status Code**: `404 Not Found`
- **Error**: `NotFoundError: User entity reference not found`

### Permission Denied

Expand All @@ -111,12 +84,12 @@ If the user doesn't have permission to read a specific entity:
- **Status Code**: `403 Forbidden`
- **Error**: Permission denied for the specific entity

### Metric Access Denied (for `/metrics/:metricId/catalog/aggregation`)
### Metric Access Denied (for `/metrics/:metricId/catalog/aggregations`)

If the user doesn't have access to the specified metric:

- **Status Code**: `403 Forbidden`
- **Error**: `NotAllowedError: Access to metric "<metricId>" denied`
- **Error**: `NotAllowedError: To view the scorecard metrics, your administrator must grant you the required permission.`

### Invalid Query Parameters

Expand All @@ -127,8 +100,9 @@ If invalid query parameters are provided:

## Best Practices

1. **Use Metric Filtering**: When you only need specific metrics, use the `metricIds` parameter to reduce response size and improve performance
1. **Handle Empty Results**: Always check for empty arrays when the user owns no entities

2. **Handle Empty Results**: Always check for empty arrays when the user owns no entities
2. **Group Structure**: Be aware of the direct parent group limitation when designing your group hierarchy. You currently receive scorecard results only for entities you own and those of your immediate parent group. To include results from _all_ parent
groups, you can either implement custom logic, restructure your groups, or (if using RHDH), enable transitive parent groups ([see transitive parent group enablement documentation](https://docs.redhat.com/en/documentation/red_hat_developer_hub/1.5/html-single/authorization_in_red_hat_developer_hub/index#enabling-transitive-parent-groups)).

3. **Group Structure**: Be aware of the direct parent group limitation when designing your group hierarchy. If you need nested group aggregation, consider restructuring your groups or implementing custom logic
3. **Metric Access**: This endpoint validates metric access upfront, so you'll get a clear `403 Forbidden` error if the user doesn't have permission to view the specified metric
Loading