From f94f6716fd7754e1c8104e21ad63188da515c441 Mon Sep 17 00:00:00 2001 From: Tobias Messner Date: Wed, 9 Oct 2024 17:15:39 +0200 Subject: [PATCH] feat: Add indicator to warn when most t4c licenses are being used Closes #1784 --- .../modelsources/t4c/license_server/models.py | 6 ++ .../modelsources/t4c/license_server/routes.py | 24 ++++- .../settings/modelsources/t4c/routes.py | 5 +- .../src/app/openapi/.openapi-generator/FILES | 1 + ...delsources-t4-c-license-servers.service.ts | 101 +++++++++++++++--- frontend/src/app/openapi/model/models.ts | 1 + .../model/public-license-server-with-usage.ts | 20 ++++ .../license-indicator.component.html | 35 ++++++ .../license-indicator.component.ts | 18 ++++ .../license-indicator.stories.ts | 101 ++++++++++++++++++ .../license-usage.service.ts | 33 ++++++ .../create-persistent-session.component.html | 2 + .../create-persistent-session.component.ts | 12 ++- .../create-persistent-session.stories.ts | 72 +++++++++++++ 14 files changed, 407 insertions(+), 24 deletions(-) create mode 100644 frontend/src/app/openapi/model/public-license-server-with-usage.ts create mode 100644 frontend/src/app/sessions/license-indicator/license-indicator.component.html create mode 100644 frontend/src/app/sessions/license-indicator/license-indicator.component.ts create mode 100644 frontend/src/app/sessions/license-indicator/license-indicator.stories.ts create mode 100644 frontend/src/app/sessions/license-indicator/license-usage.service.ts create mode 100644 frontend/src/app/sessions/user-sessions-wrapper/create-sessions/create-persistent-session/create-persistent-session.stories.ts diff --git a/backend/capellacollab/settings/modelsources/t4c/license_server/models.py b/backend/capellacollab/settings/modelsources/t4c/license_server/models.py index 96b6ac73a1..11b5a080a3 100644 --- a/backend/capellacollab/settings/modelsources/t4c/license_server/models.py +++ b/backend/capellacollab/settings/modelsources/t4c/license_server/models.py @@ -59,6 +59,12 @@ class SimpleLicenseServer(T4CLicenseServerBase): id: int +class PublicLicenseServerWithUsage(core_pydantic.BaseModel): + id: int + name: str + usage: interface.T4CLicenseServerUsage + + class T4CLicenseServer(T4CLicenseServerBase): id: int license_server_version: str | None = None diff --git a/backend/capellacollab/settings/modelsources/t4c/license_server/routes.py b/backend/capellacollab/settings/modelsources/t4c/license_server/routes.py index 8a07dc837e..467cf678c1 100644 --- a/backend/capellacollab/settings/modelsources/t4c/license_server/routes.py +++ b/backend/capellacollab/settings/modelsources/t4c/license_server/routes.py @@ -95,11 +95,33 @@ def delete_t4c_license_server( crud.delete_t4c_license_server(db, license_server) +@router.get( + "/usage", + response_model=list[models.PublicLicenseServerWithUsage], +) +def get_t4c_license_servers_usage( + db: orm.Session = fastapi.Depends(database.get_db), +) -> list[models.PublicLicenseServerWithUsage]: + usages = [] + for license_server in crud.get_t4c_license_servers(db): + usage = interface.get_t4c_license_server_usage( + license_server.usage_api + ) + usages.append( + models.PublicLicenseServerWithUsage( + id=license_server.id, + name=license_server.name, + usage=usage, + ) + ) + return usages + + @router.get( "/{t4c_license_server_id}/usage", response_model=interface.T4CLicenseServerUsage, ) -def fetch_t4c_license_server_licenses( +def get_t4c_license_server_usage( license_server: models.DatabaseT4CLicenseServer = fastapi.Depends( injectables.get_existing_license_server ), diff --git a/backend/capellacollab/settings/modelsources/t4c/routes.py b/backend/capellacollab/settings/modelsources/t4c/routes.py index 93e82797ef..20bb36cd86 100644 --- a/backend/capellacollab/settings/modelsources/t4c/routes.py +++ b/backend/capellacollab/settings/modelsources/t4c/routes.py @@ -19,13 +19,14 @@ ) router.include_router( - settings_t4c_license_server_routes.admin_router, + settings_t4c_license_server_routes.router, prefix="/license-servers", tags=["Settings - Modelsources - T4C - License Servers"], ) + router.include_router( - settings_t4c_license_server_routes.router, + settings_t4c_license_server_routes.admin_router, prefix="/license-servers", tags=["Settings - Modelsources - T4C - License Servers"], ) diff --git a/frontend/src/app/openapi/.openapi-generator/FILES b/frontend/src/app/openapi/.openapi-generator/FILES index 3001204963..afb1fd1365 100644 --- a/frontend/src/app/openapi/.openapi-generator/FILES +++ b/frontend/src/app/openapi/.openapi-generator/FILES @@ -131,6 +131,7 @@ model/project.ts model/prometheus-configuration-input.ts model/prometheus-configuration-output.ts model/protocol.ts +model/public-license-server-with-usage.ts model/pure-variants-licenses-input.ts model/pure-variants-licenses-output.ts model/put-git-model.ts diff --git a/frontend/src/app/openapi/api/settings-modelsources-t4-c-license-servers.service.ts b/frontend/src/app/openapi/api/settings-modelsources-t4-c-license-servers.service.ts index f5d472cb3c..bb5b7779fe 100644 --- a/frontend/src/app/openapi/api/settings-modelsources-t4-c-license-servers.service.ts +++ b/frontend/src/app/openapi/api/settings-modelsources-t4-c-license-servers.service.ts @@ -23,6 +23,8 @@ import { HTTPValidationError } from '../model/http-validation-error'; // @ts-ignore import { PatchT4CLicenseServer } from '../model/patch-t4-c-license-server'; // @ts-ignore +import { PublicLicenseServerWithUsage } from '../model/public-license-server-with-usage'; +// @ts-ignore import { T4CLicenseServer } from '../model/t4-c-license-server'; // @ts-ignore import { T4CLicenseServerBase } from '../model/t4-c-license-server-base'; @@ -338,17 +340,17 @@ export class SettingsModelsourcesT4CLicenseServersService { } /** - * Fetch T4C License Server Licenses + * Get T4C License Server * @param t4cLicenseServerId * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. * @param reportProgress flag to report request and response progress. */ - public fetchT4cLicenseServerLicenses(t4cLicenseServerId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; - public fetchT4cLicenseServerLicenses(t4cLicenseServerId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; - public fetchT4cLicenseServerLicenses(t4cLicenseServerId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; - public fetchT4cLicenseServerLicenses(t4cLicenseServerId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + public getT4cLicenseServer(t4cLicenseServerId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; + public getT4cLicenseServer(t4cLicenseServerId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public getT4cLicenseServer(t4cLicenseServerId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public getT4cLicenseServer(t4cLicenseServerId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { if (t4cLicenseServerId === null || t4cLicenseServerId === undefined) { - throw new Error('Required parameter t4cLicenseServerId was null or undefined when calling fetchT4cLicenseServerLicenses.'); + throw new Error('Required parameter t4cLicenseServerId was null or undefined when calling getT4cLicenseServer.'); } let localVarHeaders = this.defaultHeaders; @@ -394,8 +396,8 @@ export class SettingsModelsourcesT4CLicenseServersService { } } - let localVarPath = `/api/v1/settings/modelsources/t4c/license-servers/${this.configuration.encodeParam({name: "t4cLicenseServerId", value: t4cLicenseServerId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/usage`; - return this.httpClient.request('get', `${this.configuration.basePath}${localVarPath}`, + let localVarPath = `/api/v1/settings/modelsources/t4c/license-servers/${this.configuration.encodeParam({name: "t4cLicenseServerId", value: t4cLicenseServerId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}`; + return this.httpClient.request('get', `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, responseType: responseType_, @@ -409,17 +411,17 @@ export class SettingsModelsourcesT4CLicenseServersService { } /** - * Get T4C License Server + * Get T4C License Server Usage * @param t4cLicenseServerId * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. * @param reportProgress flag to report request and response progress. */ - public getT4cLicenseServer(t4cLicenseServerId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; - public getT4cLicenseServer(t4cLicenseServerId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; - public getT4cLicenseServer(t4cLicenseServerId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; - public getT4cLicenseServer(t4cLicenseServerId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + public getT4cLicenseServerUsage(t4cLicenseServerId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; + public getT4cLicenseServerUsage(t4cLicenseServerId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public getT4cLicenseServerUsage(t4cLicenseServerId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public getT4cLicenseServerUsage(t4cLicenseServerId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { if (t4cLicenseServerId === null || t4cLicenseServerId === undefined) { - throw new Error('Required parameter t4cLicenseServerId was null or undefined when calling getT4cLicenseServer.'); + throw new Error('Required parameter t4cLicenseServerId was null or undefined when calling getT4cLicenseServerUsage.'); } let localVarHeaders = this.defaultHeaders; @@ -465,8 +467,8 @@ export class SettingsModelsourcesT4CLicenseServersService { } } - let localVarPath = `/api/v1/settings/modelsources/t4c/license-servers/${this.configuration.encodeParam({name: "t4cLicenseServerId", value: t4cLicenseServerId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}`; - return this.httpClient.request('get', `${this.configuration.basePath}${localVarPath}`, + let localVarPath = `/api/v1/settings/modelsources/t4c/license-servers/${this.configuration.encodeParam({name: "t4cLicenseServerId", value: t4cLicenseServerId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/usage`; + return this.httpClient.request('get', `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, responseType: responseType_, @@ -546,4 +548,71 @@ export class SettingsModelsourcesT4CLicenseServersService { ); } + /** + * Get T4C License Servers Usage + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public getT4cLicenseServersUsage(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public getT4cLicenseServersUsage(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>>; + public getT4cLicenseServersUsage(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>>; + public getT4cLicenseServersUsage(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (PersonalAccessToken) required + localVarCredential = this.configuration.lookupCredential('PersonalAccessToken'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Basic ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + let localVarTransferCache: boolean | undefined = options && options.transferCache; + if (localVarTransferCache === undefined) { + localVarTransferCache = true; + } + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/api/v1/settings/modelsources/t4c/license-servers/usage`; + return this.httpClient.request>('get', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + transferCache: localVarTransferCache, + reportProgress: reportProgress + } + ); + } + } diff --git a/frontend/src/app/openapi/model/models.ts b/frontend/src/app/openapi/model/models.ts index 645353a7e9..40cd5ee7a9 100644 --- a/frontend/src/app/openapi/model/models.ts +++ b/frontend/src/app/openapi/model/models.ts @@ -109,6 +109,7 @@ export * from './project-user-role'; export * from './prometheus-configuration-input'; export * from './prometheus-configuration-output'; export * from './protocol'; +export * from './public-license-server-with-usage'; export * from './pure-variants-licenses-input'; export * from './pure-variants-licenses-output'; export * from './put-git-model'; diff --git a/frontend/src/app/openapi/model/public-license-server-with-usage.ts b/frontend/src/app/openapi/model/public-license-server-with-usage.ts new file mode 100644 index 0000000000..3969cbb6f5 --- /dev/null +++ b/frontend/src/app/openapi/model/public-license-server-with-usage.ts @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors + * SPDX-License-Identifier: Apache-2.0 + * + * Capella Collaboration + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + + To generate a new version, run `make openapi` in the root directory of this repository. + */ + +import { T4CLicenseServerUsage } from './t4-c-license-server-usage'; + + +export interface PublicLicenseServerWithUsage { + id: number; + name: string; + usage: T4CLicenseServerUsage; +} + diff --git a/frontend/src/app/sessions/license-indicator/license-indicator.component.html b/frontend/src/app/sessions/license-indicator/license-indicator.component.html new file mode 100644 index 0000000000..f38c2d59bd --- /dev/null +++ b/frontend/src/app/sessions/license-indicator/license-indicator.component.html @@ -0,0 +1,35 @@ + + +@if ((licenseUsageWrapperService.licenseServerUsage$ | async) !== undefined) { + @for ( + licenseServer of licenseUsageWrapperService.licenseServerUsage$ | async; + track licenseServer.id + ) { + @if (licenseServer.usage.free === 0) { +
+ error + All TeamForCapella licenses are currently in use. You can start new + sessions, but may encounter the error "Invalid license" when trying to + use TeamForCapella. Please make sure to terminate your sessions after + use. + +
+ } @else if (licenseServer.usage.free / licenseServer.usage.total < 0.25) { +
+ warning + Most TeamForCapella licenses are currently in use. Please make sure + to terminate your sessions after use. + +
+ } + } +} diff --git a/frontend/src/app/sessions/license-indicator/license-indicator.component.ts b/frontend/src/app/sessions/license-indicator/license-indicator.component.ts new file mode 100644 index 0000000000..596db15b39 --- /dev/null +++ b/frontend/src/app/sessions/license-indicator/license-indicator.component.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import { AsyncPipe } from '@angular/common'; +import { Component } from '@angular/core'; +import { MatIcon } from '@angular/material/icon'; +import { LicenseUsageWrapperService } from './license-usage.service'; + +@Component({ + selector: 'app-license-indicator', + standalone: true, + imports: [MatIcon, AsyncPipe], + templateUrl: './license-indicator.component.html', +}) +export class LicenseIndicatorComponent { + constructor(public licenseUsageWrapperService: LicenseUsageWrapperService) {} +} diff --git a/frontend/src/app/sessions/license-indicator/license-indicator.stories.ts b/frontend/src/app/sessions/license-indicator/license-indicator.stories.ts new file mode 100644 index 0000000000..1439a24dc8 --- /dev/null +++ b/frontend/src/app/sessions/license-indicator/license-indicator.stories.ts @@ -0,0 +1,101 @@ +/* + * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import { Meta, moduleMetadata, StoryObj } from '@storybook/angular'; +import { BehaviorSubject } from 'rxjs'; +import { PublicLicenseServerWithUsage } from '../../openapi'; +import { LicenseIndicatorComponent } from './license-indicator.component'; +import { LicenseUsageWrapperService } from './license-usage.service'; + +const meta: Meta = { + title: 'Session Components/License Indicator', + component: LicenseIndicatorComponent, +}; + +export default meta; +type Story = StoryObj; + +class MockLicenseUsageWrapperService + implements Partial +{ + private _licenseServerUsage = new BehaviorSubject< + PublicLicenseServerWithUsage[] | undefined + >(undefined); + readonly licenseServerUsage$ = this._licenseServerUsage.asObservable(); + constructor(licenseServerUsage: PublicLicenseServerWithUsage[]) { + this._licenseServerUsage.next(licenseServerUsage); + } +} + +export const AllUsed: Story = { + args: {}, + decorators: [ + moduleMetadata({ + providers: [ + { + provide: LicenseUsageWrapperService, + useFactory: () => + new MockLicenseUsageWrapperService([ + { + id: 1, + name: 'Test', + usage: { + free: 0, + total: 30, + }, + }, + ]), + }, + ], + }), + ], +}; + +export const SomeUsed: Story = { + args: {}, + decorators: [ + moduleMetadata({ + providers: [ + { + provide: LicenseUsageWrapperService, + useFactory: () => + new MockLicenseUsageWrapperService([ + { + id: 1, + name: 'Test', + usage: { + free: 5, + total: 30, + }, + }, + ]), + }, + ], + }), + ], +}; + +export const FewUsed: Story = { + args: {}, + decorators: [ + moduleMetadata({ + providers: [ + { + provide: LicenseUsageWrapperService, + useFactory: () => + new MockLicenseUsageWrapperService([ + { + id: 1, + name: 'Test', + usage: { + free: 25, + total: 30, + }, + }, + ]), + }, + ], + }), + ], +}; diff --git a/frontend/src/app/sessions/license-indicator/license-usage.service.ts b/frontend/src/app/sessions/license-indicator/license-usage.service.ts new file mode 100644 index 0000000000..718fb3c44f --- /dev/null +++ b/frontend/src/app/sessions/license-indicator/license-usage.service.ts @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Observable, tap } from 'rxjs'; +import { + PublicLicenseServerWithUsage, + SettingsModelsourcesT4CLicenseServersService, +} from '../../openapi'; + +@Injectable({ + providedIn: 'root', +}) +export class LicenseUsageWrapperService { + private _licenseServerUsage = new BehaviorSubject< + PublicLicenseServerWithUsage[] | undefined + >(undefined); + + readonly licenseServerUsage$ = this._licenseServerUsage.asObservable(); + + constructor( + private licenseServerService: SettingsModelsourcesT4CLicenseServersService, + ) { + this.loadLicenseServerUsage().subscribe(); + } + + loadLicenseServerUsage(): Observable { + return this.licenseServerService + .getT4cLicenseServersUsage() + .pipe(tap((usage) => this._licenseServerUsage.next(usage))); + } +} diff --git a/frontend/src/app/sessions/user-sessions-wrapper/create-sessions/create-persistent-session/create-persistent-session.component.html b/frontend/src/app/sessions/user-sessions-wrapper/create-sessions/create-persistent-session/create-persistent-session.component.html index 5eb8274b01..8d80805df7 100644 --- a/frontend/src/app/sessions/user-sessions-wrapper/create-sessions/create-persistent-session/create-persistent-session.component.html +++ b/frontend/src/app/sessions/user-sessions-wrapper/create-sessions/create-persistent-session/create-persistent-session.component.html @@ -91,6 +91,8 @@

Persistent Workspace Session

+ +
diff --git a/frontend/src/app/sessions/user-sessions-wrapper/create-sessions/create-persistent-session/create-persistent-session.component.ts b/frontend/src/app/sessions/user-sessions-wrapper/create-sessions/create-persistent-session/create-persistent-session.component.ts index 0756915cac..bd8e7dac97 100644 --- a/frontend/src/app/sessions/user-sessions-wrapper/create-sessions/create-persistent-session/create-persistent-session.component.ts +++ b/frontend/src/app/sessions/user-sessions-wrapper/create-sessions/create-persistent-session/create-persistent-session.component.ts @@ -2,23 +2,23 @@ * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors * SPDX-License-Identifier: Apache-2.0 */ -import { NgFor, NgIf, AsyncPipe } from '@angular/common'; +import { AsyncPipe, NgFor, NgIf } from '@angular/common'; import { Component, OnInit } from '@angular/core'; import { FormControl, FormGroup, - Validators, FormsModule, ReactiveFormsModule, + Validators, } from '@angular/forms'; import { MatButton } from '@angular/material/button'; import { MatOption } from '@angular/material/core'; -import { MatFormField, MatLabel, MatError } from '@angular/material/form-field'; +import { MatError, MatFormField, MatLabel } from '@angular/material/form-field'; import { MatIcon } from '@angular/material/icon'; -import { MatRadioGroup, MatRadioButton } from '@angular/material/radio'; +import { MatRadioButton, MatRadioGroup } from '@angular/material/radio'; import { MatSelect } from '@angular/material/select'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; -import { Observable, map } from 'rxjs'; +import { map, Observable } from 'rxjs'; import { Session, Tool, ToolVersion } from 'src/app/openapi'; import { SessionService } from 'src/app/sessions/service/session.service'; import { UserSessionService } from 'src/app/sessions/service/user-session.service'; @@ -26,6 +26,7 @@ import { ConnectionMethod, ToolWrapperService, } from 'src/app/settings/core/tools-settings/tool.service'; +import { LicenseIndicatorComponent } from '../../../license-indicator/license-indicator.component'; import { CreateSessionHistoryComponent } from '../create-session-history/create-session-history.component'; @UntilDestroy() @@ -50,6 +51,7 @@ import { CreateSessionHistoryComponent } from '../create-session-history/create- MatIcon, CreateSessionHistoryComponent, AsyncPipe, + LicenseIndicatorComponent, ], }) export class CreatePersistentSessionComponent implements OnInit { diff --git a/frontend/src/app/sessions/user-sessions-wrapper/create-sessions/create-persistent-session/create-persistent-session.stories.ts b/frontend/src/app/sessions/user-sessions-wrapper/create-sessions/create-persistent-session/create-persistent-session.stories.ts new file mode 100644 index 0000000000..d4907f0719 --- /dev/null +++ b/frontend/src/app/sessions/user-sessions-wrapper/create-sessions/create-persistent-session/create-persistent-session.stories.ts @@ -0,0 +1,72 @@ +/* + * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import { Meta, moduleMetadata, StoryObj } from '@storybook/angular'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { mockTool } from '../../../../../storybook/tool'; +import { PublicLicenseServerWithUsage, Tool } from '../../../../openapi'; +import { ToolWrapperService } from '../../../../settings/core/tools-settings/tool.service'; +import { LicenseUsageWrapperService } from '../../../license-indicator/license-usage.service'; +import { CreatePersistentSessionComponent } from './create-persistent-session.component'; + +const meta: Meta = { + title: 'Session Components/Create Persistent Session', + component: CreatePersistentSessionComponent, +}; + +export default meta; +type Story = StoryObj; + +class MockLicenseUsageWrapperService + implements Partial +{ + private _licenseServerUsage = new BehaviorSubject< + PublicLicenseServerWithUsage[] | undefined + >(undefined); + readonly licenseServerUsage$ = this._licenseServerUsage.asObservable(); + constructor(licenseServerUsage: PublicLicenseServerWithUsage[]) { + this._licenseServerUsage.next(licenseServerUsage); + } +} +class MockToolWrapperService implements Partial { + _tools = new BehaviorSubject(undefined); + get tools(): Tool[] | undefined { + return this._tools.getValue(); + } + get tools$(): Observable { + return this._tools.asObservable(); + } + + constructor(tools: Tool[]) { + this._tools.next(tools); + } +} + +export const Default: Story = { + args: {}, + decorators: [ + moduleMetadata({ + providers: [ + { + provide: LicenseUsageWrapperService, + useFactory: () => + new MockLicenseUsageWrapperService([ + { + id: 1, + name: 'Test', + usage: { + free: 0, + total: 30, + }, + }, + ]), + }, + { + provide: ToolWrapperService, + useFactory: () => new MockToolWrapperService([mockTool]), + }, + ], + }), + ], +};