commit 5849958bbeb0e2d8eea6eaeed825409ed6e4e029
Author: Ondřej Váně <onry.vane@gmail.com>
Date:   Sun Apr 12 17:20:58 2020 +0200

    Re #7816: Logování ve FE
    
    - přidána service pro logování
    - implementovány log levely: INFO, WARN, DEBUG, ERROR
    - návod na wiki v záložce Nástroje -> logování FE

diff --git a/fe/fulltextsearch/src/app/app.module.ts b/fe/fulltextsearch/src/app/app.module.ts
index 47ab504..f03a4e9 100644
--- a/fe/fulltextsearch/src/app/app.module.ts
+++ b/fe/fulltextsearch/src/app/app.module.ts
@@ -28,6 +28,7 @@ import { MainNavComponent } from './components/layout/main-nav/main-nav.componen
 import { LayoutModule } from '@angular/cdk/layout';
 import { MatSidenavModule } from '@angular/material/sidenav';
 import { MatListModule } from '@angular/material/list';
+import { DatePipe } from '@angular/common';
 
 @NgModule({
   declarations: [
@@ -62,7 +63,7 @@ import { MatListModule } from '@angular/material/list';
     MatSidenavModule,
     MatListModule
   ],
-  providers: [],
+  providers: [DatePipe],
   bootstrap: [AppComponent]
 })
 export class AppModule { }
diff --git a/fe/fulltextsearch/src/app/components/pages/browse/browse.component.ts b/fe/fulltextsearch/src/app/components/pages/browse/browse.component.ts
index 14a2a7e..bc33ffd 100644
--- a/fe/fulltextsearch/src/app/components/pages/browse/browse.component.ts
+++ b/fe/fulltextsearch/src/app/components/pages/browse/browse.component.ts
@@ -1,6 +1,7 @@
 import { Component, OnInit } from '@angular/core';
