From 912e39980fc3def24ff25ecd68b67bdd74c1f754 Mon Sep 17 00:00:00 2001 From: Jasper Herzberg Date: Wed, 21 Feb 2024 08:24:59 +0100 Subject: [PATCH] feat: add dashboard (#695) Co-authored-by: Timon Masberg --- apps/spa-e2e/src/auth.setup.ts | 4 +- apps/spa-e2e/src/auth.spec.ts | 6 +- apps/spa/project.json | 4 +- apps/spa/src/app/app.module.ts | 5 +- .../src/app/component/protected.component.ts | 12 ---- apps/spa/src/app/routes.ts | 11 ++-- apps/spa/tsconfig.app.json | 3 +- apps/spa/tsconfig.json | 3 +- libs/spa/dashboard/.eslintrc.json | 36 ++++++++++++ libs/spa/dashboard/README.md | 8 +++ libs/spa/dashboard/jest.config.ts | 22 +++++++ libs/spa/dashboard/project.json | 21 +++++++ libs/spa/dashboard/src/index.ts | 1 + .../src/lib/dashboard/dashboard.component.css | 57 +++++++++++++++++++ .../lib/dashboard/dashboard.component.html | 37 ++++++++++++ .../lib/dashboard/dashboard.component.spec.ts | 49 ++++++++++++++++ .../src/lib/dashboard/dashboard.component.ts | 56 ++++++++++++++++++ libs/spa/dashboard/src/test-setup.ts | 9 +++ libs/spa/dashboard/tsconfig.json | 29 ++++++++++ libs/spa/dashboard/tsconfig.lib.json | 17 ++++++ libs/spa/dashboard/tsconfig.spec.json | 16 ++++++ tsconfig.base.json | 1 + 22 files changed, 377 insertions(+), 30 deletions(-) delete mode 100644 apps/spa/src/app/component/protected.component.ts create mode 100644 libs/spa/dashboard/.eslintrc.json create mode 100644 libs/spa/dashboard/README.md create mode 100644 libs/spa/dashboard/jest.config.ts create mode 100644 libs/spa/dashboard/project.json create mode 100644 libs/spa/dashboard/src/index.ts create mode 100644 libs/spa/dashboard/src/lib/dashboard/dashboard.component.css create mode 100644 libs/spa/dashboard/src/lib/dashboard/dashboard.component.html create mode 100644 libs/spa/dashboard/src/lib/dashboard/dashboard.component.spec.ts create mode 100644 libs/spa/dashboard/src/lib/dashboard/dashboard.component.ts create mode 100644 libs/spa/dashboard/src/test-setup.ts create mode 100644 libs/spa/dashboard/tsconfig.json create mode 100644 libs/spa/dashboard/tsconfig.lib.json create mode 100644 libs/spa/dashboard/tsconfig.spec.json diff --git a/apps/spa-e2e/src/auth.setup.ts b/apps/spa-e2e/src/auth.setup.ts index 220267a5..0ade6edb 100644 --- a/apps/spa-e2e/src/auth.setup.ts +++ b/apps/spa-e2e/src/auth.setup.ts @@ -21,7 +21,7 @@ setup('authenticate as testusers', async ({ browser }) => { const context = await browser.newContext(); const page = await context.newPage(); await new LoginPo(page).loginWithB2C(username, password); - await page.waitForURL('/protected'); + await page.waitForURL('/dashboard'); await context.storageState({ path: getAuthStoragePath(username) }); await context.close(); @@ -31,7 +31,7 @@ setup('authenticate as testusers', async ({ browser }) => { const context = await browser.newContext(); const page = await context.newPage(); await new LoginPo(page).loginViaDevAuth(user.userName); - await page.waitForURL('/protected'); + await page.waitForURL('/dashboard'); await context.storageState({ path: getAuthStoragePath(user.userName) }); await context.close(); diff --git a/apps/spa-e2e/src/auth.spec.ts b/apps/spa-e2e/src/auth.spec.ts index d2eb974d..f84e8377 100644 --- a/apps/spa-e2e/src/auth.spec.ts +++ b/apps/spa-e2e/src/auth.spec.ts @@ -12,9 +12,9 @@ test('should get redirected to auth as unauthenticated', async ({ page }) => { test.describe('as authenticated', () => { asUser('testuser'); - test('should get initially redirected to /protected', async ({ page }) => { + test('should get initially redirected to /dashboard', async ({ page }) => { await page.goto('/'); - await page.waitForURL('/protected'); - await expect(page).toHaveURL('/protected'); + await page.waitForURL('/dashboard'); + await expect(page).toHaveURL('/dashboard'); }); }); diff --git a/apps/spa/project.json b/apps/spa/project.json index d735a85f..134a8399 100644 --- a/apps/spa/project.json +++ b/apps/spa/project.json @@ -37,8 +37,8 @@ }, { "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "maximumWarning": "500kb", + "maximumError": "1mb" } ], "outputHashing": "all" diff --git a/apps/spa/src/app/app.module.ts b/apps/spa/src/app/app.module.ts index 075ff52f..2121dfa2 100644 --- a/apps/spa/src/app/app.module.ts +++ b/apps/spa/src/app/app.module.ts @@ -10,6 +10,7 @@ import DOMPurify from 'dompurify'; import { NZ_I18N, de_DE } from 'ng-zorro-antd/i18n'; import { AuthModule, DevAuthModule } from '@kordis/spa/auth'; +import { DashboardComponent } from '@kordis/spa/dashboard'; import { GraphqlModule } from '@kordis/spa/graphql'; import { NoopObservabilityModule, @@ -18,13 +19,12 @@ import { import { environment } from '../environments/environment'; import { AppComponent } from './component/app.component'; -import { ProtectedComponent } from './component/protected.component'; import routes from './routes'; registerLocaleData(de); @NgModule({ - declarations: [AppComponent, ProtectedComponent], + declarations: [AppComponent], imports: [ BrowserModule, BrowserAnimationsModule, @@ -53,6 +53,7 @@ registerLocaleData(de); // or after 30 seconds (whichever comes first). registrationStrategy: 'registerWhenStable:30000', }), + DashboardComponent, ], providers: [ { diff --git a/apps/spa/src/app/component/protected.component.ts b/apps/spa/src/app/component/protected.component.ts deleted file mode 100644 index f2edb8b2..00000000 --- a/apps/spa/src/app/component/protected.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; - -import { TraceComponent } from '@kordis/spa/observability'; - -// placeholder until we have a feature structure -@Component({ - selector: 'kordis-protected', - template: ` ganz geheim `, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -@TraceComponent() -export class ProtectedComponent {} diff --git a/apps/spa/src/app/routes.ts b/apps/spa/src/app/routes.ts index 3f56af7a..3eb44d9e 100644 --- a/apps/spa/src/app/routes.ts +++ b/apps/spa/src/app/routes.ts @@ -1,21 +1,20 @@ import { Routes } from '@angular/router'; import { authGuard } from '@kordis/spa/auth'; - -import { ProtectedComponent } from './component/protected.component'; +import { DashboardComponent } from '@kordis/spa/dashboard'; const routes: Routes = [ { path: '', - redirectTo: 'protected', + redirectTo: 'dashbaord', pathMatch: 'full', }, { - path: 'protected', - component: ProtectedComponent, + path: 'dashboard', + component: DashboardComponent, canActivate: [authGuard], }, - { path: '**', redirectTo: 'protected' }, + { path: '**', redirectTo: 'dashboard' }, ]; export default routes; diff --git a/apps/spa/tsconfig.app.json b/apps/spa/tsconfig.app.json index aa6411ea..d361195a 100644 --- a/apps/spa/tsconfig.app.json +++ b/apps/spa/tsconfig.app.json @@ -4,8 +4,7 @@ "outDir": "../../dist/out-tsc", "types": [], "target": "ES2022", - "useDefineForClassFields": false, - "allowSyntheticDefaultImports": true + "useDefineForClassFields": false }, "files": ["src/main.ts"], "include": ["src/**/*.d.ts", "../../reset.d.ts"], diff --git a/apps/spa/tsconfig.json b/apps/spa/tsconfig.json index e01cf19b..174c1724 100644 --- a/apps/spa/tsconfig.json +++ b/apps/spa/tsconfig.json @@ -7,7 +7,8 @@ "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "allowSyntheticDefaultImports": true }, "files": [], "include": [], diff --git a/libs/spa/dashboard/.eslintrc.json b/libs/spa/dashboard/.eslintrc.json new file mode 100644 index 00000000..af40eea3 --- /dev/null +++ b/libs/spa/dashboard/.eslintrc.json @@ -0,0 +1,36 @@ +{ + "extends": ["../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts"], + "extends": [ + "plugin:@nx/angular", + "plugin:@angular-eslint/template/process-inline-templates" + ], + "rules": { + "@angular-eslint/directive-selector": [ + "error", + { + "type": "attribute", + "prefix": "krd", + "style": "camelCase" + } + ], + "@angular-eslint/component-selector": [ + "error", + { + "type": "element", + "prefix": "krd", + "style": "kebab-case" + } + ] + } + }, + { + "files": ["*.html"], + "extends": ["plugin:@nx/angular-template"], + "rules": {} + } + ] +} diff --git a/libs/spa/dashboard/README.md b/libs/spa/dashboard/README.md new file mode 100644 index 00000000..c396b892 --- /dev/null +++ b/libs/spa/dashboard/README.md @@ -0,0 +1,8 @@ +# Dashboard + +This library provides the dashboard view which integrates the protocol, +operations and deployments view as well as a menu and some user functionality. + +## Running unit tests + +Run `nx test spa-dashboard` to execute the unit tests. diff --git a/libs/spa/dashboard/jest.config.ts b/libs/spa/dashboard/jest.config.ts new file mode 100644 index 00000000..f8214739 --- /dev/null +++ b/libs/spa/dashboard/jest.config.ts @@ -0,0 +1,22 @@ +/* eslint-disable */ +export default { + displayName: 'spa-dashboard', + preset: '../../../jest.preset.js', + setupFilesAfterEnv: ['/src/test-setup.ts'], + coverageDirectory: '../../../coverage/libs/spa/dashboard', + transform: { + '^.+\\.(ts|mjs|js|html)$': [ + 'jest-preset-angular', + { + tsconfig: '/tsconfig.spec.json', + stringifyContentPathRegex: '\\.(html|svg)$', + }, + ], + }, + transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], + snapshotSerializers: [ + 'jest-preset-angular/build/serializers/no-ng-attributes', + 'jest-preset-angular/build/serializers/ng-snapshot', + 'jest-preset-angular/build/serializers/html-comment', + ], +}; diff --git a/libs/spa/dashboard/project.json b/libs/spa/dashboard/project.json new file mode 100644 index 00000000..ee3a070a --- /dev/null +++ b/libs/spa/dashboard/project.json @@ -0,0 +1,21 @@ +{ + "name": "spa-dashboard", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/spa/dashboard/src", + "prefix": "krd", + "tags": [], + "projectType": "library", + "targets": { + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/spa/dashboard/jest.config.ts" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] + } + } +} diff --git a/libs/spa/dashboard/src/index.ts b/libs/spa/dashboard/src/index.ts new file mode 100644 index 00000000..fcc210bf --- /dev/null +++ b/libs/spa/dashboard/src/index.ts @@ -0,0 +1 @@ +export * from './lib/dashboard/dashboard.component'; diff --git a/libs/spa/dashboard/src/lib/dashboard/dashboard.component.css b/libs/spa/dashboard/src/lib/dashboard/dashboard.component.css new file mode 100644 index 00000000..ecd374f6 --- /dev/null +++ b/libs/spa/dashboard/src/lib/dashboard/dashboard.component.css @@ -0,0 +1,57 @@ +@import 'ng-zorro-antd/ng-zorro-antd.variable.min.css'; + +nz-layout { + --header-size: 64px; + + [nz-menu] { + line-height: var(--header-size); + } + + nz-header { + background: none; + display: flex; + justify-content: space-between; + align-items: center; + + .logo { + color: var(--ant-primary-color); + font-size: 24px; + } + } + + nz-content { + display: grid; + grid-gap: 8px; + grid-template-areas: + 'protocol operations' + 'deployments deployments'; + grid-template-columns: 3fr 2fr; + grid-template-rows: 3fr 2fr; + min-height: 720px; + min-width: 1280px; + padding: 8px; + width: 100%; + height: calc(100vh - var(--header-size)); + + .placeholder { + /* TODO: Remove this section when filled */ + height: 100%; + width: 100%; + background-color: snow; + padding: 16px; + border-radius: 2px; + } + + .protocol { + grid-area: protocol; + } + + .operations { + grid-area: operations; + } + + .deployments { + grid-area: deployments; + } + } +} diff --git a/libs/spa/dashboard/src/lib/dashboard/dashboard.component.html b/libs/spa/dashboard/src/lib/dashboard/dashboard.component.html new file mode 100644 index 00000000..aa9be872 --- /dev/null +++ b/libs/spa/dashboard/src/lib/dashboard/dashboard.component.html @@ -0,0 +1,37 @@ + + + +
+ + +
    +
  • + {{ (user$ | async)?.firstName }} {{ (user$ | async)?.lastName }} +
  • +
  • Abmelden
  • +
  • +
  • + Credits und Lizenzen +
  • +
+
+
+
+ +
+
Protocol
+
+
+
Operations
+
+
+
Deployments
+
+
+
diff --git a/libs/spa/dashboard/src/lib/dashboard/dashboard.component.spec.ts b/libs/spa/dashboard/src/lib/dashboard/dashboard.component.spec.ts new file mode 100644 index 00000000..f585ce4d --- /dev/null +++ b/libs/spa/dashboard/src/lib/dashboard/dashboard.component.spec.ts @@ -0,0 +1,49 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { createMock } from '@golevelup/ts-jest'; +import { NzModalModule, NzModalService } from 'ng-zorro-antd/modal'; +import { of } from 'rxjs'; + +import { AUTH_SERVICE, AuthService } from '@kordis/spa/auth'; + +import { DashboardComponent } from './dashboard.component'; + +describe('DashboardComponent', () => { + let component: DashboardComponent; + let fixture: ComponentFixture; + const authServiceMock = createMock({ + user$: of(null), + }); + const modalServiceMock = createMock(); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [BrowserAnimationsModule], + providers: [{ provide: AUTH_SERVICE, useValue: authServiceMock }], + }).compileComponents(); + + TestBed.overrideModule(NzModalModule, { + set: { + providers: [{ provide: NzModalService, useValue: modalServiceMock }], + }, + }); + + fixture = TestBed.createComponent(DashboardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should call authService.logout() when logout is called', () => { + component.logout(); + expect(authServiceMock.logout).toHaveBeenCalled(); + }); + + it('should call modalService.create() when showCreditsAndLicensesModal is called', () => { + component.showCreditsAndLicensesModal(); + expect(modalServiceMock.create).toHaveBeenCalled(); + }); +}); diff --git a/libs/spa/dashboard/src/lib/dashboard/dashboard.component.ts b/libs/spa/dashboard/src/lib/dashboard/dashboard.component.ts new file mode 100644 index 00000000..8a29ead3 --- /dev/null +++ b/libs/spa/dashboard/src/lib/dashboard/dashboard.component.ts @@ -0,0 +1,56 @@ +import { CommonModule } from '@angular/common'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { NzAvatarModule } from 'ng-zorro-antd/avatar'; +import { + NzDropDownDirective, + NzDropdownMenuComponent, +} from 'ng-zorro-antd/dropdown'; +import { + NzContentComponent, + NzHeaderComponent, + NzLayoutComponent, +} from 'ng-zorro-antd/layout'; +import { NzMenuModule } from 'ng-zorro-antd/menu'; +import { NzModalModule, NzModalService } from 'ng-zorro-antd/modal'; + +import { AUTH_SERVICE } from '@kordis/spa/auth'; + +@Component({ + selector: 'krd-dashboard-view', + standalone: true, + imports: [ + CommonModule, + NzLayoutComponent, + NzHeaderComponent, + NzContentComponent, + NzDropdownMenuComponent, + NzDropDownDirective, + NzMenuModule, + NzModalModule, + NzAvatarModule, + ], + templateUrl: './dashboard.component.html', + styleUrl: './dashboard.component.css', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DashboardComponent { + private readonly authService = inject(AUTH_SERVICE); + readonly user$ = this.authService.user$; + private readonly modal = inject(NzModalService); + + logout(): void { + this.authService.logout(); + } + + showCreditsAndLicensesModal(): void { + this.modal.create({ + nzTitle: 'Kordis - Credits und Lizenzen', + nzContent: ` +

Eine Software von Jasper Herzberg und Timon Masberg veröffentlicht unter der GNU Affero General Public Lizenz in der Version 3. Der Code ist frei verfügbar auf Github.

+

Für die Entwicklung von Kordis wurden Open-Source Projekte verwendet. Eine Liste der Projekte und deren Lizenzen kann hier eingesehen werden, der Licence-Complience Check wird durch FOSSA durchgeführt und hier veröffentlicht.

+ `, + nzClosable: true, + nzFooter: null, + }); + } +} diff --git a/libs/spa/dashboard/src/test-setup.ts b/libs/spa/dashboard/src/test-setup.ts new file mode 100644 index 00000000..58672d7b --- /dev/null +++ b/libs/spa/dashboard/src/test-setup.ts @@ -0,0 +1,9 @@ +import 'jest-preset-angular/setup-jest'; + +// @ts-expect-error https://thymikee.github.io/jest-preset-angular/docs/getting-started/test-environment +globalThis.ngJest = { + testEnvironmentOptions: { + errorOnUnknownElements: true, + errorOnUnknownProperties: true, + }, +}; diff --git a/libs/spa/dashboard/tsconfig.json b/libs/spa/dashboard/tsconfig.json new file mode 100644 index 00000000..5cf0a165 --- /dev/null +++ b/libs/spa/dashboard/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "es2022", + "useDefineForClassFields": false, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "extends": "../../../tsconfig.base.json", + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/libs/spa/dashboard/tsconfig.lib.json b/libs/spa/dashboard/tsconfig.lib.json new file mode 100644 index 00000000..a28f762e --- /dev/null +++ b/libs/spa/dashboard/tsconfig.lib.json @@ -0,0 +1,17 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [] + }, + "exclude": [ + "src/**/*.spec.ts", + "src/test-setup.ts", + "jest.config.ts", + "src/**/*.test.ts" + ], + "include": ["src/**/*.ts"] +} diff --git a/libs/spa/dashboard/tsconfig.spec.json b/libs/spa/dashboard/tsconfig.spec.json new file mode 100644 index 00000000..fb725351 --- /dev/null +++ b/libs/spa/dashboard/tsconfig.spec.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "target": "es2016", + "types": ["jest", "node"] + }, + "files": ["src/test-setup.ts"], + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index d29d32c0..e826d9a5 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -25,6 +25,7 @@ "@kordis/shared/model": ["libs/shared/model/src/index.ts"], "@kordis/shared/test-helpers": ["libs/shared/test-helpers/src/index.ts"], "@kordis/spa/auth": ["libs/spa/auth/src/index.ts"], + "@kordis/spa/dashboard": ["libs/spa/dashboard/src/index.ts"], "@kordis/spa/graphql": ["libs/spa/graphql/src/index.ts"], "@kordis/spa/observability": ["libs/spa/observability/src/index.ts"] }