From 4dba6a6551a9a3d8a2e988aa81ca5b67cedabd48 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Mon, 27 Nov 2023 11:19:49 +0100 Subject: [PATCH 1/6] Add openapi client generator to the notifications-frontend --- plugins/notifications-frontend/package.json | 10 ++++---- .../src/openapi/.openapi-generator-ignore | 23 +++++++++++++++++++ .../src/openapi/.openapi-generator/FILES | 11 +++++++++ .../src/openapi/.openapi-generator/VERSION | 1 + .../src/openapi/README.md | 8 +++++++ 5 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 plugins/notifications-frontend/src/openapi/.openapi-generator-ignore create mode 100644 plugins/notifications-frontend/src/openapi/.openapi-generator/FILES create mode 100644 plugins/notifications-frontend/src/openapi/.openapi-generator/VERSION create mode 100644 plugins/notifications-frontend/src/openapi/README.md diff --git a/plugins/notifications-frontend/package.json b/plugins/notifications-frontend/package.json index 4f558bfc01..c2f066b75c 100644 --- a/plugins/notifications-frontend/package.json +++ b/plugins/notifications-frontend/package.json @@ -22,19 +22,20 @@ "clean": "backstage-cli package clean", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", - "tsc": "tsc" + "tsc": "tsc", + "openapi:generate": "openapi-generator-cli generate -i ../notifications-backend/src/openapi.yaml -g typescript-fetch -o ./src/openapi" }, "dependencies": { "@backstage/core-components": "^0.13.6", "@backstage/core-plugin-api": "^1.7.0", - "@backstage/theme": "^0.4.3", "@backstage/plugin-notifications-common": "0.1.0", - "lodash": "^4.17.21", + "@backstage/theme": "^0.4.3", + "@material-table/core": "^3.1.0", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.11.3", "@material-ui/lab": "^4.0.0-alpha.45", - "@material-table/core": "^3.1.0", "@mui/material": "^5.12.2", + "lodash": "^4.17.21", "react-use": "^17.4.0" }, "peerDependencies": { @@ -46,6 +47,7 @@ "@backstage/core-app-api": "1.11.0", "@backstage/dev-utils": "1.0.22", "@backstage/test-utils": "^1.4.4", + "@openapitools/openapi-generator-cli": "^2.7.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^12.1.5", "@testing-library/user-event": "^14.5.1", diff --git a/plugins/notifications-frontend/src/openapi/.openapi-generator-ignore b/plugins/notifications-frontend/src/openapi/.openapi-generator-ignore new file mode 100644 index 0000000000..7484ee590a --- /dev/null +++ b/plugins/notifications-frontend/src/openapi/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/plugins/notifications-frontend/src/openapi/.openapi-generator/FILES b/plugins/notifications-frontend/src/openapi/.openapi-generator/FILES new file mode 100644 index 0000000000..d0367843c0 --- /dev/null +++ b/plugins/notifications-frontend/src/openapi/.openapi-generator/FILES @@ -0,0 +1,11 @@ +apis/NotificationsApi.ts +apis/index.ts +index.ts +models/Action.ts +models/CreateBody.ts +models/CreateBodyActionsInner.ts +models/CreateNotification200Response.ts +models/GetNotificationsCount200Response.ts +models/Notification.ts +models/index.ts +runtime.ts diff --git a/plugins/notifications-frontend/src/openapi/.openapi-generator/VERSION b/plugins/notifications-frontend/src/openapi/.openapi-generator/VERSION new file mode 100644 index 0000000000..3769235d3e --- /dev/null +++ b/plugins/notifications-frontend/src/openapi/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.1.0 \ No newline at end of file diff --git a/plugins/notifications-frontend/src/openapi/README.md b/plugins/notifications-frontend/src/openapi/README.md new file mode 100644 index 0000000000..0038eecba5 --- /dev/null +++ b/plugins/notifications-frontend/src/openapi/README.md @@ -0,0 +1,8 @@ +The content of this folder is genereted, do not update it manually. + +Generated by: + +``` +cd plugins/notifications-frontend +yarn openapi:generate +``` From f84cc14c0b24d289585ae89b6d0df7c25eb8ace7 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Mon, 27 Nov 2023 14:21:43 +0100 Subject: [PATCH 2/6] Regenerate openapi client in the frontend --- .../notifications-frontend/openapitools.json | 7 + .../src/openapi/apis/NotificationsApi.ts | 375 +++++++++++++ .../src/openapi/apis/index.ts | 3 + .../src/openapi/index.ts | 5 + .../src/openapi/models/Action.ts | 85 +++ .../src/openapi/models/CreateBody.ts | 129 +++++ .../openapi/models/CreateBodyActionsInner.ts | 80 +++ .../models/CreateNotification200Response.ts | 73 +++ .../GetNotificationsCount200Response.ts | 73 +++ .../src/openapi/models/Notification.ts | 139 +++++ .../src/openapi/models/index.ts | 8 + .../src/openapi/runtime.ts | 531 ++++++++++++++++++ 12 files changed, 1508 insertions(+) create mode 100644 plugins/notifications-frontend/openapitools.json create mode 100644 plugins/notifications-frontend/src/openapi/apis/NotificationsApi.ts create mode 100644 plugins/notifications-frontend/src/openapi/apis/index.ts create mode 100644 plugins/notifications-frontend/src/openapi/index.ts create mode 100644 plugins/notifications-frontend/src/openapi/models/Action.ts create mode 100644 plugins/notifications-frontend/src/openapi/models/CreateBody.ts create mode 100644 plugins/notifications-frontend/src/openapi/models/CreateBodyActionsInner.ts create mode 100644 plugins/notifications-frontend/src/openapi/models/CreateNotification200Response.ts create mode 100644 plugins/notifications-frontend/src/openapi/models/GetNotificationsCount200Response.ts create mode 100644 plugins/notifications-frontend/src/openapi/models/Notification.ts create mode 100644 plugins/notifications-frontend/src/openapi/models/index.ts create mode 100644 plugins/notifications-frontend/src/openapi/runtime.ts diff --git a/plugins/notifications-frontend/openapitools.json b/plugins/notifications-frontend/openapitools.json new file mode 100644 index 0000000000..15fef607c4 --- /dev/null +++ b/plugins/notifications-frontend/openapitools.json @@ -0,0 +1,7 @@ +{ + "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "7.1.0" + } +} diff --git a/plugins/notifications-frontend/src/openapi/apis/NotificationsApi.ts b/plugins/notifications-frontend/src/openapi/apis/NotificationsApi.ts new file mode 100644 index 0000000000..05eca75f75 --- /dev/null +++ b/plugins/notifications-frontend/src/openapi/apis/NotificationsApi.ts @@ -0,0 +1,375 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Notifications Plugin - OpenAPI Specs + * Notifications Plugin - OpenAPI Specs + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import type { + CreateBody, + CreateNotification200Response, + GetNotificationsCount200Response, + Notification, +} from '../models/index'; +import { + CreateBodyFromJSON, + CreateBodyToJSON, + CreateNotification200ResponseFromJSON, + CreateNotification200ResponseToJSON, + GetNotificationsCount200ResponseFromJSON, + GetNotificationsCount200ResponseToJSON, + NotificationFromJSON, + NotificationToJSON, +} from '../models/index'; +import * as runtime from '../runtime'; + +export interface CreateNotificationRequest { + createBody?: CreateBody; +} + +export interface GetNotificationsRequest { + pageSize?: number; + pageNumber?: number; + orderBy?: GetNotificationsOrderByEnum; + orderByDirec?: GetNotificationsOrderByDirecEnum; + containsText?: string; + createdAfter?: Date; + messageScope?: GetNotificationsMessageScopeEnum; + user?: string; + read?: boolean; +} + +export interface GetNotificationsCountRequest { + containsText?: string; + createdAfter?: Date; + messageScope?: GetNotificationsCountMessageScopeEnum; + user?: string; + read?: boolean; +} + +export interface SetReadRequest { + messageId: string; + user: string; + read: boolean; +} + +/** + * + */ +export class NotificationsApi extends runtime.BaseAPI { + /** + * Create notification + * Create notification + */ + async createNotificationRaw( + requestParameters: CreateNotificationRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + const response = await this.request( + { + path: `/notifications`, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: CreateBodyToJSON(requestParameters.createBody), + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, jsonValue => + CreateNotification200ResponseFromJSON(jsonValue), + ); + } + + /** + * Create notification + * Create notification + */ + async createNotification( + requestParameters: CreateNotificationRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.createNotificationRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + * Gets notifications + * Gets notifications + */ + async getNotificationsRaw( + requestParameters: GetNotificationsRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise>> { + const queryParameters: any = {}; + + if (requestParameters.pageSize !== undefined) { + queryParameters['pageSize'] = requestParameters.pageSize; + } + + if (requestParameters.pageNumber !== undefined) { + queryParameters['pageNumber'] = requestParameters.pageNumber; + } + + if (requestParameters.orderBy !== undefined) { + queryParameters['orderBy'] = requestParameters.orderBy; + } + + if (requestParameters.orderByDirec !== undefined) { + queryParameters['orderByDirec'] = requestParameters.orderByDirec; + } + + if (requestParameters.containsText !== undefined) { + queryParameters['containsText'] = requestParameters.containsText; + } + + if (requestParameters.createdAfter !== undefined) { + queryParameters['createdAfter'] = ( + requestParameters.createdAfter as any + ).toISOString(); + } + + if (requestParameters.messageScope !== undefined) { + queryParameters['messageScope'] = requestParameters.messageScope; + } + + if (requestParameters.user !== undefined) { + queryParameters['user'] = requestParameters.user; + } + + if (requestParameters.read !== undefined) { + queryParameters['read'] = requestParameters.read; + } + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request( + { + path: `/notifications`, + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, jsonValue => + jsonValue.map(NotificationFromJSON), + ); + } + + /** + * Gets notifications + * Gets notifications + */ + async getNotifications( + requestParameters: GetNotificationsRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const response = await this.getNotificationsRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + * Gets notifications count + * Get notifications count + */ + async getNotificationsCountRaw( + requestParameters: GetNotificationsCountRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + const queryParameters: any = {}; + + if (requestParameters.containsText !== undefined) { + queryParameters['containsText'] = requestParameters.containsText; + } + + if (requestParameters.createdAfter !== undefined) { + queryParameters['createdAfter'] = ( + requestParameters.createdAfter as any + ).toISOString(); + } + + if (requestParameters.messageScope !== undefined) { + queryParameters['messageScope'] = requestParameters.messageScope; + } + + if (requestParameters.user !== undefined) { + queryParameters['user'] = requestParameters.user; + } + + if (requestParameters.read !== undefined) { + queryParameters['read'] = requestParameters.read; + } + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request( + { + path: `/notifications/count`, + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, jsonValue => + GetNotificationsCount200ResponseFromJSON(jsonValue), + ); + } + + /** + * Gets notifications count + * Get notifications count + */ + async getNotificationsCount( + requestParameters: GetNotificationsCountRequest = {}, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.getNotificationsCountRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + * Set notification as read/unread + * Set notification as read/unread + */ + async setReadRaw( + requestParameters: SetReadRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + if ( + requestParameters.messageId === null || + requestParameters.messageId === undefined + ) { + throw new runtime.RequiredError( + 'messageId', + 'Required parameter requestParameters.messageId was null or undefined when calling setRead.', + ); + } + + if ( + requestParameters.user === null || + requestParameters.user === undefined + ) { + throw new runtime.RequiredError( + 'user', + 'Required parameter requestParameters.user was null or undefined when calling setRead.', + ); + } + + if ( + requestParameters.read === null || + requestParameters.read === undefined + ) { + throw new runtime.RequiredError( + 'read', + 'Required parameter requestParameters.read was null or undefined when calling setRead.', + ); + } + + const queryParameters: any = {}; + + if (requestParameters.messageId !== undefined) { + queryParameters['messageId'] = requestParameters.messageId; + } + + if (requestParameters.user !== undefined) { + queryParameters['user'] = requestParameters.user; + } + + if (requestParameters.read !== undefined) { + queryParameters['read'] = requestParameters.read; + } + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request( + { + path: `/notifications/read`, + method: 'PUT', + headers: headerParameters, + query: queryParameters, + }, + initOverrides, + ); + + return new runtime.VoidApiResponse(response); + } + + /** + * Set notification as read/unread + * Set notification as read/unread + */ + async setRead( + requestParameters: SetReadRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + await this.setReadRaw(requestParameters, initOverrides); + } +} + +/** + * @export + */ +export const GetNotificationsOrderByEnum = { + Title: 'title', + Message: 'message', + Created: 'created', + Topic: 'topic', + Origin: 'origin', +} as const; +export type GetNotificationsOrderByEnum = + (typeof GetNotificationsOrderByEnum)[keyof typeof GetNotificationsOrderByEnum]; +/** + * @export + */ +export const GetNotificationsOrderByDirecEnum = { + Asc: 'asc', + Desc: 'desc', +} as const; +export type GetNotificationsOrderByDirecEnum = + (typeof GetNotificationsOrderByDirecEnum)[keyof typeof GetNotificationsOrderByDirecEnum]; +/** + * @export + */ +export const GetNotificationsMessageScopeEnum = { + All: 'all', + User: 'user', + System: 'system', +} as const; +export type GetNotificationsMessageScopeEnum = + (typeof GetNotificationsMessageScopeEnum)[keyof typeof GetNotificationsMessageScopeEnum]; +/** + * @export + */ +export const GetNotificationsCountMessageScopeEnum = { + All: 'all', + User: 'user', + System: 'system', +} as const; +export type GetNotificationsCountMessageScopeEnum = + (typeof GetNotificationsCountMessageScopeEnum)[keyof typeof GetNotificationsCountMessageScopeEnum]; diff --git a/plugins/notifications-frontend/src/openapi/apis/index.ts b/plugins/notifications-frontend/src/openapi/apis/index.ts new file mode 100644 index 0000000000..d4f1e32459 --- /dev/null +++ b/plugins/notifications-frontend/src/openapi/apis/index.ts @@ -0,0 +1,3 @@ +/* tslint:disable */ +/* eslint-disable */ +export * from './NotificationsApi'; diff --git a/plugins/notifications-frontend/src/openapi/index.ts b/plugins/notifications-frontend/src/openapi/index.ts new file mode 100644 index 0000000000..bebe8bbbe2 --- /dev/null +++ b/plugins/notifications-frontend/src/openapi/index.ts @@ -0,0 +1,5 @@ +/* tslint:disable */ +/* eslint-disable */ +export * from './runtime'; +export * from './apis/index'; +export * from './models/index'; diff --git a/plugins/notifications-frontend/src/openapi/models/Action.ts b/plugins/notifications-frontend/src/openapi/models/Action.ts new file mode 100644 index 0000000000..283f7d5d8d --- /dev/null +++ b/plugins/notifications-frontend/src/openapi/models/Action.ts @@ -0,0 +1,85 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Notifications Plugin - OpenAPI Specs + * Notifications Plugin - OpenAPI Specs + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; + +/** + * + * @export + * @interface Action + */ +export interface Action { + /** + * + * @type {string} + * @memberof Action + */ + id: string; + /** + * + * @type {string} + * @memberof Action + */ + title: string; + /** + * + * @type {string} + * @memberof Action + */ + url: string; +} + +/** + * Check if a given object implements the Action interface. + */ +export function instanceOfAction(value: object): boolean { + let isInstance = true; + isInstance = isInstance && 'id' in value; + isInstance = isInstance && 'title' in value; + isInstance = isInstance && 'url' in value; + + return isInstance; +} + +export function ActionFromJSON(json: any): Action { + return ActionFromJSONTyped(json, false); +} + +export function ActionFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): Action { + if (json === undefined || json === null) { + return json; + } + return { + id: json['id'], + title: json['title'], + url: json['url'], + }; +} + +export function ActionToJSON(value?: Action | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + id: value.id, + title: value.title, + url: value.url, + }; +} diff --git a/plugins/notifications-frontend/src/openapi/models/CreateBody.ts b/plugins/notifications-frontend/src/openapi/models/CreateBody.ts new file mode 100644 index 0000000000..2fea3a2d53 --- /dev/null +++ b/plugins/notifications-frontend/src/openapi/models/CreateBody.ts @@ -0,0 +1,129 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Notifications Plugin - OpenAPI Specs + * Notifications Plugin - OpenAPI Specs + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +import type { CreateBodyActionsInner } from './CreateBodyActionsInner'; +import { + CreateBodyActionsInnerFromJSON, + CreateBodyActionsInnerFromJSONTyped, + CreateBodyActionsInnerToJSON, +} from './CreateBodyActionsInner'; + +/** + * + * @export + * @interface CreateBody + */ +export interface CreateBody { + /** + * + * @type {string} + * @memberof CreateBody + */ + origin: string; + /** + * + * @type {string} + * @memberof CreateBody + */ + title: string; + /** + * + * @type {string} + * @memberof CreateBody + */ + message?: string; + /** + * + * @type {Array} + * @memberof CreateBody + */ + actions?: Array; + /** + * + * @type {string} + * @memberof CreateBody + */ + topic?: string; + /** + * + * @type {Array} + * @memberof CreateBody + */ + targetUsers?: Array; + /** + * + * @type {Array} + * @memberof CreateBody + */ + targetGroups?: Array; +} + +/** + * Check if a given object implements the CreateBody interface. + */ +export function instanceOfCreateBody(value: object): boolean { + let isInstance = true; + isInstance = isInstance && 'origin' in value; + isInstance = isInstance && 'title' in value; + + return isInstance; +} + +export function CreateBodyFromJSON(json: any): CreateBody { + return CreateBodyFromJSONTyped(json, false); +} + +export function CreateBodyFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CreateBody { + if (json === undefined || json === null) { + return json; + } + return { + origin: json['origin'], + title: json['title'], + message: !exists(json, 'message') ? undefined : json['message'], + actions: !exists(json, 'actions') + ? undefined + : (json['actions'] as Array).map(CreateBodyActionsInnerFromJSON), + topic: !exists(json, 'topic') ? undefined : json['topic'], + targetUsers: !exists(json, 'targetUsers') ? undefined : json['targetUsers'], + targetGroups: !exists(json, 'targetGroups') + ? undefined + : json['targetGroups'], + }; +} + +export function CreateBodyToJSON(value?: CreateBody | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + origin: value.origin, + title: value.title, + message: value.message, + actions: + value.actions === undefined + ? undefined + : (value.actions as Array).map(CreateBodyActionsInnerToJSON), + topic: value.topic, + targetUsers: value.targetUsers, + targetGroups: value.targetGroups, + }; +} diff --git a/plugins/notifications-frontend/src/openapi/models/CreateBodyActionsInner.ts b/plugins/notifications-frontend/src/openapi/models/CreateBodyActionsInner.ts new file mode 100644 index 0000000000..964b85fbd7 --- /dev/null +++ b/plugins/notifications-frontend/src/openapi/models/CreateBodyActionsInner.ts @@ -0,0 +1,80 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Notifications Plugin - OpenAPI Specs + * Notifications Plugin - OpenAPI Specs + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; + +/** + * + * @export + * @interface CreateBodyActionsInner + */ +export interface CreateBodyActionsInner { + /** + * + * @type {string} + * @memberof CreateBodyActionsInner + */ + title: string; + /** + * + * @type {string} + * @memberof CreateBodyActionsInner + */ + url: string; +} + +/** + * Check if a given object implements the CreateBodyActionsInner interface. + */ +export function instanceOfCreateBodyActionsInner(value: object): boolean { + let isInstance = true; + isInstance = isInstance && 'title' in value; + isInstance = isInstance && 'url' in value; + + return isInstance; +} + +export function CreateBodyActionsInnerFromJSON( + json: any, +): CreateBodyActionsInner { + return CreateBodyActionsInnerFromJSONTyped(json, false); +} + +export function CreateBodyActionsInnerFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CreateBodyActionsInner { + if (json === undefined || json === null) { + return json; + } + return { + title: json['title'], + url: json['url'], + }; +} + +export function CreateBodyActionsInnerToJSON( + value?: CreateBodyActionsInner | null, +): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + title: value.title, + url: value.url, + }; +} diff --git a/plugins/notifications-frontend/src/openapi/models/CreateNotification200Response.ts b/plugins/notifications-frontend/src/openapi/models/CreateNotification200Response.ts new file mode 100644 index 0000000000..677e49af9a --- /dev/null +++ b/plugins/notifications-frontend/src/openapi/models/CreateNotification200Response.ts @@ -0,0 +1,73 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Notifications Plugin - OpenAPI Specs + * Notifications Plugin - OpenAPI Specs + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; + +/** + * + * @export + * @interface CreateNotification200Response + */ +export interface CreateNotification200Response { + /** + * + * @type {string} + * @memberof CreateNotification200Response + */ + messageId: string; +} + +/** + * Check if a given object implements the CreateNotification200Response interface. + */ +export function instanceOfCreateNotification200Response( + value: object, +): boolean { + let isInstance = true; + isInstance = isInstance && 'messageId' in value; + + return isInstance; +} + +export function CreateNotification200ResponseFromJSON( + json: any, +): CreateNotification200Response { + return CreateNotification200ResponseFromJSONTyped(json, false); +} + +export function CreateNotification200ResponseFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CreateNotification200Response { + if (json === undefined || json === null) { + return json; + } + return { + messageId: json['messageId'], + }; +} + +export function CreateNotification200ResponseToJSON( + value?: CreateNotification200Response | null, +): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + messageId: value.messageId, + }; +} diff --git a/plugins/notifications-frontend/src/openapi/models/GetNotificationsCount200Response.ts b/plugins/notifications-frontend/src/openapi/models/GetNotificationsCount200Response.ts new file mode 100644 index 0000000000..0eb49fa031 --- /dev/null +++ b/plugins/notifications-frontend/src/openapi/models/GetNotificationsCount200Response.ts @@ -0,0 +1,73 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Notifications Plugin - OpenAPI Specs + * Notifications Plugin - OpenAPI Specs + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; + +/** + * + * @export + * @interface GetNotificationsCount200Response + */ +export interface GetNotificationsCount200Response { + /** + * + * @type {number} + * @memberof GetNotificationsCount200Response + */ + count: number; +} + +/** + * Check if a given object implements the GetNotificationsCount200Response interface. + */ +export function instanceOfGetNotificationsCount200Response( + value: object, +): boolean { + let isInstance = true; + isInstance = isInstance && 'count' in value; + + return isInstance; +} + +export function GetNotificationsCount200ResponseFromJSON( + json: any, +): GetNotificationsCount200Response { + return GetNotificationsCount200ResponseFromJSONTyped(json, false); +} + +export function GetNotificationsCount200ResponseFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): GetNotificationsCount200Response { + if (json === undefined || json === null) { + return json; + } + return { + count: json['count'], + }; +} + +export function GetNotificationsCount200ResponseToJSON( + value?: GetNotificationsCount200Response | null, +): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + count: value.count, + }; +} diff --git a/plugins/notifications-frontend/src/openapi/models/Notification.ts b/plugins/notifications-frontend/src/openapi/models/Notification.ts new file mode 100644 index 0000000000..4fc8841775 --- /dev/null +++ b/plugins/notifications-frontend/src/openapi/models/Notification.ts @@ -0,0 +1,139 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Notifications Plugin - OpenAPI Specs + * Notifications Plugin - OpenAPI Specs + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +import type { Action } from './Action'; +import { ActionFromJSON, ActionFromJSONTyped, ActionToJSON } from './Action'; + +/** + * + * @export + * @interface Notification + */ +export interface Notification { + /** + * + * @type {string} + * @memberof Notification + */ + id: string; + /** + * + * @type {Date} + * @memberof Notification + */ + created: Date; + /** + * + * @type {boolean} + * @memberof Notification + */ + readByUser: boolean; + /** + * + * @type {boolean} + * @memberof Notification + */ + isSystem: boolean; + /** + * + * @type {string} + * @memberof Notification + */ + origin: string; + /** + * + * @type {string} + * @memberof Notification + */ + title: string; + /** + * + * @type {string} + * @memberof Notification + */ + message?: string; + /** + * + * @type {string} + * @memberof Notification + */ + topic?: string; + /** + * + * @type {Array} + * @memberof Notification + */ + actions: Array; +} + +/** + * Check if a given object implements the Notification interface. + */ +export function instanceOfNotification(value: object): boolean { + let isInstance = true; + isInstance = isInstance && 'id' in value; + isInstance = isInstance && 'created' in value; + isInstance = isInstance && 'readByUser' in value; + isInstance = isInstance && 'isSystem' in value; + isInstance = isInstance && 'origin' in value; + isInstance = isInstance && 'title' in value; + isInstance = isInstance && 'actions' in value; + + return isInstance; +} + +export function NotificationFromJSON(json: any): Notification { + return NotificationFromJSONTyped(json, false); +} + +export function NotificationFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): Notification { + if (json === undefined || json === null) { + return json; + } + return { + id: json['id'], + created: new Date(json['created']), + readByUser: json['readByUser'], + isSystem: json['isSystem'], + origin: json['origin'], + title: json['title'], + message: !exists(json, 'message') ? undefined : json['message'], + topic: !exists(json, 'topic') ? undefined : json['topic'], + actions: (json['actions'] as Array).map(ActionFromJSON), + }; +} + +export function NotificationToJSON(value?: Notification | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + id: value.id, + created: value.created.toISOString(), + readByUser: value.readByUser, + isSystem: value.isSystem, + origin: value.origin, + title: value.title, + message: value.message, + topic: value.topic, + actions: (value.actions as Array).map(ActionToJSON), + }; +} diff --git a/plugins/notifications-frontend/src/openapi/models/index.ts b/plugins/notifications-frontend/src/openapi/models/index.ts new file mode 100644 index 0000000000..5cd30ec8d5 --- /dev/null +++ b/plugins/notifications-frontend/src/openapi/models/index.ts @@ -0,0 +1,8 @@ +/* tslint:disable */ +/* eslint-disable */ +export * from './Action'; +export * from './CreateBody'; +export * from './CreateBodyActionsInner'; +export * from './CreateNotification200Response'; +export * from './GetNotificationsCount200Response'; +export * from './Notification'; diff --git a/plugins/notifications-frontend/src/openapi/runtime.ts b/plugins/notifications-frontend/src/openapi/runtime.ts new file mode 100644 index 0000000000..43a7fd8e00 --- /dev/null +++ b/plugins/notifications-frontend/src/openapi/runtime.ts @@ -0,0 +1,531 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Notifications Plugin - OpenAPI Specs + * Notifications Plugin - OpenAPI Specs + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +export const BASE_PATH = 'http://localhost:7007/api/notifications'.replace( + /\/+$/, + '', +); + +export interface ConfigurationParameters { + basePath?: string; // override base path + fetchApi?: FetchAPI; // override for fetch implementation + middleware?: Middleware[]; // middleware to apply before/after fetch requests + queryParamsStringify?: (params: HTTPQuery) => string; // stringify function for query strings + username?: string; // parameter for basic security + password?: string; // parameter for basic security + apiKey?: string | ((name: string) => string); // parameter for apiKey security + accessToken?: + | string + | Promise + | ((name?: string, scopes?: string[]) => string | Promise); // parameter for oauth2 security + headers?: HTTPHeaders; //header params we want to use on every request + credentials?: RequestCredentials; //value for the credentials param we want to use on each request +} + +export class Configuration { + constructor(private configuration: ConfigurationParameters = {}) {} + + set config(configuration: Configuration) { + this.configuration = configuration; + } + + get basePath(): string { + return this.configuration.basePath != null + ? this.configuration.basePath + : BASE_PATH; + } + + get fetchApi(): FetchAPI | undefined { + return this.configuration.fetchApi; + } + + get middleware(): Middleware[] { + return this.configuration.middleware || []; + } + + get queryParamsStringify(): (params: HTTPQuery) => string { + return this.configuration.queryParamsStringify || querystring; + } + + get username(): string | undefined { + return this.configuration.username; + } + + get password(): string | undefined { + return this.configuration.password; + } + + get apiKey(): ((name: string) => string) | undefined { + const apiKey = this.configuration.apiKey; + if (apiKey) { + return typeof apiKey === 'function' ? apiKey : () => apiKey; + } + return undefined; + } + + get accessToken(): + | ((name?: string, scopes?: string[]) => string | Promise) + | undefined { + const accessToken = this.configuration.accessToken; + if (accessToken) { + return typeof accessToken === 'function' + ? accessToken + : async () => accessToken; + } + return undefined; + } + + get headers(): HTTPHeaders | undefined { + return this.configuration.headers; + } + + get credentials(): RequestCredentials | undefined { + return this.configuration.credentials; + } +} + +export const DefaultConfig = new Configuration(); + +/** + * This is the base class for all generated API classes. + */ +export class BaseAPI { + private static readonly jsonRegex = new RegExp( + '^(:?application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(:?;.*)?$', + 'i', + ); + private middleware: Middleware[]; + + constructor(protected configuration = DefaultConfig) { + this.middleware = configuration.middleware; + } + + withMiddleware(this: T, ...middlewares: Middleware[]) { + const next = this.clone(); + next.middleware = next.middleware.concat(...middlewares); + return next; + } + + withPreMiddleware( + this: T, + ...preMiddlewares: Array + ) { + const middlewares = preMiddlewares.map(pre => ({ pre })); + return this.withMiddleware(...middlewares); + } + + withPostMiddleware( + this: T, + ...postMiddlewares: Array + ) { + const middlewares = postMiddlewares.map(post => ({ post })); + return this.withMiddleware(...middlewares); + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + protected isJsonMime(mime: string | null | undefined): boolean { + if (!mime) { + return false; + } + return BaseAPI.jsonRegex.test(mime); + } + + protected async request( + context: RequestOpts, + initOverrides?: RequestInit | InitOverrideFunction, + ): Promise { + const { url, init } = await this.createFetchParams(context, initOverrides); + const response = await this.fetchApi(url, init); + if (response && response.status >= 200 && response.status < 300) { + return response; + } + throw new ResponseError(response, 'Response returned an error code'); + } + + private async createFetchParams( + context: RequestOpts, + initOverrides?: RequestInit | InitOverrideFunction, + ) { + let url = this.configuration.basePath + context.path; + if ( + context.query !== undefined && + Object.keys(context.query).length !== 0 + ) { + // only add the querystring to the URL if there are query parameters. + // this is done to avoid urls ending with a "?" character which buggy webservers + // do not handle correctly sometimes. + url += '?' + this.configuration.queryParamsStringify(context.query); + } + + const headers = Object.assign( + {}, + this.configuration.headers, + context.headers, + ); + Object.keys(headers).forEach(key => + headers[key] === undefined ? delete headers[key] : {}, + ); + + const initOverrideFn = + typeof initOverrides === 'function' + ? initOverrides + : async () => initOverrides; + + const initParams = { + method: context.method, + headers, + body: context.body, + credentials: this.configuration.credentials, + }; + + const overriddenInit: RequestInit = { + ...initParams, + ...(await initOverrideFn({ + init: initParams, + context, + })), + }; + + let body: any; + if ( + isFormData(overriddenInit.body) || + overriddenInit.body instanceof URLSearchParams || + isBlob(overriddenInit.body) + ) { + body = overriddenInit.body; + } else if (this.isJsonMime(headers['Content-Type'])) { + body = JSON.stringify(overriddenInit.body); + } else { + body = overriddenInit.body; + } + + const init: RequestInit = { + ...overriddenInit, + body, + }; + + return { url, init }; + } + + private fetchApi = async (url: string, init: RequestInit) => { + let fetchParams = { url, init }; + for (const middleware of this.middleware) { + if (middleware.pre) { + fetchParams = + (await middleware.pre({ + fetch: this.fetchApi, + ...fetchParams, + })) || fetchParams; + } + } + let response: Response | undefined = undefined; + try { + response = await (this.configuration.fetchApi || fetch)( + fetchParams.url, + fetchParams.init, + ); + } catch (e) { + for (const middleware of this.middleware) { + if (middleware.onError) { + response = + (await middleware.onError({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + error: e, + response: response ? response.clone() : undefined, + })) || response; + } + } + if (response === undefined) { + if (e instanceof Error) { + throw new FetchError( + e, + 'The request failed and the interceptors did not return an alternative response', + ); + } else { + throw e; + } + } + } + for (const middleware of this.middleware) { + if (middleware.post) { + response = + (await middleware.post({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + response: response.clone(), + })) || response; + } + } + return response; + }; + + /** + * Create a shallow clone of `this` by constructing a new instance + * and then shallow cloning data members. + */ + private clone(this: T): T { + const constructor = this.constructor as any; + const next = new constructor(this.configuration); + next.middleware = this.middleware.slice(); + return next; + } +} + +function isBlob(value: any): value is Blob { + return typeof Blob !== 'undefined' && value instanceof Blob; +} + +function isFormData(value: any): value is FormData { + return typeof FormData !== 'undefined' && value instanceof FormData; +} + +export class ResponseError extends Error { + override name: 'ResponseError' = 'ResponseError'; + constructor( + public response: Response, + msg?: string, + ) { + super(msg); + } +} + +export class FetchError extends Error { + override name: 'FetchError' = 'FetchError'; + constructor( + public cause: Error, + msg?: string, + ) { + super(msg); + } +} + +export class RequiredError extends Error { + override name: 'RequiredError' = 'RequiredError'; + constructor( + public field: string, + msg?: string, + ) { + super(msg); + } +} + +export const COLLECTION_FORMATS = { + csv: ',', + ssv: ' ', + tsv: '\t', + pipes: '|', +}; + +export type FetchAPI = WindowOrWorkerGlobalScope['fetch']; + +export type Json = any; +export type HTTPMethod = + | 'GET' + | 'POST' + | 'PUT' + | 'PATCH' + | 'DELETE' + | 'OPTIONS' + | 'HEAD'; +export type HTTPHeaders = { [key: string]: string }; +export type HTTPQuery = { + [key: string]: + | string + | number + | null + | boolean + | Array + | Set + | HTTPQuery; +}; +export type HTTPBody = Json | FormData | URLSearchParams; +export type HTTPRequestInit = { + headers?: HTTPHeaders; + method: HTTPMethod; + credentials?: RequestCredentials; + body?: HTTPBody; +}; +export type ModelPropertyNaming = + | 'camelCase' + | 'snake_case' + | 'PascalCase' + | 'original'; + +export type InitOverrideFunction = (requestContext: { + init: HTTPRequestInit; + context: RequestOpts; +}) => Promise; + +export interface FetchParams { + url: string; + init: RequestInit; +} + +export interface RequestOpts { + path: string; + method: HTTPMethod; + headers: HTTPHeaders; + query?: HTTPQuery; + body?: HTTPBody; +} + +export function exists(json: any, key: string) { + const value = json[key]; + return value !== null && value !== undefined; +} + +export function querystring(params: HTTPQuery, prefix: string = ''): string { + return Object.keys(params) + .map(key => querystringSingleKey(key, params[key], prefix)) + .filter(part => part.length > 0) + .join('&'); +} + +function querystringSingleKey( + key: string, + value: + | string + | number + | null + | undefined + | boolean + | Array + | Set + | HTTPQuery, + keyPrefix: string = '', +): string { + const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key); + if (value instanceof Array) { + const multiValue = value + .map(singleValue => encodeURIComponent(String(singleValue))) + .join(`&${encodeURIComponent(fullKey)}=`); + return `${encodeURIComponent(fullKey)}=${multiValue}`; + } + if (value instanceof Set) { + const valueAsArray = Array.from(value); + return querystringSingleKey(key, valueAsArray, keyPrefix); + } + if (value instanceof Date) { + return `${encodeURIComponent(fullKey)}=${encodeURIComponent( + value.toISOString(), + )}`; + } + if (value instanceof Object) { + return querystring(value as HTTPQuery, fullKey); + } + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`; +} + +export function mapValues(data: any, fn: (item: any) => any) { + return Object.keys(data).reduce( + (acc, key) => ({ ...acc, [key]: fn(data[key]) }), + {}, + ); +} + +export function canConsumeForm(consumes: Consume[]): boolean { + for (const consume of consumes) { + if ('multipart/form-data' === consume.contentType) { + return true; + } + } + return false; +} + +export interface Consume { + contentType: string; +} + +export interface RequestContext { + fetch: FetchAPI; + url: string; + init: RequestInit; +} + +export interface ResponseContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + response: Response; +} + +export interface ErrorContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + error: unknown; + response?: Response; +} + +export interface Middleware { + pre?(context: RequestContext): Promise; + post?(context: ResponseContext): Promise; + onError?(context: ErrorContext): Promise; +} + +export interface ApiResponse { + raw: Response; + value(): Promise; +} + +export interface ResponseTransformer { + (json: any): T; +} + +export class JSONApiResponse { + constructor( + public raw: Response, + private transformer: ResponseTransformer = (jsonValue: any) => jsonValue, + ) {} + + async value(): Promise { + return this.transformer(await this.raw.json()); + } +} + +export class VoidApiResponse { + constructor(public raw: Response) {} + + async value(): Promise { + return undefined; + } +} + +export class BlobApiResponse { + constructor(public raw: Response) {} + + async value(): Promise { + return await this.raw.blob(); + } +} + +export class TextApiResponse { + constructor(public raw: Response) {} + + async value(): Promise { + return await this.raw.text(); + } +} From a503afa5eb45a1e70dd241922d3aa658e7561eb2 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Mon, 27 Nov 2023 14:06:32 +0100 Subject: [PATCH 3/6] Remove notifications-common from FE dependencies We do not need it anymore since we have the generated types from OpenApi. --- plugins/notifications-frontend/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/notifications-frontend/package.json b/plugins/notifications-frontend/package.json index c2f066b75c..fd46eb9edf 100644 --- a/plugins/notifications-frontend/package.json +++ b/plugins/notifications-frontend/package.json @@ -28,7 +28,6 @@ "dependencies": { "@backstage/core-components": "^0.13.6", "@backstage/core-plugin-api": "^1.7.0", - "@backstage/plugin-notifications-common": "0.1.0", "@backstage/theme": "^0.4.3", "@material-table/core": "^3.1.0", "@material-ui/core": "^4.9.13", From 2697b6e68531eaf3372239c67fa1aeff07fae0b9 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Mon, 27 Nov 2023 11:58:30 +0100 Subject: [PATCH 4/6] Update frontend to use generated openapi calls --- .../src/api/NotificationsApiImpl.ts | 128 ++++-------------- .../src/api/notificationsApi.ts | 42 +++--- .../components/NotificationsSidebarItem.tsx | 10 +- .../NotificationsTable/NotificationsTable.tsx | 40 ++++-- .../SendNotification/SendNotification.tsx | 9 +- plugins/notifications-frontend/src/plugin.ts | 6 +- 6 files changed, 79 insertions(+), 156 deletions(-) diff --git a/plugins/notifications-frontend/src/api/NotificationsApiImpl.ts b/plugins/notifications-frontend/src/api/NotificationsApiImpl.ts index b84f2e000f..b7108609a5 100644 --- a/plugins/notifications-frontend/src/api/NotificationsApiImpl.ts +++ b/plugins/notifications-frontend/src/api/NotificationsApiImpl.ts @@ -1,29 +1,31 @@ -import { ConfigApi, IdentityApi } from '@backstage/core-plugin-api'; +import { IdentityApi } from '@backstage/core-plugin-api'; + import { - CreateNotificationRequest, + CreateBody, + DefaultConfig, + GetNotificationsRequest, Notification, -} from '@backstage/plugin-notifications-common'; - + NotificationsApi as NotificationsOpenApi, +} from '../openapi'; import { NotificationMarkAsRead, NotificationsApi, NotificationsCountQuery, - NotificationsFilter, - NotificationsQuery, } from './notificationsApi'; export type NotificationsApiOptions = { - configApi: ConfigApi; identityApi: IdentityApi; }; export class NotificationsApiImpl implements NotificationsApi { - private readonly backendUrl: string; private readonly identityApi: IdentityApi; + private readonly backendRestApi: NotificationsOpenApi; constructor(options: NotificationsApiOptions) { - this.backendUrl = options.configApi.getString('backend.baseUrl'); this.identityApi = options.identityApi; + + const configuration = DefaultConfig; + this.backendRestApi = new NotificationsOpenApi(configuration); } private async getLogedInUsername(): Promise { @@ -34,110 +36,32 @@ export class NotificationsApiImpl implements NotificationsApi { return userEntityRef.slice('start:'.length - 1); } - private addFilter(url: URL, user: string, filter: NotificationsFilter) { - url.searchParams.append('user', user); - - if (filter.containsText) { - url.searchParams.append('containsText', filter.containsText); - } - if (filter.createdAfter) { - url.searchParams.append( - 'createdAfter', - filter.createdAfter.toISOString(), - ); - } - if (filter.messageScope) { - url.searchParams.append('messageScope', filter.messageScope); - } - if (filter.isRead !== undefined) { - url.searchParams.append('read', filter.isRead ? 'true' : 'false'); - } - } - - async post(notification: CreateNotificationRequest): Promise { - const url = new URL(`${this.backendUrl}/api/notifications/notifications`); - - const response = await fetch(url.href, { - method: 'POST', - body: JSON.stringify(notification), - headers: { 'Content-Type': 'application/json' }, + async createNotification(notification: CreateBody): Promise { + const data = await this.backendRestApi.createNotification({ + createBody: notification, }); - const data = await response.json(); - if (response.status !== 200 && response.status !== 201) { - throw new Error(data.message || data.error?.message); - } - - return Promise.resolve(data.messageId); + return data.messageId; } - async getNotifications(query: NotificationsQuery): Promise { - const url = new URL(`${this.backendUrl}/api/notifications/notifications`); + async getNotifications( + query: GetNotificationsRequest, + ): Promise { const user = await this.getLogedInUsername(); - - url.searchParams.append('pageSize', `${query.pageSize}`); - url.searchParams.append('pageNumber', `${query.pageNumber}`); - - if (query.sorting) { - url.searchParams.append('orderBy', `${query.sorting.fieldName}`); - url.searchParams.append('orderByDirec', `${query.sorting.direction}`); - } - - this.addFilter(url, user, query); - - const response = await fetch(url.href); - const data = await response.json(); - if (response.status !== 200 && response.status !== 201) { - throw new Error(data.message); - } - - if (!Array.isArray(data)) { - throw new Error('Unexpected format of notifications received'); - } - - return data; + return this.backendRestApi.getNotifications({ ...query, user }); } async getNotificationsCount(query: NotificationsCountQuery): Promise { - const url = new URL( - `${this.backendUrl}/api/notifications/notifications/count`, - ); const user = await this.getLogedInUsername(); - - this.addFilter(url, user, query); - - const response = await fetch(url.href); - const data = await response.json(); - if (response.status !== 200 && response.status !== 201) { - throw new Error(data.message); - } - - const count = parseInt(data.count, 10); - if (Number.isNaN(count)) { - throw new Error('Unexpected format of notifications count received'); - } - - return count; + const data = await this.backendRestApi.getNotificationsCount({ + ...query, + user, + }); + return data.count; } - async markAsRead({ - notificationId, - isRead, - }: NotificationMarkAsRead): Promise { - const url = new URL( - `${this.backendUrl}/api/notifications/notifications/read`, - ); + async markAsRead(params: NotificationMarkAsRead): Promise { const user = await this.getLogedInUsername(); - url.searchParams.append('read', isRead ? 'true' : 'false'); - url.searchParams.append('user', user); - url.searchParams.append('messageId', notificationId); - - const response = await fetch(url.href, { - method: 'PUT', - }); - - if (response.status !== 200 && response.status !== 201) { - throw new Error('Failed to mark the message as read'); - } + return this.backendRestApi.setRead({ ...params, user }); } } diff --git a/plugins/notifications-frontend/src/api/notificationsApi.ts b/plugins/notifications-frontend/src/api/notificationsApi.ts index c2f9c5be8d..3d4e5c2ba6 100644 --- a/plugins/notifications-frontend/src/api/notificationsApi.ts +++ b/plugins/notifications-frontend/src/api/notificationsApi.ts @@ -1,32 +1,26 @@ import { createApiRef } from '@backstage/core-plugin-api'; + import { - CreateNotificationRequest, + CreateBody, + GetNotificationsCountRequest, + GetNotificationsRequest, Notification, - NotificationsQuerySorting, -} from '@backstage/plugin-notifications-common'; - -export type NotificationsFilter = { - containsText?: string; - createdAfter?: Date; - messageScope?: 'all' | 'user' | 'system'; - isRead?: boolean; // if undefined, include both read and unread -}; - -export type NotificationsQuery = NotificationsFilter & { - pageSize: number; - pageNumber: number; - - sorting?: NotificationsQuerySorting; -}; -export type NotificationsCountQuery = NotificationsFilter; - -export type NotificationMarkAsRead = { - notificationId: string; - isRead: boolean; -}; + SetReadRequest, +} from '../openapi'; + +export type NotificationsCreateRequest = CreateBody; + +export type NotificationsQuery = Omit; + +export type NotificationsCountQuery = Omit< + GetNotificationsCountRequest, + 'user' +>; + +export type NotificationMarkAsRead = Omit; export interface NotificationsApi { /** Create a notification. Returns its new ID. */ - post(notification: CreateNotificationRequest): Promise; + createNotification(notification: NotificationsCreateRequest): Promise; /** Read a list of notifications based on filter parameters. */ getNotifications(query?: NotificationsQuery): Promise; diff --git a/plugins/notifications-frontend/src/components/NotificationsSidebarItem.tsx b/plugins/notifications-frontend/src/components/NotificationsSidebarItem.tsx index 5931aa2f04..1c0fc5d38b 100644 --- a/plugins/notifications-frontend/src/components/NotificationsSidebarItem.tsx +++ b/plugins/notifications-frontend/src/components/NotificationsSidebarItem.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { SidebarItem } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; -import { Notification } from '@backstage/plugin-notifications-common'; import { IconButton, @@ -17,6 +16,7 @@ import NotificationsOffIcon from '@material-ui/icons/NotificationsOff'; import { notificationsApiRef } from '../api'; import { NOTIFICATIONS_ROUTE } from '../constants'; +import { Notification } from '../openapi'; import { usePollingEffect } from './usePollingEffect'; const NotificationsErrorIcon = () => ( @@ -58,7 +58,7 @@ export const NotificationsSidebarItem = ({ try { setUnreadCount( await notificationsApi.getNotificationsCount({ - isRead: false, + read: false, messageScope: 'user', }), ); @@ -67,10 +67,8 @@ export const NotificationsSidebarItem = ({ pageSize: 1, pageNumber: 1, createdAfter: pageLoadingTime, - sorting: { - fieldName: 'created', - direction: 'desc', - }, + orderBy: 'created', + orderByDirec: 'desc', messageScope: 'system', }); diff --git a/plugins/notifications-frontend/src/components/NotificationsTable/NotificationsTable.tsx b/plugins/notifications-frontend/src/components/NotificationsTable/NotificationsTable.tsx index f38c9fd488..58d7fe674c 100644 --- a/plugins/notifications-frontend/src/components/NotificationsTable/NotificationsTable.tsx +++ b/plugins/notifications-frontend/src/components/NotificationsTable/NotificationsTable.tsx @@ -8,10 +8,6 @@ import { TableColumn, } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; -import { - Notification, - NotificationsQuerySorting, -} from '@backstage/plugin-notifications-common'; import { MaterialTableProps } from '@material-table/core'; import { Grid, IconButton, Tooltip } from '@material-ui/core'; @@ -19,8 +15,14 @@ import { makeStyles } from '@material-ui/core/styles'; import MarkAsReadIcon from '@material-ui/icons/CheckCircle'; import debounce from 'lodash/debounce'; -import { notificationsApiRef, NotificationsFilter } from '../../api'; +import { notificationsApiRef, NotificationsQuery } from '../../api'; import { DebounceDelayMs } from '../../constants'; +import { + GetNotificationsCountMessageScopeEnum, + GetNotificationsOrderByDirecEnum, + GetNotificationsOrderByEnum, + Notification, +} from '../../openapi'; import MarkAsUnreadIcon from './MarkAsUnreadIcon'; import { CreatedAfterOptions, @@ -38,7 +40,7 @@ const useStyles = makeStyles({ }); export type NotificationsTableProps = { - messageScope: NonNullable; + messageScope: GetNotificationsCountMessageScopeEnum; }; export const NotificationsTable = ({ @@ -52,7 +54,11 @@ export const NotificationsTable = ({ const [createdAfter, setCreatedAfter] = React.useState('lastWeek'); const [unreadOnly, setUnreadOnly] = React.useState(true); const [sorting, setSorting] = React.useState< - NotificationsQuerySorting | undefined + | { + orderBy: GetNotificationsOrderByEnum; + orderByDirec: GetNotificationsOrderByDirecEnum; + } + | undefined >(); const [reload, setReload] = React.useState(0); @@ -60,8 +66,8 @@ export const NotificationsTable = ({ (notification: Notification) => { notificationsApi .markAsRead({ - notificationId: notification.id, - isRead: !notification.readByUser, + messageId: notification.id, + read: !notification.readByUser, }) .then(() => setReload(Date.now())); }, @@ -79,22 +85,26 @@ export const NotificationsTable = ({ }> => { const createdAfterDate = CreatedAfterOptions[createdAfter].getDate(); - const commonParams: NotificationsFilter = { + const commonParams: Pick< + NotificationsQuery, + 'containsText' | 'createdAfter' | 'messageScope' | 'read' + > = { containsText, createdAfter: createdAfterDate, messageScope, }; if (unreadOnly !== undefined) { - commonParams.isRead = !unreadOnly; + commonParams.read = !unreadOnly; } const data = await notificationsApi.getNotifications({ ...commonParams, + ...sorting, pageSize, pageNumber: pageNumber + 1 /* BE starts at 1 */, - sorting, }); + // TODO: extend BE to get both in a single query/response const total = await notificationsApi.getNotificationsCount({ ...commonParams, @@ -169,13 +179,13 @@ export const NotificationsTable = ({ const onOrderChange = React.useCallback< NonNullable['onOrderChange']> - >((orderBy, direction) => { + >((orderBy, orderByDirec) => { if (orderBy < 0) { setSorting(undefined); return; } - const fieldNames: NotificationsQuerySorting['fieldName'][] = [ + const fieldNames: GetNotificationsOrderByEnum[] = [ /* Keep the order in sync with the column definitions bellow */ 'title', 'message', @@ -185,7 +195,7 @@ export const NotificationsTable = ({ ]; const fieldName = fieldNames[orderBy]; - setSorting({ fieldName, direction }); + setSorting({ orderBy: fieldName, orderByDirec }); }, []); const columns = React.useMemo( diff --git a/plugins/notifications-frontend/src/components/SendNotification/SendNotification.tsx b/plugins/notifications-frontend/src/components/SendNotification/SendNotification.tsx index c5f3c4e4c1..a851c79c30 100644 --- a/plugins/notifications-frontend/src/components/SendNotification/SendNotification.tsx +++ b/plugins/notifications-frontend/src/components/SendNotification/SendNotification.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { configApiRef, useApi } from '@backstage/core-plugin-api'; -import { CreateNotificationRequest } from '@backstage/plugin-notifications-common'; import { makeStyles } from '@material-ui/core'; import Alert from '@mui/material/Alert'; @@ -10,7 +9,7 @@ import Stack from '@mui/material/Stack'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; -import { notificationsApiRef } from '../../api'; +import { notificationsApiRef, NotificationsCreateRequest } from '../../api'; const useStyles = makeStyles({ container: { @@ -39,7 +38,7 @@ export const SendNotification = () => { try { const parsedActions = actions ? JSON.parse(actions) : undefined; - const notification: CreateNotificationRequest = { + const notification: NotificationsCreateRequest = { origin, title, message, @@ -49,7 +48,7 @@ export const SendNotification = () => { targetGroups, }; - const id = await notificationsApi.post(notification); + const id = await notificationsApi.createNotification(notification); setNotificationId(id); } catch (_e) { const e = _e as Error; @@ -58,7 +57,7 @@ export const SendNotification = () => { }; const getCurl = () => { - const data: CreateNotificationRequest = { + const data: NotificationsCreateRequest = { title, origin, }; diff --git a/plugins/notifications-frontend/src/plugin.ts b/plugins/notifications-frontend/src/plugin.ts index 22bbaedc66..53ac86c775 100644 --- a/plugins/notifications-frontend/src/plugin.ts +++ b/plugins/notifications-frontend/src/plugin.ts @@ -1,5 +1,4 @@ import { - configApiRef, createApiFactory, createPlugin, createRoutableExtension, @@ -17,10 +16,9 @@ export const notificationsPlugin = createPlugin({ apis: [ createApiFactory({ api: notificationsApiRef, - deps: { configApi: configApiRef, identityApi: identityApiRef }, - factory({ configApi, identityApi }) { + deps: { identityApi: identityApiRef }, + factory({ identityApi }) { return new NotificationsApiImpl({ - configApi, identityApi, }); }, From 953a18f8c79382787b49c06c6f15a3fda265a670 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Mon, 27 Nov 2023 15:17:58 +0100 Subject: [PATCH 5/6] Add ts-nocheck to generated OpenApi code --- plugins/notifications-frontend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/notifications-frontend/package.json b/plugins/notifications-frontend/package.json index fd46eb9edf..befd2c26e9 100644 --- a/plugins/notifications-frontend/package.json +++ b/plugins/notifications-frontend/package.json @@ -23,7 +23,7 @@ "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", "tsc": "tsc", - "openapi:generate": "openapi-generator-cli generate -i ../notifications-backend/src/openapi.yaml -g typescript-fetch -o ./src/openapi" + "openapi:generate": "openapi-generator-cli generate -i ../notifications-backend/src/openapi.yaml --enable-post-process-file -g typescript-fetch -o ./src/openapi && find ./src/openapi -name '*.ts' -exec sed -i '1i // @ts-nocheck' {} \\;" }, "dependencies": { "@backstage/core-components": "^0.13.6", From 3a0959ea210eb1302bdd8996c4b9fc563164351e Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Mon, 27 Nov 2023 15:18:36 +0100 Subject: [PATCH 6/6] Regenerate openapi --- .../notifications-frontend/src/openapi/apis/NotificationsApi.ts | 1 + plugins/notifications-frontend/src/openapi/apis/index.ts | 1 + plugins/notifications-frontend/src/openapi/index.ts | 1 + plugins/notifications-frontend/src/openapi/models/Action.ts | 1 + plugins/notifications-frontend/src/openapi/models/CreateBody.ts | 1 + .../src/openapi/models/CreateBodyActionsInner.ts | 1 + .../src/openapi/models/CreateNotification200Response.ts | 1 + .../src/openapi/models/GetNotificationsCount200Response.ts | 1 + .../notifications-frontend/src/openapi/models/Notification.ts | 1 + plugins/notifications-frontend/src/openapi/models/index.ts | 1 + plugins/notifications-frontend/src/openapi/runtime.ts | 1 + 11 files changed, 11 insertions(+) diff --git a/plugins/notifications-frontend/src/openapi/apis/NotificationsApi.ts b/plugins/notifications-frontend/src/openapi/apis/NotificationsApi.ts index 05eca75f75..d894d07ac8 100644 --- a/plugins/notifications-frontend/src/openapi/apis/NotificationsApi.ts +++ b/plugins/notifications-frontend/src/openapi/apis/NotificationsApi.ts @@ -1,3 +1,4 @@ +// @ts-nocheck /* tslint:disable */ /* eslint-disable */ /** diff --git a/plugins/notifications-frontend/src/openapi/apis/index.ts b/plugins/notifications-frontend/src/openapi/apis/index.ts index d4f1e32459..d201cd0f61 100644 --- a/plugins/notifications-frontend/src/openapi/apis/index.ts +++ b/plugins/notifications-frontend/src/openapi/apis/index.ts @@ -1,3 +1,4 @@ +// @ts-nocheck /* tslint:disable */ /* eslint-disable */ export * from './NotificationsApi'; diff --git a/plugins/notifications-frontend/src/openapi/index.ts b/plugins/notifications-frontend/src/openapi/index.ts index bebe8bbbe2..af50c5b678 100644 --- a/plugins/notifications-frontend/src/openapi/index.ts +++ b/plugins/notifications-frontend/src/openapi/index.ts @@ -1,3 +1,4 @@ +// @ts-nocheck /* tslint:disable */ /* eslint-disable */ export * from './runtime'; diff --git a/plugins/notifications-frontend/src/openapi/models/Action.ts b/plugins/notifications-frontend/src/openapi/models/Action.ts index 283f7d5d8d..1b391706d3 100644 --- a/plugins/notifications-frontend/src/openapi/models/Action.ts +++ b/plugins/notifications-frontend/src/openapi/models/Action.ts @@ -1,3 +1,4 @@ +// @ts-nocheck /* tslint:disable */ /* eslint-disable */ /** diff --git a/plugins/notifications-frontend/src/openapi/models/CreateBody.ts b/plugins/notifications-frontend/src/openapi/models/CreateBody.ts index 2fea3a2d53..803479986c 100644 --- a/plugins/notifications-frontend/src/openapi/models/CreateBody.ts +++ b/plugins/notifications-frontend/src/openapi/models/CreateBody.ts @@ -1,3 +1,4 @@ +// @ts-nocheck /* tslint:disable */ /* eslint-disable */ /** diff --git a/plugins/notifications-frontend/src/openapi/models/CreateBodyActionsInner.ts b/plugins/notifications-frontend/src/openapi/models/CreateBodyActionsInner.ts index 964b85fbd7..4bf3a3da3e 100644 --- a/plugins/notifications-frontend/src/openapi/models/CreateBodyActionsInner.ts +++ b/plugins/notifications-frontend/src/openapi/models/CreateBodyActionsInner.ts @@ -1,3 +1,4 @@ +// @ts-nocheck /* tslint:disable */ /* eslint-disable */ /** diff --git a/plugins/notifications-frontend/src/openapi/models/CreateNotification200Response.ts b/plugins/notifications-frontend/src/openapi/models/CreateNotification200Response.ts index 677e49af9a..856942f98f 100644 --- a/plugins/notifications-frontend/src/openapi/models/CreateNotification200Response.ts +++ b/plugins/notifications-frontend/src/openapi/models/CreateNotification200Response.ts @@ -1,3 +1,4 @@ +// @ts-nocheck /* tslint:disable */ /* eslint-disable */ /** diff --git a/plugins/notifications-frontend/src/openapi/models/GetNotificationsCount200Response.ts b/plugins/notifications-frontend/src/openapi/models/GetNotificationsCount200Response.ts index 0eb49fa031..464bd8f627 100644 --- a/plugins/notifications-frontend/src/openapi/models/GetNotificationsCount200Response.ts +++ b/plugins/notifications-frontend/src/openapi/models/GetNotificationsCount200Response.ts @@ -1,3 +1,4 @@ +// @ts-nocheck /* tslint:disable */ /* eslint-disable */ /** diff --git a/plugins/notifications-frontend/src/openapi/models/Notification.ts b/plugins/notifications-frontend/src/openapi/models/Notification.ts index 4fc8841775..b3cf5e5c05 100644 --- a/plugins/notifications-frontend/src/openapi/models/Notification.ts +++ b/plugins/notifications-frontend/src/openapi/models/Notification.ts @@ -1,3 +1,4 @@ +// @ts-nocheck /* tslint:disable */ /* eslint-disable */ /** diff --git a/plugins/notifications-frontend/src/openapi/models/index.ts b/plugins/notifications-frontend/src/openapi/models/index.ts index 5cd30ec8d5..5f553c5119 100644 --- a/plugins/notifications-frontend/src/openapi/models/index.ts +++ b/plugins/notifications-frontend/src/openapi/models/index.ts @@ -1,3 +1,4 @@ +// @ts-nocheck /* tslint:disable */ /* eslint-disable */ export * from './Action'; diff --git a/plugins/notifications-frontend/src/openapi/runtime.ts b/plugins/notifications-frontend/src/openapi/runtime.ts index 43a7fd8e00..90ca48f287 100644 --- a/plugins/notifications-frontend/src/openapi/runtime.ts +++ b/plugins/notifications-frontend/src/openapi/runtime.ts @@ -1,3 +1,4 @@ +// @ts-nocheck /* tslint:disable */ /* eslint-disable */ /**