Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Admin UI: Breadcrumbs #500

Merged
merged 52 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
848a712
fix: tests now run when script is used
stamenione Jan 17, 2024
e0329fb
chore: add data to support breadcrumb label
stamenione Jan 17, 2024
997e483
feat: implement BreadcrumbService and add appropriate tests for Bread…
stamenione Jan 18, 2024
3920585
feat: add breadcrumb component to AdminUI
stamenione Jan 18, 2024
c71a476
chore: add breadcrumb component to module and in appropriate place of…
stamenione Jan 18, 2024
e882a51
chore: clear localStorage for breadcrumb history
stamenione Jan 18, 2024
bfbe5c7
fix: create a deep copy of the result
stamenione Jan 18, 2024
614a686
chore: add css styling for breadcrumbs
stamenione Jan 18, 2024
0f09117
fix: eslint errors and add router module
stamenione Jan 19, 2024
342bc59
chore: click on one of the sidebar items resets the breadcrumbs
stamenione Jan 22, 2024
b3d3988
chore: ignoring the history changes
stamenione Jan 22, 2024
a03e141
chore: prettier
stamenione Jan 22, 2024
fd27062
Merge branch 'main' into add-breadcrumb-navigation
stamenione Jan 22, 2024
657e020
chore: unnecessary set of the breadcrumb history
stamenione Jan 22, 2024
50e1b75
chore: display dynamic data if there is one instead of breadcrumb label
stamenione Jan 22, 2024
3062df1
chore: prettier
stamenione Jan 22, 2024
b930f35
Helm Chart: Update Consumer API, Admin UI and Admin CLI (#501)
tnotheis Jan 22, 2024
5e68c58
Merge main into add-breadcrumb-navigation
github-actions[bot] Jan 22, 2024
dcd55aa
Helm Chart: Update Consumer API, Admin UI and Admin CLI (#501)
tnotheis Jan 22, 2024
1beb375
Merge main into add-breadcrumb-navigation
github-actions[bot] Jan 22, 2024
9e1883b
Merge main into add-breadcrumb-navigation
github-actions[bot] Jan 22, 2024
436e7fe
chore: now using session storage
stamenione Jan 22, 2024
1331d9d
Merge branch 'add-breadcrumb-navigation' of https://github.com/nmshd/…
stamenione Jan 22, 2024
56bb413
Merge main into add-breadcrumb-navigation
github-actions[bot] Jan 26, 2024
4f4e5c0
ci: trigger pipelines
stamenione Jan 29, 2024
2d969dd
Merge main into add-breadcrumb-navigation
github-actions[bot] Jan 29, 2024
f518702
chore: remove unused breadcrumb data
stamenione Jan 29, 2024
1223c78
refactor: enhance logout in AuthService to clear breadcrumb history
stamenione Jan 29, 2024
c96aaad
test: now use map instead of forEach
stamenione Jan 29, 2024
3802601
refactor: unify variable names and remove redundant type definitions …
stamenione Jan 29, 2024
5cf281b
Merge branch 'add-breadcrumb-navigation' of https://github.com/nmshd/…
stamenione Jan 29, 2024
b683962
chore: prettier
stamenione Jan 29, 2024
63c07cf
Merge main into add-breadcrumb-navigation
github-actions[bot] Jan 29, 2024
6627b71
Merge main into add-breadcrumb-navigation
github-actions[bot] Jan 30, 2024
e027c3e
chore: replace forEach and map with simple for loop
stamenione Jan 30, 2024
e360b83
Merge branch 'add-breadcrumb-navigation' of https://github.com/nmshd/…
stamenione Jan 30, 2024
7ae5ea0
Merge main into add-breadcrumb-navigation
github-actions[bot] Feb 5, 2024
ae8a291
Merge main into add-breadcrumb-navigation
github-actions[bot] Feb 5, 2024
34be81b
ci: trigger pipelines
stamenione Feb 6, 2024
cb15075
Merge main into add-breadcrumb-navigation
github-actions[bot] Feb 6, 2024
67edd6b
ci: trigger pipelines
stamenione Feb 7, 2024
d227e93
Merge branch 'add-breadcrumb-navigation' of https://github.com/nmshd/…
stamenione Feb 7, 2024
569c53d
Merge main into add-breadcrumb-navigation
github-actions[bot] Feb 9, 2024
4aae064
Merge main into add-breadcrumb-navigation
github-actions[bot] Feb 9, 2024
46239b6
chore: unnecessary type definition
stamenione Feb 9, 2024
228497f
chore: remove null suppressing operator
stamenione Feb 9, 2024
6b0a459
chore: give a variable a proper name
stamenione Feb 9, 2024
ad5809d
Merge main into add-breadcrumb-navigation
github-actions[bot] Feb 9, 2024
22d9966
Merge main into add-breadcrumb-navigation
github-actions[bot] Feb 9, 2024
e2d7fbe
Merge main into add-breadcrumb-navigation
github-actions[bot] Feb 9, 2024
5e05dd5
chore: remove unnecessary array duplication
stamenione Feb 9, 2024
926252c
Merge branch 'main' into add-breadcrumb-navigation
mergify[bot] Feb 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ ASALocalRun/

# Local History for Visual Studio
.localhistory/
**/.history/*

# BeatPulse healthcheck temp database
healthchecksdb
Expand Down
12 changes: 6 additions & 6 deletions AdminUi/src/AdminUi/ClientApp/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ import { LoginComponent } from "./components/shared/login/login.component";
const routes: Routes = [
{ path: "", redirectTo: "/dashboard", pathMatch: "full" },
{ path: "login", component: LoginComponent },
{ path: "dashboard", component: DashboardComponent, canActivate: [AuthGuard] },
{ path: "identities", component: IdentityListComponent, canActivate: [AuthGuard] },
{ path: "dashboard", component: DashboardComponent, data: { breadcrumb: "Dashboard" }, canActivate: [AuthGuard] },
{ path: "identities", component: IdentityListComponent, data: { breadcrumb: "Identities" }, canActivate: [AuthGuard] },
{ path: "identities/:address", component: IdentityDetailsComponent, canActivate: [AuthGuard] },
{ path: "tiers", component: TierListComponent, canActivate: [AuthGuard] },
{ path: "tiers/create", component: TierEditComponent, canActivate: [AuthGuard] },
{ path: "tiers", component: TierListComponent, data: { breadcrumb: "Tiers" }, canActivate: [AuthGuard] },
{ path: "tiers/create", component: TierEditComponent, data: { breadcrumb: "Create Tier" }, canActivate: [AuthGuard] },
{ path: "tiers/:id", component: TierEditComponent, canActivate: [AuthGuard] },
{ path: "clients", component: ClientListComponent, canActivate: [AuthGuard] },
{ path: "clients/create", component: ClientEditComponent, canActivate: [AuthGuard] },
{ path: "clients", component: ClientListComponent, data: { breadcrumb: "Clients" }, canActivate: [AuthGuard] },
{ path: "clients/create", component: ClientEditComponent, data: { breadcrumb: "Create Client" }, canActivate: [AuthGuard] },
{ path: "clients/:id", component: ClientEditComponent, canActivate: [AuthGuard] },
{ path: "**", component: PageNotFoundComponent }
];
Expand Down
1 change: 1 addition & 0 deletions AdminUi/src/AdminUi/ClientApp/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
</mat-sidenav>
<mat-sidenav-content class="layout-main-container">
<div class="layout-content">
<app-breadcrumb></app-breadcrumb>
<router-outlet (activate)="changeOfRoute()"></router-outlet>
</div>
</mat-sidenav-content>
Expand Down
4 changes: 3 additions & 1 deletion AdminUi/src/AdminUi/ClientApp/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { TierEditComponent } from "./components/quotas/tier/tier-edit/tier-edit.
import { TierListComponent } from "./components/quotas/tier/tier-list/tier-list.component";
import { ConfirmationDialogComponent } from "./components/shared/confirmation-dialog/confirmation-dialog.component";
import { IdentitiesOverviewComponent } from "./components/shared/identities-overview/identities-overview.component";
import { BreadcrumbComponent } from "./components/shared/breadcrumb/breadcrumb.component";
import { LoginComponent } from "./components/shared/login/login.component";
import { SidebarComponent } from "./components/sidebar/sidebar.component";
import { TopbarComponent } from "./components/topbar/topbar.component";
Expand Down Expand Up @@ -76,7 +77,8 @@ import { XSRFInterceptor } from "./shared/interceptors/xsrf.interceptor";
LoginComponent,
ChangeSecretDialogComponent,
IdentitiesOverviewComponent,
IdentityDetailsRelationshipsComponent
IdentityDetailsRelationshipsComponent,
BreadcrumbComponent
],
imports: [
FormsModule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
nav {
font-size: 16px;
margin: 10px 0;
}

a {
color: #17428d;
text-decoration: none;
transition: color 0.3s ease;
}

a:hover {
color: #076fde;
}

span::after {
content: " > ";
margin: 0 5px;
color: #999;
}

span:last-child::after {
content: "";
}

span.active {
color: #333;
font-weight: bold;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<nav>
<ng-container *ngFor="let trail of breadcrumbHistory; let i = index; let last = last">
<ng-container *ngIf="!last || (last && breadcrumbHistory.length > 1)">
<a *ngIf="!last" [routerLink]="trail.url" (click)="onBreadcrumbClick(i)">{{ trail.label }}</a>
<span *ngIf="last">{{ trail.label }}</span>
</ng-container>
<ng-container *ngIf="!last"> &gt; </ng-container>
</ng-container>
</nav>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { RouterTestingModule } from "@angular/router/testing";
import { BreadcrumbComponent } from "./breadcrumb.component";

describe("BreadcrumbComponent", function () {
let component: BreadcrumbComponent;
let fixture: ComponentFixture<BreadcrumbComponent>;

beforeEach(async function () {
await TestBed.configureTestingModule({
declarations: [BreadcrumbComponent],
imports: [RouterTestingModule]
}).compileComponents();

fixture = TestBed.createComponent(BreadcrumbComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it("should create", async function () {
await expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Component } from "@angular/core";
import { NavigationEnd, Router } from "@angular/router";
import { filter } from "rxjs/operators";
import { Breadcrumb, BreadcrumbService } from "src/app/services/breadcrumb-service/breadcrumb.service";

@Component({
selector: "app-breadcrumb",
templateUrl: "./breadcrumb.component.html",
styleUrls: ["./breadcrumb.component.css"]
})
export class BreadcrumbComponent {
public breadcrumbHistory: Breadcrumb[] = [];

public constructor(
private readonly breadcrumbService: BreadcrumbService,
private readonly router: Router
) {}

public ngOnInit(): void {
this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe(() => {
this.breadcrumbHistory = this.breadcrumbService.getBreadcrumbHistory();
});
}

public onBreadcrumbClick(index: number): void {
this.breadcrumbService.clearBreadcrumbHistoryAfterIndex(index);
this.breadcrumbHistory = this.breadcrumbService.getBreadcrumbHistory();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Router } from "@angular/router";
import { BehaviorSubject, Observable } from "rxjs";
import { environment } from "src/environments/environment";
import { XSRFService } from "../xsrf-service/xsrf.service";
import { BreadcrumbService } from "../breadcrumb-service/breadcrumb.service";

@Injectable({
providedIn: "root"
Expand All @@ -19,7 +20,8 @@ export class AuthService {
public constructor(
private readonly router: Router,
private readonly http: HttpClient,
private readonly xsrfService: XSRFService
private readonly xsrfService: XSRFService,
private readonly breadcrumbService: BreadcrumbService
) {
this.apiUrl = environment.apiUrl;
}
Expand Down Expand Up @@ -49,6 +51,7 @@ export class AuthService {

public async logout(): Promise<boolean> {
localStorage.removeItem("api-key");
this.breadcrumbService.clearBreadcrumbHistoryAfterIndex(0);
this.loggedIn.next(false);
this.xsrfService.clearStoredToken();
return await this.router.navigate(["/login"]);
Expand Down
tnotheis marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { TestBed } from "@angular/core/testing";
import { BreadcrumbService } from "./breadcrumb.service";
import { ActivatedRoute, NavigationEnd, Router } from "@angular/router";
import { BehaviorSubject } from "rxjs";

class MockActivatedRoute {
private readonly subject = new BehaviorSubject(this.testParams);
public params = this.subject.asObservable();

private _testParams!: {};
stamenione marked this conversation as resolved.
Show resolved Hide resolved
public get testParams() {
return this._testParams;
}
public set testParams(params: {}) {
this._testParams = params;
this.subject.next(params);
}
}

describe("BreadcrumbService", function () {
let breadcrumbService: BreadcrumbService;
let mockRouter: Router;
let mockActivatedRoute: MockActivatedRoute;

const initialHistory = [
{ label: "Home", url: "/home" },
{ label: "Details", url: "/details" },
{ label: "Products", url: "/products" }
];

beforeEach(function () {
mockRouter = {
events: new BehaviorSubject(new NavigationEnd(0, "", ""))
} as any;

mockActivatedRoute = new MockActivatedRoute();

TestBed.configureTestingModule({
providers: [BreadcrumbService, { provide: Router, useValue: mockRouter }, { provide: ActivatedRoute, useValue: mockActivatedRoute }]
});
breadcrumbService = TestBed.inject(BreadcrumbService);
});

it("should be created", async function () {
await expect(breadcrumbService).toBeTruthy();
});

it("should return an empty array initially", async function () {
// Arrange

// Act
const result = breadcrumbService.getBreadcrumbHistory();

// Assert
await expect(result.length).toEqual(0);
});

it("should return the breadcrumb history array", async function () {
// Arrange
const expectedHistory = [
{ label: "Home", url: "/home" },
{ label: "Details", url: "/details" },
{ label: "Products", url: "/products" }
];

// Act
for (const e of expectedHistory) {
stamenione marked this conversation as resolved.
Show resolved Hide resolved
breadcrumbService["breadcrumbHistory"].push(e);
}
const result = breadcrumbService.getBreadcrumbHistory();

// Assert
await expect(result).toEqual(expectedHistory);
});

it("should return a flat array when multiple trails are added", async function () {
// Arrange
const firstTrail = [{ label: "Home", url: "/home" }];
const secondTrail = [
{ label: "Details", url: "/details" },
{ label: "Products", url: "/products" }
];

breadcrumbService["breadcrumbHistory"] = [...firstTrail, ...secondTrail];

// Act
const result = breadcrumbService.getBreadcrumbHistory();

// Assert
await expect(result).toEqual([...firstTrail, ...secondTrail]);
});

it("should return a copy of the breadcrumb history array", async function () {
// Arrange
const expectedHistory = [
{ label: "Home", url: "/home" },
{ label: "Details", url: "/details" },
{ label: "Products", url: "/products" }
];

// Act
for (const e of expectedHistory) {
stamenione marked this conversation as resolved.
Show resolved Hide resolved
breadcrumbService["breadcrumbHistory"].push(e);
}
const result = JSON.parse(JSON.stringify(breadcrumbService.getBreadcrumbHistory()));
result[0].label = "Modified";

// Assert
await expect(result).not.toEqual(expectedHistory);
});

it("should not modify the internal breadcrumb history array when modifying the result", async function () {
// Arrange
const expectedHistory = [
{ label: "Home", url: "/home" },
{ label: "Details", url: "/details" },
{ label: "Products", url: "/products" }
];

// Act
for (const e of expectedHistory) {
stamenione marked this conversation as resolved.
Show resolved Hide resolved
breadcrumbService["breadcrumbHistory"].push(e);
}
const result = breadcrumbService.getBreadcrumbHistory();

// Modify the result
result[0].label = "Modified";

// Assert
await expect(breadcrumbService.getBreadcrumbHistory()).toEqual(expectedHistory);
});

it("should do nothing if the index is negative", async function () {
// Arrange
breadcrumbService["breadcrumbHistory"] = [...initialHistory];

// Act
breadcrumbService.clearBreadcrumbHistoryAfterIndex(-1);

// Assert
await expect(breadcrumbService.getBreadcrumbHistory()).toEqual(initialHistory);
});

it("should do nothing if the index is equal to the last index", async function () {
// Arrange
breadcrumbService["breadcrumbHistory"] = [...initialHistory];
const lastIndex = initialHistory.length - 1;

// Act
breadcrumbService.clearBreadcrumbHistoryAfterIndex(lastIndex);

// Assert
await expect(breadcrumbService.getBreadcrumbHistory()).toEqual(initialHistory);
});

it("should clear history after the specified index", async function () {
// Arrange
const expectedHistory = [
{ label: "Home", url: "/home" },
{ label: "Details", url: "/details" }
];
breadcrumbService["breadcrumbHistory"] = [...initialHistory];
const indexToClear = 1;

// Act
breadcrumbService.clearBreadcrumbHistoryAfterIndex(indexToClear);

// Assert
await expect(breadcrumbService.getBreadcrumbHistory()).toEqual(expectedHistory);
});
});
Loading