Skip to content

Commit

Permalink
Added logs and error handling for UI
Browse files Browse the repository at this point in the history
  • Loading branch information
Kuldeep-knoldus committed Jul 2, 2024
1 parent c46b016 commit 419f938
Show file tree
Hide file tree
Showing 18 changed files with 535 additions and 120 deletions.
7 changes: 4 additions & 3 deletions blogs-analyzer-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@
"@angular/platform-browser": "^16.0.0",
"@angular/platform-browser-dynamic": "^16.0.0",
"@angular/router": "^16.0.0",
"ag-grid-angular": "^31.3.2",
"ag-grid-community": "^31.3.2",
"ag-grid-angular": "^32.0.0",
"ag-grid-community": "^32.0.0",
"bootstrap": "^5.3.0",
"highcharts": "^11.4.3",
"highcharts-angular": "^4.0.0",
"ngx-logger": "^5.0.12",
"ngx-markdown": "^16.0.0",
"rxjs": "~7.8.0",
"sonarqube-scanner": "^3.0.1",
Expand All @@ -33,7 +34,7 @@
},
"devDependencies": {
"@angular-devkit/build-angular": "^16.0.2",
"@angular/cli": "~16.0.2",
"@angular/cli": "^16.2.14",
"@angular/compiler-cli": "^16.0.0",
"@types/jasmine": "~4.3.0",
"jasmine-core": "~4.6.0",
Expand Down
2 changes: 1 addition & 1 deletion blogs-analyzer-ui/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const routes: Routes = [
},
{path: 'home', component: DashboardComponent},
{path: 'quality-check', component: QualityCheckComponent},
{path: '', redirectTo: '/home', pathMatch: 'full'}, // Default route
{path: '', redirectTo: '/home', pathMatch: 'full'},
{path: '**', redirectTo: '/home'}

];
Expand Down
2 changes: 2 additions & 0 deletions blogs-analyzer-ui/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { MatButtonModule } from "@angular/material/button";
import { ReportComponent } from './report/report.component';
import { HighchartsChartModule } from "highcharts-angular";
import { MarkdownModule } from "ngx-markdown";
import { LoggerModule, NgxLoggerLevel } from "ngx-logger";

