Bladeren bron

feat: implement basic CSV data download

fzadrazil 4 jaren geleden
bovenliggende
commit
7d8a0493fb

+ 1 - 0
.gitignore

@@ -3,3 +3,4 @@
 ################################################################################
 
 /.vs
+/node_modules

File diff suppressed because it is too large
+ 19589 - 1
package-lock.json


+ 1 - 1
package.json

@@ -4,7 +4,7 @@
   "scripts": {
     "ng": "ng",
     "start": "ng serve",
-    "build": "ng build",
+    "build": "ng build --prod",
     "test": "ng test",
     "lint": "ng lint",
     "e2e": "ng e2e"

+ 6 - 4
proxy-config.json

@@ -1,10 +1,12 @@
 {
   "/analytics": {
-    "target": "http://51.15.45.95:9090",
-    "secure": false
+    "target": "https://sensor.lesprojekt.cz",
+    "secure": true,
+    "changeOrigin": true
   },
   "/senslog15": {
-    "target": "http://51.15.45.95:8080",
-    "secure": false
+    "target": "https://sensor.lesprojekt.cz",
+    "secure": true,
+    "changeOrigin": true
   }
 }

+ 1 - 1
src/app/dashboard/components/dashboard.component.ts

@@ -91,7 +91,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
   getUnits() {
     this.dataService.getData().subscribe(data => {
       this.units = data;
-      this.units.forEach(unit => unit.sensors.sort((a, b)  => a.sensorId - b.sensorId));
+      this.units.forEach(unit => unit.sensors.sort((a, b) => a.sensorId - b.sensorId));
     }, err => this.toastService.showError(err.error.message));
   }
 

+ 28 - 0
src/app/shared/api/endpoints/services/observation.service.ts

@@ -1,5 +1,6 @@
 /* tslint:disable */
 /* eslint-disable */
+import { formatDate } from '@angular/common';
 import { Injectable } from '@angular/core';
 import { HttpClient, HttpResponse } from '@angular/common/http';
 import { BaseService } from '../base-service';
@@ -30,6 +31,7 @@ export class ObservationService extends BaseService {
    * Path part for operation getObservation
    */
   static readonly GetObservationPath = '/senslog15/SensorService?Operation=GetObservations';
+  static readonly RestObservationsPath = '/senslog15/rest/observation/';
 
   /**
    * Get observation.
@@ -89,4 +91,30 @@ export class ObservationService extends BaseService {
     );
   }
 
+
+  exportObservations(params: {
+    //group_id: number,
+    sensor_id: number,
+    from?: Date;
+    to?: Date;
+  }): Observable<StrictHttpResponse<string>> {
+    const rb = new RequestBuilder(this.rootUrl, ObservationService.RestObservationsPath + 'export', 'get');
+    if (params) {
+      rb.query('sensor_id', params.sensor_id);
+      rb.query('from_time', formatDate(params.from, 'yyyy-MM-dd', 'en-US'));
+      rb.query('to_time', formatDate(params.to, 'yyyy-MM-dd', 'en-US'));
+      rb.query('style', 'crosstab');
+      rb.query('nullable', false);
+    }
+
+    return this.http.request(rb.build({
+      responseType: 'text',
+      accept: 'text/plain'
+    })).pipe(
+      filter((r: any) => r instanceof HttpResponse),
+      map((r: HttpResponse<any>) => {
+        return r as StrictHttpResponse<string>;
+      })
+    );
+  }
 }

+ 38 - 0
src/app/shared/nav-bar/components/data-download/data-download-popup.component.html

@@ -0,0 +1,38 @@
+<p-dialog [visible]="isVisible" [modal]="true" [closable]="false" [draggable]="false" header="Data download"
+          [baseZIndex]="10000" (onShow)="clearFormArray()" [className]="'popup-form'">
+
+  <form [formGroup]="downloadForm">
+    <div class="input-group form-group">
+      <div class="input-group-prepend">
+        <span class="input-group-text"><i class="fas fa-file-signature"></i></span>
+        <select formControlName="sensor_id" id="sensor_id">
+          <option value="null" disabled>Select sensor</option>
+          <option *ngFor="let s of sensors; let i = index" [value]="sensors[i].sensorId">
+            {{s.sensorName + '   (' + s.sensorId + ')'}}
+          </option>
+        </select>
+      </div>
+    </div>
+    <div class="input-group form-group">
+      <div class="input-group-prepend">
+        <span class="input-group-text"><i class="far fa-calendar-alt"></i>From</span>
+        <p-calendar [monthNavigator]="true" [yearNavigator]="true" yearRange="2000:2021" inputId="navigators" formControlName="from"></p-calendar>
+      </div>
+    </div>
+    <div class="input-group form-group">
+      <div class="input-group-prepend">
+        <span class="input-group-text"><i class="far fa-calendar-alt"></i>To</span>
+        <p-calendar [monthNavigator]="true" [yearNavigator]="true" yearRange="2000:2021" inputId="navigators" formControlName="to"></p-calendar>
+      </div>
+    </div>
+  </form>
+  <div *ngIf="inProgress" class="download-progress">Export in progress<p-progressBar mode="indeterminate" [style]="{'height': '6px'}"></p-progressBar></div>
+  <p-footer>
+    <div class="row">
+      <div class="popup-buttons">
+        <button pButton type="button" label="Close" class="p-button-primary dark" icon="pi pi-times" (click)="close()"></button>
+        <button pButton type="submit" label="Download" class="p-button-primary dark" icon="pi pi-download" (click)="processDownload()" [disabled]="inProgress"></button>
+      </div>
+    </div>
+  </p-footer>
+</p-dialog>

+ 22 - 0
src/app/shared/nav-bar/components/data-download/data-download-popup.component.scss

@@ -0,0 +1,22 @@
+
+::ng-deep .p-dialog-content {
+  min-height: 500px;
+  height: 600px;
+  max-height: 95%;
+}
+
+::ng-deep .p-datepicker-calendar tbody span {
+  background-color: transparent !important;
+}
+
+::ng-deep .input-group-text i {
+  margin-right: 0.5rem;
+}
+
+.download-progress {
+  position: absolute;
+  bottom: 100px;
+  width: 90%;
+  margin-left: 2.5%;
+  text-align: center;
+}

+ 121 - 0
src/app/shared/nav-bar/components/data-download/data-download-popup.component.ts

@@ -0,0 +1,121 @@
+import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
+import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
+import {HttpResponse} from '@angular/common/http';
+import {map} from 'rxjs/operators';
+import {ObservationService} from '../../../api/endpoints/services/observation.service';
+import { ToastService } from '../../../services/toast.service';
+import { SensorsService } from '../../../api/endpoints/services/sensors.service';
+import { DataService } from '../../../api/endpoints/services/data.service';
+import { Sensor } from '../../../api/endpoints/models/sensor';
+import * as moment from 'moment-timezone';
+import { DashboardComponent } from '../../../../dashboard/components/dashboard.component';
+
+@Component({
+  selector: 'app-data-download-popup',
+  templateUrl: './data-download-popup.component.html',
+  styleUrls: ['./data-download-popup.component.scss']
+})
+export class DataDownloadPopupComponent implements OnInit {
+
+  downloadForm: FormGroup;
+  items: FormArray;
+  dateFrom: Date = moment().hour(0).minutes(0).subtract(7, 'days').toDate();
+  dateTo: Date = moment().toDate();
+
+  inProgress: Boolean = false;
+  @Input() isVisible;
+  @Output() isVisibleChange: EventEmitter<boolean> = new EventEmitter<boolean>();
+  @Input() sensors: Sensor[];
+
+  constructor(
+    private formBuilder: FormBuilder,
+    private observationService: ObservationService,
+    private dataService: DataService,
+    private sensorsService: SensorsService,
+    private toastService: ToastService
+  ) {
+    this.initForm();
+  }
+
+  ngOnInit(): void {
+  }
+
+  close() {
+    this.isVisibleChange.emit(false);
+  }
+
+  initForm() {
+    this.downloadForm = this.formBuilder.group({
+      from: [this.dateFrom, Validators.required],
+      to: [this.dateTo, Validators.required],
+      sensor_id: ['', Validators.required]
+    });
+
+    this.dataService.getData().subscribe(data => {
+      if (data && data.length > 0) {
+        let firstUnitId: number = data[0].unit.unitId;
+        this.sensorsService.getUnitSensors({ unit_id: firstUnitId }).subscribe(sens => {
+          if (sens) {
+            this.sensors = sens;
+          }
+        });
+      }
+    }, err => this.toastService.showError(err.error.message));
+  }
+
+
+  /**
+   * Clear form
+   */
+  clearFormArray() {
+    const frmArray = this.downloadForm?.get('sensors') as FormArray;
+    if (frmArray) {
+      frmArray.clear();
+    }
+    this.downloadForm.reset();
+  }
+
+  /**
+   * Insert unit with sensor and position if form valid
+   */
+  processDownload() {
+    if (this.downloadForm.valid) {
+
+      this.inProgress = true;
+      this.observationService.exportObservations(this.downloadForm.value).pipe(
+        map((response: HttpResponse<any>) => {
+          this.inProgress = false;
+          if (response.status === 200) {
+            console.log('Export successful');
+            this.saveAsFile(response.body, this.downloadForm.value.sensor_id + '.csv');
+          } else {
+            this.toastService.showError('Data download caused error!');
+          }
+        })
+      ).toPromise().then().catch(err => {
+        this.inProgress = false;
+        this.toastService.showError(err.error.message)
+      });
+    }
+  }
+
+  saveAsFile(data, filename) {
+    let file = new Blob([data], { type: 'text/plain' });
+    let evt = document.createEvent('MouseEvents');
+    let link = document.createElement('a');
+
+    if (window.navigator && window.navigator.msSaveOrOpenBlob) {
+      window.navigator.msSaveOrOpenBlob(file, filename);
+    }
+    else {
+      var e = document.createEvent('MouseEvents'),
+        a = document.createElement('a');
+
+      a.download = filename;
+      a.href = window.URL.createObjectURL(file);
+      a.dataset.downloadurl = ['text/plain', a.download, a.href].join(':');
+      e.initEvent('click', true, false);
+      a.dispatchEvent(e);
+    }
+  }
+}

+ 6 - 2
src/app/shared/nav-bar/components/nav-bar.component.html

@@ -12,15 +12,18 @@
       </a>
       <div class="collapse navbar-collapse" id="navbarNav">
         <ul class="navbar-nav left">
-          <li class="nav-item">
+          <!--<li class="nav-item">
             <a class="nav-link" [routerLink]="['/dashboard']"><h2>Dashboard</h2></a>
-          </li>
+          </li>-->
           <li *ngIf="loggedUser?.userInfo?.rightsId == 0" class="nav-item">
             <a class="nav-link" id="addUser" (click)="addUser()"><h2><i class="fas fa-user-plus"></i>&nbsp;Add user</h2></a>
           </li>
           <li *ngIf="loggedUser?.userInfo?.rightsId == 0 || loggedUser?.userInfo?.rightsId == 1" class="nav-item">
             <a class="nav-link" id="addUnit" (click)="insertUnitPopup()"><h2><i class="fas fa-folder-plus"></i>&nbsp;Add unit</h2></a>
           </li>
+          <li *ngIf="loggedUser?.userInfo?.rightsId == 0 || loggedUser?.userInfo?.rightsId == 1" class="nav-item">
+            <a class="nav-link" id="downloadData" (click)="downloadData()"><h2><i class="fas fa-download"></i>&nbsp;Data download</h2></a>
+          </li>
         </ul>
         <a class="navbar-brand desktop" href="/">
           <img src="/assets/images/senslog-logo.svg" alt="Logo SensLog">
@@ -40,3 +43,4 @@
 
 <app-user-insert-popup *ngIf="showAddUserPopup" [(isVisible)]="showAddUserPopup"></app-user-insert-popup>
 <app-unit-insert-popup [(isVisible)]="showInsertUnitPopup" [phenomenons]="phenomenons" (emitNewUnit)="addUnit($event)" [sensorTypes]="sensorTypes"></app-unit-insert-popup>
+<app-data-download-popup [(isVisible)]="showDataDownloadPopup"></app-data-download-popup>

+ 5 - 0
src/app/shared/nav-bar/components/nav-bar.component.scss

@@ -13,3 +13,8 @@
   border-left: 1px solid #F2F2F2;
   border-right: 1px solid #FFF;
 }