-import {DocumentResponse} from '../../../model/DocumentResponse';
-import {QueryService} from '../../../services/query.service';
+import { DocumentResponse } from '../../../model/DocumentResponse';
+import { QueryService } from '../../../services/query/query.service';
+import { LoggerService } from '../../../services/logger/logger.service';
 
 @Component({
   selector: 'app-browse',
@@ -10,9 +11,10 @@ import {QueryService} from '../../../services/query.service';
 export class BrowseComponent implements OnInit {
   documents: DocumentResponse[] = null;
 
-  constructor( private queryService: QueryService ) { }
+  constructor( private queryService: QueryService, private LOGGER: LoggerService ) { }
 
   ngOnInit(): void {
+    this.LOGGER.info('browse component on init');
     this.queryService.getDocuments().subscribe( documents => {
       this.documents = documents;
     });
diff --git a/fe/fulltextsearch/src/app/components/pages/browse/table/table.component.ts b/fe/fulltextsearch/src/app/components/pages/browse/table/table.component.ts
index fe826eb..17f360f 100644
--- a/fe/fulltextsearch/src/app/components/pages/browse/table/table.component.ts
+++ b/fe/fulltextsearch/src/app/components/pages/browse/table/table.component.ts
@@ -4,7 +4,7 @@ import { MatSort } from '@angular/material/sort';
 import { MatTable } from '@angular/material/table';
 import { TableDataSource } from './table-datasource';
 import { DocumentResponse } from '../../../../model/DocumentResponse';
-import {QueryService} from '../../../../services/query.service';
+import { QueryService } from '../../../../services/query/query.service';
 
 @Component({
   selector: 'app-table-browse',
diff --git a/fe/fulltextsearch/src/app/components/pages/help/help.component.ts b/fe/fulltextsearch/src/app/components/pages/help/help.component.ts
index ae7d80f..1ef7c3b 100644
--- a/fe/fulltextsearch/src/app/components/pages/help/help.component.ts
+++ b/fe/fulltextsearch/src/app/components/pages/help/help.component.ts
@@ -1,4 +1,5 @@
 import { Component, OnInit } from '@angular/core';
+import { LoggerService } from '../../../services/logger/logger.service';
 
 @Component({
   selector: 'app-help',
@@ -7,9 +8,10 @@ import { Component, OnInit } from '@angular/core';
 })
 export class HelpComponent implements OnInit {
 
-  constructor() { }
+  constructor( private LOGGER: LoggerService ) { }
 
   ngOnInit(): void {
+    this.LOGGER.info('help component on init');
   }
 
 }
diff --git a/fe/fulltextsearch/src/app/components/pages/home/home.component.ts b/fe/fulltextsearch/src/app/components/pages/home/home.component.ts
index 41944fa..77f5c78 100644
--- a/fe/fulltextsearch/src/app/components/pages/home/home.component.ts
+++ b/fe/fulltextsearch/src/app/components/pages/home/home.component.ts
@@ -1,4 +1,5 @@
 import { Component, OnInit } from '@angular/core';
+import { LoggerService } from '../../../services/logger/logger.service';
 
 @Component({
   selector: 'app-home',
@@ -7,7 +8,10 @@ import { Component, OnInit } from '@angular/core';
 })
 export class HomeComponent implements OnInit {
 
+  constructor( private LOGGER: LoggerService ) {}
+
   ngOnInit() {
+    this.LOGGER.info('home component on init');
   }
 
 }
diff --git a/fe/fulltextsearch/src/app/components/pages/search/search.component.ts b/fe/fulltextsearch/src/app/components/pages/search/search.component.ts
index e80e70a..f124774 100644
--- a/fe/fulltextsearch/src/app/components/pages/search/search.component.ts
+++ b/fe/fulltextsearch/src/app/components/pages/search/search.component.ts
@@ -1,4 +1,5 @@
 import { Component, OnInit } from '@angular/core';
+import { LoggerService } from '../../../services/logger/logger.service';
 
 @Component({
   selector: 'app-search',
@@ -7,9 +8,10 @@ import { Component, OnInit } from '@angular/core';
 })
 export class SearchComponent implements OnInit {
 
-  constructor() { }
+  constructor( private LOGGER: LoggerService ) { }
 
   ngOnInit(): void {
+    this.LOGGER.info('search component on init');
   }
 
 }
diff --git a/fe/fulltextsearch/src/app/components/pages/upload/upload.component.ts b/fe/fulltextsearch/src/app/components/pages/upload/upload.component.ts
index 6dfda07..aac3e20 100644
--- a/fe/fulltextsearch/src/app/components/pages/upload/upload.component.ts
+++ b/fe/fulltextsearch/src/app/components/pages/upload/upload.component.ts
@@ -1,7 +1,8 @@
 import { Component, OnInit } from '@angular/core';
-import {QueryService} from '../../../services/query.service';
-import {HttpEventType} from '@angular/common/http';
+import { QueryService } from '../../../services/query/query.service';
+import { HttpEventType } from '@angular/common/http';
 import Utils from '../../../../Utils';
+import { LoggerService } from '../../../services/logger/logger.service';
 
 @Component({
   selector: 'app-upload',
@@ -13,9 +14,10 @@ export class UploadComponent implements OnInit {
   resultMessage: string = null;
   progress = 0;
 
-  constructor( private queryService: QueryService ) { }
+  constructor( private queryService: QueryService, private LOGGER: LoggerService ) { }
 
   ngOnInit(): void {
+    this.LOGGER.info('upload component on init');
   }
 
   onFilesSelected(event) {
@@ -25,10 +27,10 @@ export class UploadComponent implements OnInit {
   }
 
   onUpload() {
-    console.log(this.selectedFiles);
+    this.LOGGER.info('Selected files: ' + this.selectedFiles);
 
     if (!this.checkFiles(this.selectedFiles)) {
-      console.log('Files are not valid');
+      this.LOGGER.warn('Files are not valid');
       return;
     }
 
@@ -38,6 +40,7 @@ export class UploadComponent implements OnInit {
       } else if (event.type === HttpEventType.Response ) {
         this.resultMessage = 'Successfully uploaded ' + this.selectedFiles.length + ' files';
         this.selectedFiles = null;
+        this.LOGGER.info('Files successfully uploaded');
       }
     });
   }
diff --git a/fe/fulltextsearch/src/app/components/shared-components/search-box/search-box.component.ts b/fe/fulltextsearch/src/app/components/shared-components/search-box/search-box.component.ts
index de4448e..7112664 100644
--- a/fe/fulltextsearch/src/app/components/shared-components/search-box/search-box.component.ts
+++ b/fe/fulltextsearch/src/app/components/shared-components/search-box/search-box.component.ts
@@ -1,6 +1,6 @@
 import { Component, OnInit } from '@angular/core';
 import { SearchRequest } from '../../../model/SearchRequest';
-import { QueryService } from '../../../services/query.service';
+import { QueryService } from '../../../services/query/query.service';
 import { Router } from '@angular/router';
 
 @Component({
diff --git a/fe/fulltextsearch/src/app/services/logger/logger.service.spec.ts b/fe/fulltextsearch/src/app/services/logger/logger.service.spec.ts
new file mode 100644
index 0000000..627aba4
--- /dev/null
+++ b/fe/fulltextsearch/src/app/services/logger/logger.service.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { LoggerService } from './logger.service';
+
+describe('LoggerService', () => {
+  let service: LoggerService;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+    service = TestBed.inject(LoggerService);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/fe/fulltextsearch/src/app/services/logger/logger.service.ts b/fe/fulltextsearch/src/app/services/logger/logger.service.ts
new file mode 100644
index 0000000..63fcdec
--- /dev/null
+++ b/fe/fulltextsearch/src/app/services/logger/logger.service.ts
@@ -0,0 +1,46 @@
+import { Injectable } from '@angular/core';
+import { DatePipe } from '@angular/common';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class LoggerService {
+
+  // log config
+  private dateFormat = 'yyyy-MM-dd HH:mm:ss.SSS';
+  private DEBUG = 'DEBUG';
+  private DEBUG_STYLE = 'color: ';
+  private INFO = 'INFO';
+  private INFO_STYLE = 'color: #4CCF27';
+  private WARN = 'WARN';
+  private WARN_STYLE = 'color: #FAC837';
+  private ERROR = 'ERROR';
+  private ERROR_STYLE = 'color: #FA3737';
+
+  constructor(public datePipe: DatePipe) {
+  }
+
+  private log(message: any, logLevel, style) {
+    console.log('%c' + this.getFormattedDate() + '\t\t' + logLevel + '\t' + JSON.stringify(message), style);
+  }
+
+  private getFormattedDate(): string {
+    return this.datePipe.transform(new Date(), this.dateFormat);
+  }
+
+  info(message: any) {
+    this.log(message, this.INFO, this.INFO_STYLE);
+  }
+
+  debug(message: any) {
+    this.log(message, this.DEBUG, this.DEBUG_STYLE);
+  }
+
+  warn(message: any) {
+    this.log(message, this.WARN, this.WARN_STYLE);
+  }
+
+  error(message: any) {
+    this.log(message, this.ERROR, this.ERROR_STYLE);
+  }
+}
diff --git a/fe/fulltextsearch/src/app/services/query.service.spec.ts b/fe/fulltextsearch/src/app/services/query.service.spec.ts
deleted file mode 100644
index a484326..0000000
--- a/fe/fulltextsearch/src/app/services/query.service.spec.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { TestBed } from '@angular/core/testing';
-
-import { QueryService } from './query.service';
-import {HttpClientModule} from '@angular/common/http';
-
-describe('QueryService', () => {
-  beforeEach(() => TestBed.configureTestingModule({
-    imports: [HttpClientModule]
-  }));
-
-  it('should be created', () => {
-    const service: QueryService = TestBed.get(QueryService);
-    expect(service).toBeTruthy();
-  });
-});
diff --git a/fe/fulltextsearch/src/app/services/query.service.ts b/fe/fulltextsearch/src/app/services/query.service.ts
deleted file mode 100644
index eb4ef94..0000000
--- a/fe/fulltextsearch/src/app/services/query.service.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import {Injectable} from '@angular/core';
-import {HttpClient, HttpHeaders} from '@angular/common/http';
-import {Observable} from 'rxjs';
-import {DocumentResponse} from '../model/DocumentResponse';
-import {SearchResponse} from '../model/SearchResponse';
-import {SearchRequest} from '../model/SearchRequest';
-
-const httpOptions = {
-  headers: new HttpHeaders( {
-    'Content-Type': 'application/json'
-  })
-};
-
-@Injectable({
-  providedIn: 'root'
-})
-export class QueryService {
-
-  backendUrl = 'http://localhost:8080/';
-  uploadFileUrl = 'upload';
-  documentsUrl = 'documents';
-
-  constructor(private httpClient: HttpClient) {}
-
-  searchPost(searchRequest: SearchRequest): Observable<SearchResponse> {
-    return this.httpClient.post<SearchResponse>(this.backendUrl, searchRequest, httpOptions);
-  }
-
-
-  uploadFile(files: File[]): Observable<any> {
-    const formDate = new FormData();
-    for (const file of files) {
-      formDate.append('file', file);
-    }
-    return this.httpClient.post(this.backendUrl + this.uploadFileUrl, formDate, {
-      reportProgress: true,
-      observe: 'events'
-    });
-  }
-
-  getDocuments(): Observable<DocumentResponse[]> {
-    return this.httpClient.get<DocumentResponse[]>(this.backendUrl + this.documentsUrl, httpOptions);
-  }
-
-
-}
diff --git a/fe/fulltextsearch/src/app/services/query/query.service.spec.ts b/fe/fulltextsearch/src/app/services/query/query.service.spec.ts
new file mode 100644
index 0000000..22585d6
--- /dev/null
+++ b/fe/fulltextsearch/src/app/services/query/query.service.spec.ts
@@ -0,0 +1,15 @@
+import { TestBed } from '@angular/core/testing';
+
+import { QueryService } from './query.service';
+import { HttpClientModule } from '@angular/common/http';
+
+describe('QueryService', () => {
+  beforeEach(() => TestBed.configureTestingModule({
+    imports: [HttpClientModule]
+  }));
+
+  it('should be created', () => {
+    const service: QueryService = TestBed.get(QueryService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/fe/fulltextsearch/src/app/services/query/query.service.ts b/fe/fulltextsearch/src/app/services/query/query.service.ts
new file mode 100644
index 0000000..a71023c
--- /dev/null
+++ b/fe/fulltextsearch/src/app/services/query/query.service.ts
@@ -0,0 +1,46 @@
+import { Injectable } from '@angular/core';
+import { HttpClient, HttpHeaders } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { DocumentResponse } from '../../model/DocumentResponse';
+import { SearchResponse } from '../../model/SearchResponse';
+import { SearchRequest } from '../../model/SearchRequest';
+
+const httpOptions = {
+  headers: new HttpHeaders( {
+    'Content-Type': 'application/json'
+  })
+};
+
+@Injectable({
+  providedIn: 'root'
+})
+export class QueryService {
+
+  backendUrl = 'http://localhost:8080/';
+  uploadFileUrl = 'upload';
+  documentsUrl = 'documents';
+
+  constructor(private httpClient: HttpClient) {}
+
+  searchPost(searchRequest: SearchRequest): Observable<SearchResponse> {
+    return this.httpClient.post<SearchResponse>(this.backendUrl, searchRequest, httpOptions);
+  }
+
+
+  uploadFile(files: File[]): Observable<any> {
+    const formDate = new FormData();
+    for (const file of files) {
+      formDate.append('file', file);
+    }
+    return this.httpClient.post(this.backendUrl + this.uploadFileUrl, formDate, {
+      reportProgress: true,
+      observe: 'events'
+    });
+  }
+
+  getDocuments(): Observable<DocumentResponse[]> {
+    return this.httpClient.get<DocumentResponse[]>(this.backendUrl + this.documentsUrl, httpOptions);
+  }
+
+
+}