@NgModule({
declarations: [
Expand All @@ -43,6 +44,7 @@ import { MarkdownModule } from "ngx-markdown";
MatButtonModule,
HighchartsChartModule,
MarkdownModule.forRoot(),
LoggerModule.forRoot({level: NgxLoggerLevel.DEBUG, serverLogLevel: NgxLoggerLevel.ERROR})
],
providers: [],
bootstrap: [AppComponent]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@
<input placeholder="Blog Title" class="subscribe-input" type="text" [(ngModel)]="searchTitle">
<br>
<button class="search-btn" (click)="searchByTitle()">Search</button>
<div *ngIf="errorMessage && errorContext === 'title'" class="error-message">{{ errorMessage }}</div>
</div>
<div class="subscribe">
<p>Search Blogs by Author Id</p>
<input placeholder="Author Id" class="subscribe-input" type="number" [(ngModel)]="authorId">
<br>
<button class="search-btn" (click)="getBlogByAuthorId()">Search</button>
<div *ngIf="errorMessage && errorContext === 'author'" class="error-message">{{ errorMessage }}</div>
</div>
<div class="subscribe">
<p>Search Blogs by ID</p>
<input placeholder="Blog Id" class="subscribe-input" type="number" [(ngModel)]="blogId">
<br>
<button class="search-btn" (click)="getBlogById()">Search</button>
<div *ngIf="errorMessage && errorContext === 'id'" class="error-message">{{ errorMessage }}</div>
</div>
</div>
<h2 class="mx-4">Existing Blogs</h2>
Expand All @@ -26,4 +29,4 @@ <h2 class="mx-4">Existing Blogs</h2>
<app-tabular-view></app-tabular-view>
</mat-card>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.subscribe {
position: relative;
height: 140px;
height: 180px;
width: 300px;
padding: 10px;
background-color: #FFF;
Expand All @@ -11,18 +11,6 @@
0 1px 1px 0 #9b9b9b;
}

.subscribe:after {
position: absolute;
content: "";
right: -10px;
bottom: 18px;
width: 0;
height: 0;
border-left: 0 solid transparent;
border-right: 10px solid transparent;
border-bottom: 10px solid #1a044e;
}

.subscribe p {
text-align: center;
font-size: 20px;
Expand All @@ -33,7 +21,7 @@

.subscribe input {
position: absolute;
bottom: 30px;
bottom: 60px;
border: none;
border-bottom: 1px solid #d4d4d4;
padding: 10px;
Expand All @@ -60,8 +48,8 @@
font-size: 12px;
font-weight: bold;
letter-spacing: 5px;
right: -10px;
bottom: -20px;
right: 0;
bottom: 10px;
cursor: pointer;
transition: all .25s ease;
box-shadow: -5px 6px 20px 0px rgba(26, 26, 26, 0.4);
Expand All @@ -72,6 +60,14 @@
box-shadow: -5px 6px 20px 0px rgba(88, 88, 88, 0.569);
}

.error-message {
color: red;
font-size: 12px;
position: absolute;
bottom: 0;
left: 10px;
}

.tabular-view-wrapper {
display: inline;
align-items: center;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,31 @@ import { MatCardModule } from "@angular/material/card";
import { NO_ERRORS_SCHEMA } from "@angular/core";
import { BlogService } from "../../../services/blog.service";
import { Router } from "@angular/router";
import { of } from "rxjs";
import { of, throwError } from "rxjs";
import { LoggerModule, NGXLogger, NgxLoggerLevel } from "ngx-logger";

describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
let blogService: jasmine.SpyObj<BlogService>;
let router: jasmine.SpyObj<Router>;
let logger: jasmine.SpyObj<NGXLogger>;

beforeEach(() => {
const blogServiceSpy = jasmine.createSpyObj('BlogService', ['searchPostsByTitle', 'getPostById', 'getPostByAuthorId']);
const routerSpy = jasmine.createSpyObj('Router', ['navigate']);
const loggerSpy = jasmine.createSpyObj('NGXLogger', ['debug', 'error']);

TestBed.configureTestingModule({
declarations: [HomeComponent],
imports: [RouterTestingModule, HttpClientModule, MatCardModule],
imports: [RouterTestingModule, HttpClientModule, MatCardModule, LoggerModule.forRoot({
level: NgxLoggerLevel.DEBUG,
serverLogLevel: NgxLoggerLevel.ERROR
})],
providers: [
{ provide: BlogService, useValue: blogServiceSpy },
{ provide: Router, useValue: routerSpy }
{provide: BlogService, useValue: blogServiceSpy},
{provide: Router, useValue: routerSpy},
{provide: NGXLogger, useValue: loggerSpy}
],
schemas: [NO_ERRORS_SCHEMA]
});
Expand All @@ -33,6 +40,7 @@ describe('HomeComponent', () => {
component = fixture.componentInstance;
blogService = TestBed.inject(BlogService) as jasmine.SpyObj<BlogService>;
router = TestBed.inject(Router) as jasmine.SpyObj<Router>;
logger = TestBed.inject(NGXLogger as any) as jasmine.SpyObj<NGXLogger>;
fixture.detectChanges();
});

Expand All @@ -41,14 +49,14 @@ describe('HomeComponent', () => {
});

it('should call searchPostsByTitle and navigate on searchByTitle', () => {
const mockData = [{ id: 1, title: 'Test Post' }];
const mockData = [{id: 1, title: 'Test Post'}];
blogService.searchPostsByTitle.and.returnValue(of(mockData));

component.searchTitle = 'Test';
component.searchByTitle();

expect(blogService.searchPostsByTitle).toHaveBeenCalledWith('Test');
expect(router.navigate).toHaveBeenCalledWith(['/quality-check'], { state: { data: mockData } });
expect(router.navigate).toHaveBeenCalledWith(['/quality-check'], {state: {data: mockData}});
});

it('should not call searchPostsByTitle if searchTitle is empty', () => {
Expand All @@ -60,14 +68,14 @@ describe('HomeComponent', () => {
});

it('should call getPostById and navigate on getBlogById', () => {
const mockData = { id: 1, title: 'Test Post' };
const mockData = {id: 1, title: 'Test Post'};
blogService.getPostById.and.returnValue(of(mockData));

component.blogId = 1;
component.getBlogById();

expect(blogService.getPostById).toHaveBeenCalledWith(1);
expect(router.navigate).toHaveBeenCalledWith(['/quality-check'], { state: { data: mockData } });
expect(router.navigate).toHaveBeenCalledWith(['/quality-check'], {state: {data: mockData}});
});

it('should not call getPostById if blogId is not set', () => {
Expand All @@ -77,4 +85,84 @@ describe('HomeComponent', () => {
expect(blogService.getPostById).not.toHaveBeenCalled();
expect(router.navigate).not.toHaveBeenCalled();
});

it('should handle empty searchTitle in searchByTitle', () => {
component.searchTitle = '';
component.searchByTitle();

expect(component.errorMessage).toBeNull();
expect(component.errorContext).toBeNull();
expect(blogService.searchPostsByTitle).not.toHaveBeenCalled();
expect(router.navigate).not.toHaveBeenCalled();
});

it('should handle error in searchByTitle', () => {
const mockError = new Error('Search failed');
blogService.searchPostsByTitle.and.returnValue(throwError(mockError));

component.searchTitle = 'Test';
component.searchByTitle();

expect(component.errorMessage).toEqual('Search failed');
expect(component.errorContext).toEqual('title');
expect(router.navigate).not.toHaveBeenCalled();
expect(logger.error).toHaveBeenCalledWith(`Error searching posts by title: ${mockError.message}`);
});

it('should log error when search fails', () => {
const mockError = new Error('Search failed');
blogService.searchPostsByTitle.and.returnValue(throwError(mockError));

component.searchTitle = 'Test';
component.searchByTitle();

expect(logger.error).toHaveBeenCalledWith(`Error searching posts by title: ${mockError.message}`);
});

it('should handle unset authorId in getBlogByAuthorId', () => {
component.authorId = 0;
component.getBlogByAuthorId();

expect(component.errorMessage).toBeNull();
expect(component.errorContext).toBeNull();
expect(blogService.getPostByAuthorId).not.toHaveBeenCalled();
expect(router.navigate).not.toHaveBeenCalled();
});

it('should handle error in getBlogByAuthorId', () => {
const mockError = new Error('Fetch by author failed');
blogService.getPostByAuthorId.and.returnValue(throwError(mockError));

component.authorId = 1;
component.getBlogByAuthorId();

expect(component.errorMessage).toEqual('Fetch by author failed');
expect(component.errorContext).toEqual('author');
expect(router.navigate).not.toHaveBeenCalled();
expect(logger.error).toHaveBeenCalledWith(`Error fetching posts by author ID: ${mockError.message}`);
});

it('should handle unset blogId in getBlogById', () => {
component.blogId = 0;
component.getBlogById();

expect(component.errorMessage).toBeNull();
expect(component.errorContext).toBeNull();
expect(blogService.getPostById).not.toHaveBeenCalled();
expect(router.navigate).not.toHaveBeenCalled();
});

it('should handle error in getBlogById', () => {
const mockError = new Error('Fetch failed');
blogService.getPostById.and.returnValue(throwError(mockError));

component.blogId = 1;
component.getBlogById();

expect(component.errorMessage).toEqual('Fetch failed');
expect(component.errorContext).toEqual('id');
expect(router.navigate).not.toHaveBeenCalled();
expect(logger.error).toHaveBeenCalledWith(`Error fetching post by ID: ${mockError.message}`);
});

});
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Component } from '@angular/core';
import { BlogService } from "../../../services/blog.service";
import { Router } from "@angular/router";
import { BlogService } from '../../../services/blog.service';
import { Router } from '@angular/router';
import { NGXLogger } from 'ngx-logger';

@Component({
selector: 'app-home',
Expand All @@ -11,30 +12,62 @@ export class HomeComponent {
searchTitle!: string;
blogId!: number;
authorId!: number;
errorMessage: string | null = null;
errorContext: 'title' | 'author' | 'id' | null = null;

constructor(private blogService: BlogService, private router: Router) {
constructor(
private blogService: BlogService,
private router: Router,
private logger: NGXLogger) {
}

searchByTitle() {
if (this.searchTitle) {
this.blogService.searchPostsByTitle(this.searchTitle).subscribe((data: any[]) => {
this.router.navigate(['/quality-check'], {state: {data: data}});
this.logger.debug(`Searching posts by title: ${this.searchTitle}`);
this.blogService.searchPostsByTitle(this.searchTitle).subscribe({
next: (data: any[]) => {
this.errorMessage = null;
this.router.navigate(['/quality-check'], {state: {data: data}});
},
error: (error: Error) => {
this.errorContext = 'title';
this.errorMessage = error.message;
this.logger.error(`Error searching posts by title: ${error.message}`);
}
});
}
}

getBlogById() {
if (this.blogId) {
this.blogService.getPostById(this.blogId).subscribe((data: any[]) => {
this.router.navigate(['/quality-check'], {state: {data: data}});
this.logger.debug(`Fetching post by ID: ${this.blogId}`);
this.blogService.getPostById(this.blogId).subscribe({
next: (data: any[]) => {
this.errorMessage = null;
this.router.navigate(['/quality-check'], {state: {data: data}});
},
error: (error: Error) => {
this.errorContext = 'id';
this.errorMessage = error.message;
this.logger.error(`Error fetching post by ID: ${error.message}`);
}
});
}
}

getBlogByAuthorId() {
if (this.authorId) {
this.blogService.getPostByAuthorId(this.authorId).subscribe((data: any[]) => {
this.router.navigate(['/quality-check'], {state: {data: data}});
this.logger.debug(`Fetching posts by author ID: ${this.authorId}`);
this.blogService.getPostByAuthorId(this.authorId).subscribe({
next: (data: any[]) => {
this.errorMessage = null;
this.router.navigate(['/quality-check'], {state: {data: data}});
},
error: (error: Error) => {
this.errorContext = 'author';
this.errorMessage = error.message;
this.logger.error(`Error fetching posts by author ID: ${error.message}`);
}
});
}
}
Expand Down
Loading

0 comments on commit 419f938

Please sign in to comment.