From 890fb5e4cd8178e758920049fe9fc9fc27e6570a Mon Sep 17 00:00:00 2001 From: Mojtaba Erfan Rad Date: Mon, 9 Sep 2024 14:23:22 +0330 Subject: [PATCH 1/2] fix(guard): guards are now fixed and redirect to login and dashboard based on permission,fixed graph view,edge info can now be seen using right click on edge * fix(guard): guards are now fixed and redirect to login and dashboard based on permission * fix(login-form): added tests * fix(data-analysis): fixed graph view * fix(data-analysis): edge info can now be seen using right click on edge --- api-config/api-url.ts | 4 +- src/app/app-routing.module.ts | 22 ++-- .../data-analysis.component.html | 6 +- .../data-analysis.component.spec.ts | 18 ++- .../data-analysis/data-analysis.component.ts | 46 ++++++-- .../components/data-analysis/graph-options.ts | 20 ++-- .../services/load-graph/load-graph.service.ts | 12 +- src/app/guards/auth/auth.guard.spec.ts | 26 +---- src/app/guards/auth/auth.guard.ts | 32 +----- src/app/guards/auth/login.guard.spec.ts | 104 ++++++++++++++++++ src/app/guards/auth/login.guard.ts | 36 ++++++ .../login-form/login-form.component.html | 14 +-- .../login-form/login-form.component.spec.ts | 96 +++++++++++++++- .../login/login-form/login-form.component.ts | 11 +- src/app/user/services/auth/auth.service.ts | 10 +- 15 files changed, 343 insertions(+), 114 deletions(-) create mode 100644 src/app/guards/auth/login.guard.spec.ts create mode 100644 src/app/guards/auth/login.guard.ts diff --git a/api-config/api-url.ts b/api-config/api-url.ts index 5ee4a0e..e8dd07c 100644 --- a/api-config/api-url.ts +++ b/api-config/api-url.ts @@ -1,4 +1,4 @@ export const environment = { - apiUrl: 'https://localhost:44322', - // apiUrl: 'http://localhost:8085', + // apiUrl: 'https://localhost:44322', + apiUrl: 'http://localhost:8085', }; diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 04b9e01..a08fd52 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -6,7 +6,6 @@ import { MainPageComponent } from './user/components/dashboard/main-page/main-pa import { ManageAccountComponent } from './user/components/dashboard/manage-account/manage-account.component'; import { DataAnalysisComponent } from './graph/components/data-analysis/data-analysis.component'; import { ManageUsersComponent } from './user/components/dashboard/manage-users/manage-users.component'; -import { AppComponent } from './app.component'; import { AuthGuard } from './guards/auth/auth.guard'; import { PermissionGuard } from './guards/permissions/permission.guard'; import { AddGraphComponent } from './graph/components/add-graph/add-graph.component'; @@ -15,11 +14,14 @@ import { CategoryComponent } from './graph/components/category/category.componen import { RecoverPassFormComponent } from './user/components/login/recover-pass-form/recover-pass-form.component'; import { LoginFormComponent } from './user/components/login/login-form/login-form.component'; import { ResetPasswordComponent } from './user/components/login/reset-password/reset-password.component'; +import { LoginGuard } from './guards/auth/login.guard'; const routes: Routes = [ + { path: '', redirectTo: '/login', pathMatch: 'full' }, { path: '', component: LoginComponent, + canActivate: [LoginGuard], children: [ { path: 'recover-password', @@ -30,16 +32,9 @@ const routes: Routes = [ path: 'login', component: LoginFormComponent, title: 'StarData | Login', - // canActivate: [AuthGuard], }, ], }, - // { - // path: 'login', - // component: LoginComponent, - // title: 'StarData | Login', - // canActivate: [AuthGuard], - // }, { path: 'dashboard', component: DashboardComponent, @@ -56,7 +51,7 @@ const routes: Routes = [ path: 'manage-users', component: ManageUsersComponent, title: 'StarData | Manage Users', - data: { permission: undefined }, + data: { permission: 'Register' }, }, { path: 'manage-account', @@ -67,21 +62,25 @@ const routes: Routes = [ path: 'data-analysis', component: DataAnalysisComponent, title: 'StarData | Data Analysis', + data: { permission: 'GetNodesAsync' }, }, { path: 'add-graph', component: AddGraphComponent, title: 'StarData | Add Graph', + data: { permission: 'UploadNodeFile' }, }, { path: 'assign-file', component: AssignFileComponent, title: 'StarData | Assign File', + data: { permission: 'AccessFileToUser' }, }, { path: 'manage-category', component: CategoryComponent, title: 'StarData | Manage Category', + data: { permission: 'GetCategories' }, }, ], }, @@ -90,11 +89,6 @@ const routes: Routes = [ component: ResetPasswordComponent, title: 'StarData | Reset Password', }, - { - path: '', - component: AppComponent, - canActivate: [AuthGuard], - }, ]; @NgModule({ diff --git a/src/app/graph/components/data-analysis/data-analysis.component.html b/src/app/graph/components/data-analysis/data-analysis.component.html index ae2ddfb..d589516 100644 --- a/src/app/graph/components/data-analysis/data-analysis.component.html +++ b/src/app/graph/components/data-analysis/data-analysis.component.html @@ -37,7 +37,11 @@ > - + @if (isNode) { + + } @else { + + } diff --git a/src/app/graph/components/data-analysis/data-analysis.component.spec.ts b/src/app/graph/components/data-analysis/data-analysis.component.spec.ts index 77133d8..da5fb86 100644 --- a/src/app/graph/components/data-analysis/data-analysis.component.spec.ts +++ b/src/app/graph/components/data-analysis/data-analysis.component.spec.ts @@ -38,6 +38,7 @@ describe('DataAnalysisComponent', () => { mockLoadGraphService = jasmine.createSpyObj([ 'getAllNodes', 'getNodeInfo', + 'getEdgeInfo', 'nodesData$', 'getGraph', ]); @@ -79,7 +80,7 @@ describe('DataAnalysisComponent', () => { fixture.detectChanges(); spyOn(document, 'getElementById').and.returnValue({ - dataset: { nodeid: '123' }, + dataset: { nodeid: '123', edgeid: '123' }, } as unknown as HTMLElement); }); @@ -93,13 +94,24 @@ describe('DataAnalysisComponent', () => { expect(component.isDarkMode).toBeTrue(); }); - it('getInfo SHOULD show info WHEN get data successfully', () => { + it('getNodeInfo SHOULD show info WHEN get data successfully', () => { // Arrange mockLoadGraphService.getNodeInfo .withArgs('123') .and.returnValue(of({ name: 'mamad', id: 1 })); // Act - component.getInfo(); + component.getNodeInfo(); + // Assert + expect(mockMatDialog.open).toHaveBeenCalled(); + }); + + it('getEdgeInfo SHOULD show info WHEN get data successfully', () => { + // Arrange + mockLoadGraphService.getEdgeInfo + .withArgs('123') + .and.returnValue(of({ name: 'mamad', id: 1 })); + // Act + component.getEdgeInfo(); // Assert expect(mockMatDialog.open).toHaveBeenCalled(); }); diff --git a/src/app/graph/components/data-analysis/data-analysis.component.ts b/src/app/graph/components/data-analysis/data-analysis.component.ts index 3b7390c..45a120d 100644 --- a/src/app/graph/components/data-analysis/data-analysis.component.ts +++ b/src/app/graph/components/data-analysis/data-analysis.component.ts @@ -53,6 +53,7 @@ export class DataAnalysisComponent implements AfterViewInit { isDarkMode = false; nodeColor!: string; selectedNodeColor!: string; + isNode!: boolean; nodes = new DataSet([] as unknown as Node[]); edges = new DataSet([] as Edge[]); @@ -92,6 +93,7 @@ export class DataAnalysisComponent implements AfterViewInit { const edgeId = this.networkInstance.getEdgeAt(params.pointer.DOM); if (nodeId !== undefined) { + this.isNode = true; this.menuTrigger.nativeElement.style.left = params.event.clientX + 'px'; this.menuTrigger.nativeElement.style.top = params.event.clientY + 'px'; this.menuTrigger.nativeElement.style.position = 'fixed'; @@ -103,14 +105,20 @@ export class DataAnalysisComponent implements AfterViewInit { ) as HTMLElement; rightClickNodeInfoElem.dataset['nodeid'] = nodeId.toString(); - - // Custom logic for node right-click } else if (edgeId !== undefined) { + this.isNode = false; console.log('Right-clicked edge:', edgeId); - // Custom logic for edge right-click - } else { - console.log('Right-clicked on empty space'); - // Custom logic for right-click on empty space + this.menuTrigger.nativeElement.style.left = params.event.clientX + 'px'; + this.menuTrigger.nativeElement.style.top = params.event.clientY + 'px'; + this.menuTrigger.nativeElement.style.position = 'fixed'; + this.matMenuTrigger.openMenu(); + + this.changeDetector.detectChanges(); + const rightClickNodeInfoElem = document.getElementById( + 'right-click-node-info' + ) as HTMLElement; + + rightClickNodeInfoElem.dataset['edgeid'] = edgeId.toString(); } }); @@ -180,7 +188,7 @@ export class DataAnalysisComponent implements AfterViewInit { console.log('edge click: ', edgeId); } - getInfo() { + getNodeInfo() { const account = ( document.getElementById('right-click-node-info') as HTMLElement ).dataset['nodeid']; @@ -204,6 +212,30 @@ export class DataAnalysisComponent implements AfterViewInit { }); } + getEdgeInfo() { + const account = ( + document.getElementById('right-click-node-info') as HTMLElement + ).dataset['edgeid']; + + this.loadGraphService.getEdgeInfo(account!).subscribe({ + next: (data) => { + this.dialog.open(InfoDialogComponent, { + width: '105rem', + data, + }); + this.loadingService.setLoading(false); + }, + error: (error) => { + this._snackBar.openFromComponent(DangerSuccessNotificationComponent, { + data: error.error.message, + panelClass: ['notification-class-danger'], + duration: 2000, + }); + this.loadingService.setLoading(false); + }, + }); + } + showAsGraph(account: Account) { this.nodes.add({ id: account.id, label: account.entityName }); } diff --git a/src/app/graph/components/data-analysis/graph-options.ts b/src/app/graph/components/data-analysis/graph-options.ts index 76eaf5e..0db2e02 100644 --- a/src/app/graph/components/data-analysis/graph-options.ts +++ b/src/app/graph/components/data-analysis/graph-options.ts @@ -16,17 +16,15 @@ export function getOptions() { const svgDataUrl = 'data:image/svg+xml;charset=UTF-8,' + - encodeURIComponent(` - - - + encodeURIComponent(` + + + `); return { physics: false, edges: { - width: 0.7, smooth: { enabled: false, type: 'vertical', roundness: 0 }, arrows: { to: { @@ -40,13 +38,11 @@ export function getOptions() { color: textColor, strokeWidth: 0, face: 'MyCustomFont', - size: 6, }, }, nodes: { shape: 'image', image: svgDataUrl, - size: 8, color: { background: labelColor, border: labelBorder, @@ -59,7 +55,6 @@ export function getOptions() { align: 'center', color: textColor, face: 'MyCustomFont', - size: 6, }, }, } as Options; @@ -69,10 +64,9 @@ export function getSvg(color: string, borderColor = color) { return ( 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(` - - - + + + `) ); } diff --git a/src/app/graph/services/load-graph/load-graph.service.ts b/src/app/graph/services/load-graph/load-graph.service.ts index c3eea22..d5ef54f 100644 --- a/src/app/graph/services/load-graph/load-graph.service.ts +++ b/src/app/graph/services/load-graph/load-graph.service.ts @@ -39,7 +39,17 @@ export class LoadGraphService { getNodeInfo(headerUniqueId: string) { this.loadingService.setLoading(true); return this.http.get( - `${this.apiUrl}/nodes/${headerUniqueId}/attributes?id=${headerUniqueId}`, + `${this.apiUrl}/nodes/${headerUniqueId}/attributes`, + { + withCredentials: true, + } + ); + } + + getEdgeInfo(headerUniqueId: string) { + this.loadingService.setLoading(true); + return this.http.get( + `${this.apiUrl}/edges/${headerUniqueId}/attributes?id=${headerUniqueId}`, { withCredentials: true, } diff --git a/src/app/guards/auth/auth.guard.spec.ts b/src/app/guards/auth/auth.guard.spec.ts index ce5c1ef..3a6b616 100644 --- a/src/app/guards/auth/auth.guard.spec.ts +++ b/src/app/guards/auth/auth.guard.spec.ts @@ -48,7 +48,7 @@ describe('AuthGuard', () => { | UrlTree | Observable | Promise, - done: DoneFn, + done: DoneFn ) { if (result instanceof Observable) { result.subscribe((value) => { @@ -78,25 +78,7 @@ describe('AuthGuard', () => { authService.getPermissions.and.returnValue(of(mockPermissions)); - const result = guard.canActivate(route); - - handleResult(result, done); - }); - - it('should redirect to /dashboard if the user has permissions but tries to access another page', (done) => { - const mockPermissions = { - permission: ['viewDashboard'], - firstName: 'John', - lastName: 'Doe', - image: 'some-image-url', - }; - - route.url = [createMockUrlSegment('otherPage')]; - - authService.getPermissions.and.returnValue(of(mockPermissions)); - router.parseUrl.and.returnValue('/dashboard' as unknown as UrlTree); - - const result = guard.canActivate(route); + const result = guard.canActivate(); handleResult(result, done); }); @@ -114,7 +96,7 @@ describe('AuthGuard', () => { authService.getPermissions.and.returnValue(of(mockPermissions)); router.parseUrl.and.returnValue('/login' as unknown as UrlTree); - const result = guard.canActivate(route); + const result = guard.canActivate(); handleResult(result, done); }); @@ -123,7 +105,7 @@ describe('AuthGuard', () => { route.url = [createMockUrlSegment('dashboard')]; authService.getPermissions.and.returnValue(throwError('Error')); - const result = guard.canActivate(route); + const result = guard.canActivate(); if (result instanceof Observable) { result.subscribe((value) => { diff --git a/src/app/guards/auth/auth.guard.ts b/src/app/guards/auth/auth.guard.ts index 9a603e2..0b183d4 100644 --- a/src/app/guards/auth/auth.guard.ts +++ b/src/app/guards/auth/auth.guard.ts @@ -1,32 +1,16 @@ import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - CanActivate, - CanActivateFn, - Router, - UrlTree, -} from '@angular/router'; +import { CanActivate, Router, UrlTree } from '@angular/router'; import { catchError, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { AuthService } from '../../user/services/auth/auth.service'; -export const authGuard: CanActivateFn = (route, state) => { - if (route && state) return true; - return true; -}; - @Injectable({ providedIn: 'root', }) export class AuthGuard implements CanActivate { - constructor( - private authService: AuthService, - private router: Router, - ) {} + constructor(private authService: AuthService, private router: Router) {} - canActivate( - route: ActivatedRouteSnapshot, - ): + canActivate(): | Observable | Promise | boolean @@ -34,21 +18,15 @@ export class AuthGuard implements CanActivate { return this.authService.getPermissions().pipe( map((permissions) => { if (permissions?.permission.length) { - if (route.url[0].path !== 'dashboard') { - return this.router.parseUrl('/dashboard'); - } return true; } else { - if (route.url[0].path !== 'login') { - return this.router.parseUrl('/login'); - } - return true; + return this.router.parseUrl('/login'); } }), catchError(() => { this.router.navigate(['/login']); return [false]; - }), + }) ); } } diff --git a/src/app/guards/auth/login.guard.spec.ts b/src/app/guards/auth/login.guard.spec.ts new file mode 100644 index 0000000..eefbd1e --- /dev/null +++ b/src/app/guards/auth/login.guard.spec.ts @@ -0,0 +1,104 @@ +import { TestBed } from '@angular/core/testing'; +import { + ActivatedRouteSnapshot, + Router, + UrlSegment, + UrlTree, +} from '@angular/router'; +import { Observable, of } from 'rxjs'; +import { LoginGuard } from './login.guard'; +import { AuthService } from '../../user/services/auth/auth.service'; + +describe('AuthGuard', () => { + let guard: LoginGuard; + let authService: jasmine.SpyObj; + let router: jasmine.SpyObj; + let route: ActivatedRouteSnapshot; + // let state: RouterStateSnapshot; + + beforeEach(() => { + const authServiceSpy = jasmine.createSpyObj('AuthService', [ + 'getPermissions', + ]); + const routerSpy = jasmine.createSpyObj('Router', ['navigate', 'parseUrl']); + + TestBed.configureTestingModule({ + providers: [ + LoginGuard, + { provide: AuthService, useValue: authServiceSpy }, + { provide: Router, useValue: routerSpy }, + ], + }); + + guard = TestBed.inject(LoginGuard); + authService = TestBed.inject(AuthService) as jasmine.SpyObj; + router = TestBed.inject(Router) as jasmine.SpyObj; + + route = {} as ActivatedRouteSnapshot; + // state = {} as RouterStateSnapshot; + }); + + function createMockUrlSegment(path: string): UrlSegment { + return new UrlSegment(path, {}); + } + + function handleResult( + result: + | boolean + | UrlTree + | Observable + | Promise, + done: DoneFn + ) { + if (result instanceof Observable) { + result.subscribe((value) => { + expect(value).toBeTruthy(); + done(); + }); + } else if (result instanceof Promise) { + result.then((value) => { + expect(value).toBeTruthy(); + done(); + }); + } else { + expect(result).toBeTruthy(); + done(); + } + } + + it('should redirect to /dashboard if the user has permissions but tries to access another page', (done) => { + const mockPermissions = { + permission: ['viewDashboard'], + firstName: 'John', + lastName: 'Doe', + image: 'some-image-url', + }; + + route.url = [createMockUrlSegment('otherPage')]; + + authService.getPermissions.and.returnValue(of(mockPermissions)); + router.parseUrl.and.returnValue('/dashboard' as unknown as UrlTree); + + const result = guard.canActivate(); + + handleResult(result, done); + }); + + it('should redirect to /login if the user does not have permissions and tries to access a non-login page', (done) => { + const mockPermissions = { + permission: [], + firstName: 'John', + lastName: 'Doe', + image: 'some-image-url', + }; + + route.url = [createMockUrlSegment('dashboard')]; + + authService.getPermissions.and.returnValue(of(mockPermissions)); + router.parseUrl.and.returnValue('/login' as unknown as UrlTree); + + const result = guard.canActivate(); + + handleResult(result, done); + }); +}); diff --git a/src/app/guards/auth/login.guard.ts b/src/app/guards/auth/login.guard.ts new file mode 100644 index 0000000..f15db38 --- /dev/null +++ b/src/app/guards/auth/login.guard.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@angular/core'; +import { CanActivate, CanActivateFn, Router, UrlTree } from '@angular/router'; +import { catchError, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { AuthService } from '../../user/services/auth/auth.service'; + +export const loginGuard: CanActivateFn = (route, state) => { + if (route && state) return true; + return true; +}; + +@Injectable({ + providedIn: 'root', +}) +export class LoginGuard implements CanActivate { + constructor(private authService: AuthService, private router: Router) {} + + canActivate(): + | Observable + | Promise + | boolean + | UrlTree { + return this.authService.getPermissions().pipe( + map((permissions) => { + if (permissions?.permission.length) { + return this.router.parseUrl('/dashboard'); + } else { + return true; + } + }), + catchError(() => { + return [true]; + }) + ); + } +} diff --git a/src/app/user/components/login/login-form/login-form.component.html b/src/app/user/components/login/login-form/login-form.component.html index 0f35ba7..e184a5b 100644 --- a/src/app/user/components/login/login-form/login-form.component.html +++ b/src/app/user/components/login/login-form/login-form.component.html @@ -15,18 +15,18 @@

Login

placeholder="at least 8 chracters" [(ngModel)]="password" matInput - [type]="hide() ? 'password' : 'text'" + [type]="hide ? 'password' : 'text'" name="password" /> Login [disabled]="isLoading" (click)="loginClick()" > - @if (isLoading) { - Logging in... - } @else { - Login - } + @if (isLoading) { Logging in... } @else { Login }

diff --git a/src/app/user/components/login/login-form/login-form.component.spec.ts b/src/app/user/components/login/login-form/login-form.component.spec.ts index c361565..87e2860 100644 --- a/src/app/user/components/login/login-form/login-form.component.spec.ts +++ b/src/app/user/components/login/login-form/login-form.component.spec.ts @@ -10,12 +10,33 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatButtonModule } from '@angular/material/button'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { Router } from '@angular/router'; +import { AuthService } from '../../../services/auth/auth.service'; +import { of, throwError } from 'rxjs'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { LoadingService } from '../../../../shared/services/loading.service'; +import { DangerSuccessNotificationComponent } from '../../../../shared/components/danger-success-notification/danger-success-notification.component'; describe('LoginFormComponent', () => { let component: LoginFormComponent; let fixture: ComponentFixture; + let mockAuthService: jasmine.SpyObj; + let mockRouter: jasmine.SpyObj; + let mockMatSnackBar: jasmine.SpyObj; + let mockLoadingService: jasmine.SpyObj; + + const loginInfo = { + username: 'mamad', + password: 'M@mad123', + rememberMe: true, + }; beforeEach(async () => { + mockRouter = jasmine.createSpyObj(['navigate']); + mockAuthService = jasmine.createSpyObj(['login']); + mockMatSnackBar = jasmine.createSpyObj(['openFromComponent']); + mockLoadingService = jasmine.createSpyObj(['setLoading']); + await TestBed.configureTestingModule({ declarations: [LoginFormComponent], imports: [ @@ -27,7 +48,14 @@ describe('LoginFormComponent', () => { MatButtonModule, BrowserAnimationsModule, ], - providers: [provideHttpClient(), provideHttpClientTesting()], + providers: [ + provideHttpClient(), + provideHttpClientTesting(), + { provide: AuthService, useValue: mockAuthService }, + { provide: MatSnackBar, useValue: mockMatSnackBar }, + { provide: Router, useValue: mockRouter }, + { provide: LoadingService, useValue: mockLoadingService }, + ], }).compileComponents(); fixture = TestBed.createComponent(LoginFormComponent); @@ -42,10 +70,10 @@ describe('LoginFormComponent', () => { it('should bind username and password inputs', () => { fixture.whenStable().then(() => { const usernameInput = fixture.nativeElement.querySelector( - 'input[name="userName"]', + 'input[name="userName"]' ); const passwordInput = fixture.nativeElement.querySelector( - 'input[name="password"]', + 'input[name="password"]' ); usernameInput.value = 'testUser'; @@ -73,4 +101,66 @@ describe('LoginFormComponent', () => { const button = fixture.nativeElement.querySelector('button[type="submit"]'); expect(button.disabled).toBeTruthy(); }); + + it('SHOULD show message and redirect to dashboard WHEN login successfully', () => { + mockAuthService.login.and.returnValue(of()); + component.username = loginInfo.username; + component.password = loginInfo.password; + component.checked = loginInfo.rememberMe; + + component.loginClick(); + + expect(mockAuthService.login).toHaveBeenCalledWith({ + username: loginInfo.username, + password: loginInfo.password, + rememberMe: loginInfo.rememberMe, + }); + + // expect(mockMatSnackBar.openFromComponent).toHaveBeenCalledWith( + // DangerSuccessNotificationComponent, + // { + // data: 'Logged in successfully.', + // panelClass: ['notification-class-success'], + // duration: 2000, + // } + // ); + // expect(mockRouter.navigate).toHaveBeenCalledWith(['/dashboard']); + }); + + it('SHOULD give error WHEN login fails', () => { + const mockError = { error: { message: 'Update failed' } }; + + mockAuthService.login.and.returnValue(throwError(() => mockError)); + + component.username = loginInfo.username; + component.password = loginInfo.password; + component.checked = loginInfo.rememberMe; + + component.loginClick(); + + expect(mockAuthService.login).toHaveBeenCalledWith({ + username: loginInfo.username, + password: loginInfo.password, + rememberMe: loginInfo.rememberMe, + }); + + expect(mockMatSnackBar.openFromComponent).toHaveBeenCalledWith( + DangerSuccessNotificationComponent, + { + data: mockError.error.message, + panelClass: ['notification-class-danger'], + duration: 2000, + } + ); + expect(mockLoadingService.setLoading).toHaveBeenCalledWith(false); + }); + + it('should toggle hide property and prevent event propagation', () => { + // Arrange + component.hide = false; + // Act + component.hidePassClick(); + // Assert + expect(component.hide).toBeTrue(); + }); }); diff --git a/src/app/user/components/login/login-form/login-form.component.ts b/src/app/user/components/login/login-form/login-form.component.ts index d55eeed..30080fe 100644 --- a/src/app/user/components/login/login-form/login-form.component.ts +++ b/src/app/user/components/login/login-form/login-form.component.ts @@ -1,4 +1,4 @@ -import { Component, signal } from '@angular/core'; +import { Component } from '@angular/core'; import { LoginRequest } from '../../../models/User'; import { DangerSuccessNotificationComponent } from '../../../../shared/components/danger-success-notification/danger-success-notification.component'; import { AuthService } from '../../../services/auth/auth.service'; @@ -12,7 +12,7 @@ import { LoadingService } from '../../../../shared/services/loading.service'; styleUrl: './login-form.component.scss', }) export class LoginFormComponent { - hide = signal(true); + hide = true; checked = false; username = ''; password = ''; @@ -22,7 +22,7 @@ export class LoginFormComponent { private authService: AuthService, private router: Router, private _snackBar: MatSnackBar, - private loadingService: LoadingService, + private loadingService: LoadingService ) { this.loadingService.setLoading(false); } @@ -58,8 +58,7 @@ export class LoginFormComponent { }); } - hidePassClick(event: MouseEvent) { - this.hide.set(!this.hide()); - event.stopPropagation(); + hidePassClick() { + this.hide = !this.hide; } } diff --git a/src/app/user/services/auth/auth.service.ts b/src/app/user/services/auth/auth.service.ts index 2f76c9a..77223d8 100644 --- a/src/app/user/services/auth/auth.service.ts +++ b/src/app/user/services/auth/auth.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -import { BehaviorSubject, Observable, tap } from 'rxjs'; +import { BehaviorSubject, tap } from 'rxjs'; import { LoginRequest, UserPermissions } from '../../models/User'; import { environment } from '../../../../../api-config/api-url'; import { LoadingService } from '../../../shared/services/loading.service'; @@ -18,9 +18,9 @@ export class AuthService { private loadingService: LoadingService ) {} - login(loginRequest: LoginRequest): Observable { + login(loginRequest: LoginRequest) { this.loadingService.setLoading(true); - return this.http.post(this.apiUrl + '/login', loginRequest, { + return this.http.post(this.apiUrl + '/login', loginRequest, { withCredentials: true, }); } @@ -34,9 +34,7 @@ export class AuthService { }) .pipe( tap((response) => { - if (response.firstName) { - this.permissions.next(response); - } + this.permissions.next(response); }) ); } From 3d4e7fed78b6169c103f97e462a4a7d41a555a29 Mon Sep 17 00:00:00 2001 From: Mojtaba Erfan Rad Date: Mon, 9 Sep 2024 15:33:39 +0330 Subject: [PATCH 2/2] fix(search-nodes): node now have filter based on category (#49) --- .../category/category.component.html | 9 +++++++ .../search-nodes/search-nodes.component.html | 15 ++++++++++- .../search-nodes/search-nodes.component.scss | 4 +++ .../search-nodes/search-nodes.component.ts | 26 ++++++++++++++++++- src/app/graph/model/Category.ts | 5 ++++ .../services/category/category.service.ts | 12 ++++++++- .../assign-file/assign-file.component.html | 9 +++++++ .../manage-users/manage-users.component.html | 9 +++++++ 8 files changed, 86 insertions(+), 3 deletions(-) diff --git a/src/app/graph/components/category/category.component.html b/src/app/graph/components/category/category.component.html index 4f7f658..b92a93f 100644 --- a/src/app/graph/components/category/category.component.html +++ b/src/app/graph/components/category/category.component.html @@ -22,6 +22,15 @@ } + + + diff --git a/src/app/graph/components/data-analysis/search-nodes/search-nodes.component.html b/src/app/graph/components/data-analysis/search-nodes/search-nodes.component.html index 95e0910..7f3c5c2 100644 --- a/src/app/graph/components/data-analysis/search-nodes/search-nodes.component.html +++ b/src/app/graph/components/data-analysis/search-nodes/search-nodes.component.html @@ -22,6 +22,19 @@ search + + Category + + All categories + @for (category of allCategories; track category) { + {{ category.name }} + } + +
@for (account of accounts; track $index) {
- } + }@empty {

No nodes Found!

} (); nodeName$!: Observable; + category = ''; constructor( private _snackBar: MatSnackBar, private loadGraphService: LoadGraphService, private dialog: MatDialog, - private loadingService: LoadingService + private loadingService: LoadingService, + private categoryService: CategoryService ) {} ngOnInit(): void { @@ -73,6 +78,21 @@ export class SearchNodesComponent implements OnInit { }, }); }); + + this.categoryService.getAllCategories().subscribe({ + next: (data) => { + this.allCategories = data; + this.loadingService.setLoading(false); + }, + error: (error) => { + this._snackBar.openFromComponent(DangerSuccessNotificationComponent, { + data: error.error.message, + panelClass: ['notification-class-danger'], + duration: 2000, + }); + this.loadingService.setLoading(false); + }, + }); } searchNodes() { @@ -113,4 +133,8 @@ export class SearchNodesComponent implements OnInit { this.length = e.length; this.loadGraphService.getAllNodes(e.pageIndex); } + + categoryChanged() { + this.loadGraphService.getAllNodes(0, this.category); + } } diff --git a/src/app/graph/model/Category.ts b/src/app/graph/model/Category.ts index 4141d51..7ec01eb 100644 --- a/src/app/graph/model/Category.ts +++ b/src/app/graph/model/Category.ts @@ -9,3 +9,8 @@ export interface GetCategoriesResponse { pageIndex: number; totalCount: number; } + +export interface AllCategories { + id: number; + name: string; +} diff --git a/src/app/graph/services/category/category.service.ts b/src/app/graph/services/category/category.service.ts index 2e5dc35..15f102c 100644 --- a/src/app/graph/services/category/category.service.ts +++ b/src/app/graph/services/category/category.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { environment } from '../../../../../api-config/api-url'; import { Subject } from 'rxjs'; -import { GetCategoriesResponse } from '../../model/Category'; +import { AllCategories, GetCategoriesResponse } from '../../model/Category'; import { LoadingService } from '../../../shared/services/loading.service'; @Injectable({ @@ -66,4 +66,14 @@ export class CategoryService { withCredentials: true, }); } + + getAllCategories() { + this.loadingService.setLoading(true); + return this.httpClient.get( + this.apiUrl + `/all-category-without-pagination`, + { + withCredentials: true, + } + ); + } } diff --git a/src/app/user/components/dashboard/assign-file/assign-file.component.html b/src/app/user/components/dashboard/assign-file/assign-file.component.html index de87ca1..e078875 100644 --- a/src/app/user/components/dashboard/assign-file/assign-file.component.html +++ b/src/app/user/components/dashboard/assign-file/assign-file.component.html @@ -1,6 +1,15 @@
+ No category Found! +
id {{ element.id }}
+ + + diff --git a/src/app/user/components/dashboard/manage-users/manage-users.component.html b/src/app/user/components/dashboard/manage-users/manage-users.component.html index ac7a652..758508c 100644 --- a/src/app/user/components/dashboard/manage-users/manage-users.component.html +++ b/src/app/user/components/dashboard/manage-users/manage-users.component.html @@ -5,6 +5,15 @@ person_addAdd new user
+ No file found! +
Name {{ element.fileName }}
+ + +
+ No user found! +
Username {{ element.userName }}