From 7eba760e6f60cfa1d1f18f166f1fdd89316b6512 Mon Sep 17 00:00:00 2001 From: Roohn Date: Mon, 13 Mar 2023 16:10:52 +0100 Subject: [PATCH] Add aggregate results by entityRef Signed-off-by: Roohn --- .changeset/fast-snakes-buy.md | 7 +++++++ .../src/service/router.test.ts | 16 ++++++++++++++++ .../src/service/router.ts | 18 +++++++++++++++++- plugins/entity-feedback-common/api-report.md | 10 +++++++--- plugins/entity-feedback-common/src/index.ts | 11 ++++++++--- plugins/entity-feedback/api-report.md | 4 ++++ .../src/api/EntityFeedbackApi.ts | 6 ++++++ .../src/api/EntityFeedbackClient.test.ts | 18 ++++++++++++++++++ .../src/api/EntityFeedbackClient.ts | 17 +++++++++++++++++ 9 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 .changeset/fast-snakes-buy.md diff --git a/.changeset/fast-snakes-buy.md b/.changeset/fast-snakes-buy.md new file mode 100644 index 0000000000..711588918d --- /dev/null +++ b/.changeset/fast-snakes-buy.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-entity-feedback': minor +'@backstage/plugin-entity-feedback-backend': patch +'@backstage/plugin-entity-feedback-common': patch +--- + +Added an endpoint to fetch anonymous aggregated results from an entity diff --git a/plugins/entity-feedback-backend/src/service/router.test.ts b/plugins/entity-feedback-backend/src/service/router.test.ts index 74fae8248b..5df0c5cf9d 100644 --- a/plugins/entity-feedback-backend/src/service/router.test.ts +++ b/plugins/entity-feedback-backend/src/service/router.test.ts @@ -258,6 +258,22 @@ describe('createRouter', () => { }); }); + describe('GET /ratings/:entityRef/aggregate', () => { + it('should get aggregated ratings for an entity correctly', async () => { + const response = await request(app) + .get('/ratings/component%3Adefault%2Fservice/aggregate') + .send(); + expect(mockDbHandler.getRatings).toHaveBeenCalledWith( + 'component:default/service', + ); + expect(response.status).toEqual(200); + expect(response.body).toEqual({ + DISLIKE: 1, + LIKE: 2, + }); + }); + }); + describe('POST /responses/:entityRef', () => { it('should record a response correctly', async () => { const body = { response: 'blah', comments: 'feedback', consent: true }; diff --git a/plugins/entity-feedback-backend/src/service/router.ts b/plugins/entity-feedback-backend/src/service/router.ts index e1243cedaf..ea759c1639 100644 --- a/plugins/entity-feedback-backend/src/service/router.ts +++ b/plugins/entity-feedback-backend/src/service/router.ts @@ -25,7 +25,10 @@ import { getBearerTokenFromAuthorizationHeader, IdentityApi, } from '@backstage/plugin-auth-node'; -import { EntityRatingsData } from '@backstage/plugin-entity-feedback-common'; +import { + EntityRatingsData, + Ratings, +} from '@backstage/plugin-entity-feedback-common'; import express from 'express'; import Router from 'express-promise-router'; import { Logger } from 'winston'; @@ -167,6 +170,19 @@ export async function createRouter( res.json(ratings.filter(r => accessibleEntityRefs.includes(r.userRef))); }); + router.get('/ratings/:entityRef/aggregate', async (req, res) => { + const entityRatings = ( + await dbHandler.getRatings(req.params.entityRef) + ).reduce((ratings: Ratings, { rating }) => { + ratings[rating] = ratings[rating] ?? 0; + ratings[rating]++; + + return ratings; + }, {}); + + res.json(entityRatings); + }); + router.post('/responses/:entityRef', async (req, res) => { const user = await identity.getIdentity({ request: req }); const { response, comments, consent } = req.body; diff --git a/plugins/entity-feedback-common/api-report.md b/plugins/entity-feedback-common/api-report.md index 118e6c78f2..f31bb0d041 100644 --- a/plugins/entity-feedback-common/api-report.md +++ b/plugins/entity-feedback-common/api-report.md @@ -10,9 +10,7 @@ export interface EntityRatingsData { // (undocumented) entityTitle?: string; // (undocumented) - ratings: { - [ratingValue: string]: number; - }; + ratings: Ratings; } // @public (undocumented) @@ -38,4 +36,10 @@ export interface Rating { // (undocumented) userRef: string; } + +// @public (undocumented) +export interface Ratings { + // (undocumented) + [ratingValue: string]: number; +} ``` diff --git a/plugins/entity-feedback-common/src/index.ts b/plugins/entity-feedback-common/src/index.ts index eea289a899..247a5a5d54 100644 --- a/plugins/entity-feedback-common/src/index.ts +++ b/plugins/entity-feedback-common/src/index.ts @@ -40,13 +40,18 @@ export interface FeedbackResponse { userRef: string; } +/** + * @public + */ +export interface Ratings { + [ratingValue: string]: number; +} + /** * @public */ export interface EntityRatingsData { entityRef: string; entityTitle?: string; - ratings: { - [ratingValue: string]: number; - }; + ratings: Ratings; } diff --git a/plugins/entity-feedback/api-report.md b/plugins/entity-feedback/api-report.md index 06b0166b0d..696536aef4 100644 --- a/plugins/entity-feedback/api-report.md +++ b/plugins/entity-feedback/api-report.md @@ -13,6 +13,7 @@ import { EntityRatingsData } from '@backstage/plugin-entity-feedback-common'; import { FeedbackResponse } from '@backstage/plugin-entity-feedback-common'; import { FetchApi } from '@backstage/core-plugin-api'; import { Rating } from '@backstage/plugin-entity-feedback-common'; +import { Ratings } from '@backstage/plugin-entity-feedback-common'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; @@ -22,6 +23,7 @@ export interface EntityFeedbackApi { getAllRatings(): Promise; // (undocumented) getOwnedRatings(ownerRef: string): Promise; + getRatingAggregates(entityRef: string): Promise; // (undocumented) getRatings(entityRef: string): Promise[]>; // (undocumented) @@ -48,6 +50,8 @@ export class EntityFeedbackClient implements EntityFeedbackApi { // (undocumented) getOwnedRatings(ownerRef: string): Promise; // (undocumented) + getRatingAggregates(entityRef: string): Promise; + // (undocumented) getRatings(entityRef: string): Promise[]>; // (undocumented) getResponses( diff --git a/plugins/entity-feedback/src/api/EntityFeedbackApi.ts b/plugins/entity-feedback/src/api/EntityFeedbackApi.ts index d2f1a46f55..7ec6a447f0 100644 --- a/plugins/entity-feedback/src/api/EntityFeedbackApi.ts +++ b/plugins/entity-feedback/src/api/EntityFeedbackApi.ts @@ -19,6 +19,7 @@ import { EntityRatingsData, FeedbackResponse, Rating, + Ratings, } from '@backstage/plugin-entity-feedback-common'; /** @@ -40,6 +41,11 @@ export interface EntityFeedbackApi { getRatings(entityRef: string): Promise[]>; + /** + * Returns anonymized aggregated results for one entityRef + */ + getRatingAggregates(entityRef: string): Promise; + recordResponse( entityRef: string, response: Omit, diff --git a/plugins/entity-feedback/src/api/EntityFeedbackClient.test.ts b/plugins/entity-feedback/src/api/EntityFeedbackClient.test.ts index 694fd66b06..8ef6810115 100644 --- a/plugins/entity-feedback/src/api/EntityFeedbackClient.test.ts +++ b/plugins/entity-feedback/src/api/EntityFeedbackClient.test.ts @@ -121,6 +121,24 @@ describe('EntityFeedbackClient', () => { expect(response).toEqual(ratings); }); + it('getRatingAggregates', async () => { + const ratings = { LIKE: 3, DISLIKE: 5 }; + + server.use( + rest.get( + `${mockBaseUrl}/ratings/${encodeURIComponent( + 'component:default/service', + )}/aggregate`, + (_, res, ctx) => res(ctx.json(ratings)), + ), + ); + + const response = await client.getRatingAggregates( + 'component:default/service', + ); + expect(response).toEqual(ratings); + }); + it('recordResponse', async () => { expect.assertions(1); const response = { diff --git a/plugins/entity-feedback/src/api/EntityFeedbackClient.ts b/plugins/entity-feedback/src/api/EntityFeedbackClient.ts index 34856473c8..f911cdc7f8 100644 --- a/plugins/entity-feedback/src/api/EntityFeedbackClient.ts +++ b/plugins/entity-feedback/src/api/EntityFeedbackClient.ts @@ -20,6 +20,7 @@ import { EntityRatingsData, FeedbackResponse, Rating, + Ratings, } from '@backstage/plugin-entity-feedback-common'; import { EntityFeedbackApi } from './EntityFeedbackApi'; @@ -97,6 +98,22 @@ export class EntityFeedbackClient implements EntityFeedbackApi { return resp.json(); } + async getRatingAggregates(entityRef: string): Promise { + const baseUrl = await this.discoveryApi.getBaseUrl('entity-feedback'); + const resp = await this.fetchApi.fetch( + `${baseUrl}/ratings/${encodeURIComponent(entityRef)}/aggregate`, + { + method: 'GET', + }, + ); + + if (!resp.ok) { + throw await ResponseError.fromResponse(resp); + } + + return resp.json(); + } + async recordResponse( entityRef: string, response: Omit,