+
+::ng-deep .input-group-prepend .input-group-text {
+  width: 77px;
+  min-height: 38px;
+}

+ 12 - 1
src/app/shared/nav-bar/components/nav-bar.component.ts

@@ -6,7 +6,7 @@ import {Right} from '../../api/endpoints/models/right';
 import {Phenomenon} from '../../api/endpoints/models/phenomenon';
 import {SensorsService} from '../../api/endpoints/services/sensors.service';
 import {InsertUnit} from '../../api/endpoints/models/insert-unit';
-import {InsertSensor} from '../../api/endpoints/models/insert-sensor';
+import { InsertSensor } from '../../api/endpoints/models/insert-sensor';
 
 @Component({
   selector: 'app-nav-bar',
@@ -20,6 +20,7 @@ export class NavBarComponent implements OnInit, OnDestroy {
   showAddUserPopup = false;
   rights: Right[];
   showInsertUnitPopup = false;
+  showDataDownloadPopup = false;
   phenomenons: Phenomenon[];
   @Output() emitNewUnit: EventEmitter<{unit: InsertUnit, sensors: InsertSensor[]}> =
     new EventEmitter<{unit: InsertUnit, sensors: InsertSensor[]}>();
@@ -56,6 +57,16 @@ export class NavBarComponent implements OnInit, OnDestroy {
     this.showInsertUnitPopup = true;
   }
 
+  /**
+   * Show data download popup
+   */
+  downloadData() {
+    this.sensorService.getPhenomenons().subscribe(
+      response => this.phenomenons = response
+    );
+    this.showDataDownloadPopup = true;
+  }
+
   logOut(): void {
     this.authService.doLogout();
   }

+ 13 - 5
src/app/shared/nav-bar/nav-bar.module.ts

@@ -1,25 +1,33 @@
 import {NgModule} from '@angular/core';
-import {CommonModule} from '@angular/common';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
 import {NavBarComponent} from './components/nav-bar.component';
 import {RouterModule} from '@angular/router';
 import {ButtonModule} from 'primeng/button';
 import { UserInsertPopupComponent } from './components/user-insert-popup/user-insert-popup.component';
 import {DialogModule} from 'primeng/dialog';
 import {ReactiveFormsModule} from '@angular/forms';
-import {UnitInsertPopupComponent} from './components/unit-insert-popup/unit-insert-popup.component';
-
+import { UnitInsertPopupComponent } from './components/unit-insert-popup/unit-insert-popup.component';
+import { DataDownloadPopupComponent } from './components/data-download/data-download-popup.component';
+import { CalendarModule } from 'primeng/calendar';
+import { ProgressSpinnerModule } from 'primeng/progressspinner';
+import { ProgressBarModule } from 'primeng/progressbar';
 
 @NgModule({
-  declarations: [NavBarComponent, UserInsertPopupComponent, UnitInsertPopupComponent],
+  declarations: [NavBarComponent, UserInsertPopupComponent, UnitInsertPopupComponent, DataDownloadPopupComponent],
   exports: [
     NavBarComponent
   ],
   imports: [
     CommonModule,
+    FormsModule,
     RouterModule,
     ButtonModule,
     DialogModule,
-    ReactiveFormsModule
+    ReactiveFormsModule,
+    CalendarModule,
+    ProgressSpinnerModule,
+    ProgressBarModule
   ]
 })
 export class NavBarModule {

Some files were not shown because too many files changed in this diff