From 272a602ffa863d7ffe39ead2deabdf793a374e3b Mon Sep 17 00:00:00 2001 From: Dave <62899351+davidclaveau@users.noreply.github.com> Date: Fri, 30 Aug 2024 12:07:53 -0700 Subject: [PATCH] BRS-351: Add Missing/Null Values Data Export (#370) * add missing data export tab Signed-off-by: David * add missing functions from rebase Signed-off-by: David * missing report - export all set true by default Signed-off-by: David --------- Signed-off-by: David --- .../export-reports.component.html | 57 +++++++++++- .../export-reports.component.scss | 4 + .../export-reports.component.ts | 92 +++++++++++++++++-- .../export-reports/export-reports.module.ts | 10 +- src/app/guards/auth.guard.ts | 32 +++++-- src/app/home/home.component.ts | 6 +- src/app/services/export.service.ts | 74 ++++++++++++--- src/app/shared/utils/constants.ts | 5 +- 8 files changed, 244 insertions(+), 36 deletions(-) diff --git a/src/app/export-reports/export-reports.component.html b/src/app/export-reports/export-reports.component.html index 2be6302..ec432bf 100644 --- a/src/app/export-reports/export-reports.component.html +++ b/src/app/export-reports/export-reports.component.html @@ -15,6 +15,11 @@

Export Report

role="tab" aria-controls="variance" aria-selected="false" (click)="changeActiveTab('variance')">Variance +
@@ -59,8 +64,58 @@

Export variance data

- +
+
+

Export missing data

+

+ Generate a report of missing records. +

+ +
+ +
+
+ +
+
+ +
+ +
+
+ + + +
+
+ +
+
+
Exporter status:
{{ status }}
diff --git a/src/app/export-reports/export-reports.component.scss b/src/app/export-reports/export-reports.component.scss index e69de29..7552611 100644 --- a/src/app/export-reports/export-reports.component.scss +++ b/src/app/export-reports/export-reports.component.scss @@ -0,0 +1,4 @@ +input[type="checkbox"] { + height: 16px; + width: 16px; +} diff --git a/src/app/export-reports/export-reports.component.ts b/src/app/export-reports/export-reports.component.ts index 0a1e2bf..ca51832 100644 --- a/src/app/export-reports/export-reports.component.ts +++ b/src/app/export-reports/export-reports.component.ts @@ -1,10 +1,14 @@ -import { ChangeDetectorRef, Component, OnInit, OnDestroy } from '@angular/core'; -import { Subscription } from 'rxjs'; +import { ChangeDetectorRef, Component, OnDestroy } from '@angular/core'; +import { Subscription, BehaviorSubject, debounceTime } from 'rxjs'; import { DataService } from '../services/data.service'; import { ExportService } from '../services/export.service'; import { Constants } from '../shared/utils/constants'; import { DateTime, Duration } from 'luxon'; -import { UntypedFormControl, UntypedFormGroup } from '@angular/forms'; +import { + UntypedFormControl, + UntypedFormGroup, + FormsModule, +} from '@angular/forms'; @Component({ selector: 'app-export-reports', @@ -24,6 +28,7 @@ export class ExportReportsComponent implements OnDestroy { ERROR: 99, }; + public _parks = new BehaviorSubject(null); public status = 'Standing by'; public percentageComplete = 0; public progressBarTextOverride; @@ -40,6 +45,7 @@ export class ExportReportsComponent implements OnDestroy { public fiscalYearRangeString = this.defaultRangeString; public modelDate = NaN; public activeTab = ''; + public exportAllCheck = true; public tz = Constants.timezone; public maxDate = DateTime.now().setZone(this.tz); @@ -49,6 +55,7 @@ export class ExportReportsComponent implements OnDestroy { public form = new UntypedFormGroup({ year: new UntypedFormControl(null), + park: new UntypedFormControl(null), }); public exportMessage = 'Last export: -'; @@ -93,6 +100,33 @@ export class ExportReportsComponent implements OnDestroy { this.jobUpdate(res); }), ); + this.subscriptions.add( + dataService + .watchItem(Constants.dataIds.ENTER_DATA_PARK) + .subscribe((res) => { + if (res && res.length) { + this._parks.next(this.createTypeaheadObj(res, 'parkName')); + } + }), + ); + this.subscriptions.add( + this.form.controls['park'].valueChanges + .pipe(debounceTime(0)) + .subscribe((changes) => { + if (changes) { + this.form.controls['park'].setValue( + this.getLocalStorageParkById(changes.orcs), + ); + } + }), + ); + this.subscriptions.add( + this.dataService + .watchItem(Constants.dataIds.EXPORT_MISSING_POLLING_DATA) + .subscribe((res) => { + this.jobUpdate(res); + }), + ); } setMaxDate() { @@ -105,6 +139,30 @@ export class ExportReportsComponent implements OnDestroy { this.maxDate = DateTime.local(year); } + // Get park object by orcs + getLocalStorageParkById(orcs) { + let park = this._parks?.value?.find((p) => p?.value?.orcs === orcs); + return park?.value || null; + } + + // create typeahead object + createTypeaheadObj(items, display) { + let list = []; + for (const item of items) { + list.push({ + value: item, + display: item[display], + }); + } + return list; + } + + toggleExportAllCheck() { + this.exportAllCheck = !this.exportAllCheck; + // Remove any park that was selected + this.form.controls['park'].setValue(''); + } + jobUpdate(res) { if (res) { this.initialLoad = false; @@ -157,13 +215,23 @@ export class ExportReportsComponent implements OnDestroy { 'variance', { fiscalYearEnd: year }, ); + } else if (this.activeTab === 'missing') { + const year = this.form.controls['year'].value[1].slice(0, 4); + const orcs = this.form.controls['park'].value?.orcs || ''; + this.exportService.generateReport( + Constants.dataIds.EXPORT_MISSING_POLLING_DATA, + 'missing', + { + fiscalYearEnd: year, + orcs: orcs, + }, + ); } else { this.exportService.generateReport( Constants.dataIds.EXPORT_ALL_POLLING_DATA, 'standard', ); } - this.cd.detectChanges(); } setState(state) { @@ -241,13 +309,21 @@ export class ExportReportsComponent implements OnDestroy { ); } return; + } else if (this.activeTab === 'missing') { + if (this.modelDate) { + this.exportService.checkForReports( + Constants.dataIds.EXPORT_MISSING_POLLING_DATA, + 'missing', + { fiscalYearEnd: this.modelDate }, + ); + } + return; } else { this.exportService.checkForReports( Constants.dataIds.EXPORT_ALL_POLLING_DATA, 'standard', ); } - this.cd.detectChanges(); return; } @@ -279,10 +355,14 @@ export class ExportReportsComponent implements OnDestroy { this.exportMessage = 'No previous report found. Click generate report.'; } } + this.cd.detectChanges(); } disableGenerateButton() { - if (this.activeTab === 'variance' && !this.form?.controls?.['year'].value) { + if ( + (this.activeTab === 'variance' || this.activeTab === 'missing') && + !this.form?.controls?.['year'].value + ) { return true; } if (![0, 2, 99].includes(this.currentState)) { diff --git a/src/app/export-reports/export-reports.module.ts b/src/app/export-reports/export-reports.module.ts index 1c1d7c4..db8e36a 100644 --- a/src/app/export-reports/export-reports.module.ts +++ b/src/app/export-reports/export-reports.module.ts @@ -5,10 +5,18 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgdsTabsModule } from '@digitalspace/ngds-toolkit'; import { BsDatepickerModule } from 'ngx-bootstrap/datepicker'; import { NgdsFormsModule } from '@digitalspace/ngds-forms'; +import { FormsModule } from '@angular/forms'; @NgModule({ declarations: [ExportReportsComponent], - imports: [CommonModule, NgbModule, NgdsTabsModule, NgdsFormsModule, BsDatepickerModule.forRoot()], + imports: [ + CommonModule, + NgbModule, + NgdsTabsModule, + NgdsFormsModule, + BsDatepickerModule.forRoot(), + FormsModule, + ], exports: [ExportReportsComponent], }) export class ExportReportsModule {} diff --git a/src/app/guards/auth.guard.ts b/src/app/guards/auth.guard.ts index 1f712d3..6f05e09 100644 --- a/src/app/guards/auth.guard.ts +++ b/src/app/guards/auth.guard.ts @@ -1,23 +1,28 @@ import { Injectable } from '@angular/core'; -import { UrlTree, Router, RouterStateSnapshot, ActivatedRouteSnapshot } from '@angular/router'; +import { + UrlTree, + Router, + RouterStateSnapshot, + ActivatedRouteSnapshot, +} from '@angular/router'; import { KeycloakService } from '../services/keycloak.service'; @Injectable({ providedIn: 'root', }) -export class AuthGuard { +export class AuthGuard { constructor( private readonly keycloakService: KeycloakService, - private readonly router: Router + private readonly router: Router, ) {} canActivate( route: ActivatedRouteSnapshot, - state: RouterStateSnapshot + state: RouterStateSnapshot, ): boolean | UrlTree { // When a successful login occurs, we store the identity provider used in sessionStorage. const lastIdp = sessionStorage.getItem( - this.keycloakService.LAST_IDP_AUTHENTICATED + this.keycloakService.LAST_IDP_AUTHENTICATED, ); // Not authenticated @@ -48,7 +53,7 @@ export class AuthGuard { if (idp !== '') { sessionStorage.setItem( this.keycloakService.LAST_IDP_AUTHENTICATED, - idp + idp, ); } } @@ -59,15 +64,24 @@ export class AuthGuard { return this.router.parseUrl('/unauthorized'); } - if (!this.keycloakService.isAllowed('export-reports') && state.url === '/export-reports') { + if ( + !this.keycloakService.isAllowed('export-reports') && + state.url === '/export-reports' + ) { return this.router.parseUrl('/'); } - if (!this.keycloakService.isAllowed('lock-records') && state.url === '/lock-records') { + if ( + !this.keycloakService.isAllowed('lock-records') && + state.url === '/lock-records' + ) { return this.router.parseUrl('/'); } - if (!this.keycloakService.isAllowed('review-data') && state.url === '/review-data') { + if ( + !this.keycloakService.isAllowed('review-data') && + state.url === '/review-data' + ) { return this.router.parseUrl('/'); } diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts index bd983bd..879d7ea 100644 --- a/src/app/home/home.component.ts +++ b/src/app/home/home.component.ts @@ -15,7 +15,7 @@ export class HomeComponent { cardText: 'Use this section to enter attendance and revenue and send to BC Parks. You can also view and edit past enteries.', navigation: 'enter-data', - } + }, ]; constructor(protected keyCloakService: KeycloakService) { if (keyCloakService.isAllowed('export-reports')) { @@ -30,7 +30,8 @@ export class HomeComponent { this.cardConfig.push({ cardHeader: 'Lock/Unlock Records', cardTitle: 'Lock/Unlock by fiscal year', - cardText: 'Use this section to lock/unlock fiscal years (April-March) against editing.', + cardText: + 'Use this section to lock/unlock fiscal years (April-March) against editing.', navigation: 'lock-records', }); } @@ -42,6 +43,5 @@ export class HomeComponent { navigation: 'review-data', }); } - } } diff --git a/src/app/services/export.service.ts b/src/app/services/export.service.ts index 6906bd8..a9ec5dc 100644 --- a/src/app/services/export.service.ts +++ b/src/app/services/export.service.ts @@ -10,26 +10,37 @@ import { LoggerService } from './logger.service'; export class ExportService { private pollingRate = 1000; private retryPollingRate = 10000; - private retryTimeout = 300000 // 5 minutes + private retryTimeout = 300000; // 5 minutes private numberOfRetrys = 5; constructor( private apiService: ApiService, private dataService: DataService, - private loggerService: LoggerService - ) { } + private loggerService: LoggerService, + ) {} - async checkForReports(dataId, dataType, params:any = {}, errorObj = {}) { + async checkForReports(dataId, dataType, params: any = {}, errorObj = {}) { let res; try { this.loggerService.debug(`Export GET job`); if (dataType === 'variance') { res = await firstValueFrom( - this.apiService.get('export-variance', { getJob: true, fiscalYearEnd: params.fiscalYearEnd }) + this.apiService.get('expor-variance', { + getJob: true, + fiscalYearEnd: params.fiscalYearEnd, + }), + ); + } else if (dataType === 'missing') { + res = await firstValueFrom( + this.apiService.get('export-missing', { + getJob: true, + fiscalYearEnd: params.fiscalYearEnd, + orcs: params.orcs, + }), ); } else { res = await firstValueFrom( - this.apiService.get('export', { getJob: true }) + this.apiService.get('export', { getJob: true }), ); } if (Object.keys(errorObj).length > 0) { @@ -48,7 +59,21 @@ export class ExportService { if (dataType === 'variance') { if (params?.fiscalYearEnd) { res = await firstValueFrom( - this.apiService.get('export-variance', { fiscalYearEnd: params.fiscalYearEnd }) + this.apiService.get('export-variance', { + fiscalYearEnd: params.fiscalYearEnd, + }), + ); + } else { + throw 'Missing fiscal year end property'; + } + } + if (dataType === 'missing') { + if (params?.fiscalYearEnd) { + res = await firstValueFrom( + this.apiService.get('export-missing', { + fiscalYearEnd: params.fiscalYearEnd, + orcs: params.orcs, + }), ); } else { throw 'Missing fiscal year end property'; @@ -65,8 +90,8 @@ export class ExportService { this.loggerService.error(`${error}`); this.checkForReports(dataId, dataType, params, { state: 'error', - msg: 'Unable to create job. Please try again.' - }) + msg: 'Unable to create job. Please try again.', + }); return error; } } @@ -103,13 +128,24 @@ export class ExportService { this.loggerService.debug(`Export GET job pollTick`); if (dataType === 'variance') { res = await firstValueFrom( - this.apiService.get('export-variance', { fiscalYearEnd: params?.fiscalYearEnd,getJob: true }) + this.apiService.get('export-variance', { + fiscalYearEnd: params?.fiscalYearEnd, + getJob: true, + }), + ); + } else if (dataType === 'missing') { + res = await firstValueFrom( + this.apiService.get('export-missing', { + fiscalYearEnd: params?.fiscalYearEnd, + getJob: true, + orcs: params?.orcs, + }), ); } else { res = await firstValueFrom( - this.apiService.get('export', { getJob: true }) - ); - } + this.apiService.get('export', { getJob: true }), + ); + } if (res.error || res.jobObj?.progressState === 'error') { throw 'error'; } @@ -130,7 +166,17 @@ export class ExportService { if (res.jobObj?.progressState === 'error') { this.dataService.setItemValue(pollObj.dataId, null); if (dataType === 'variance') { - await firstValueFrom(this.apiService.get('export-variance', { fiscalYearEnd: params?.fiscalYearEnd })); + await firstValueFrom( + this.apiService.get('export-variance', { + fiscalYearEnd: params?.fiscalYearEnd, + }), + ); + } else if (dataType === 'missing') { + await firstValueFrom( + this.apiService.get('export-missing', { + fiscalYearEnd: params?.fiscalYearEnd, + }), + ); } else { await firstValueFrom(this.apiService.get('export')); } diff --git a/src/app/shared/utils/constants.ts b/src/app/shared/utils/constants.ts index e4d5f25..814c8ff 100644 --- a/src/app/shared/utils/constants.ts +++ b/src/app/shared/utils/constants.ts @@ -16,18 +16,19 @@ export class Constants { ENTER_DATA_URL_PARAMS: 'enter-data-url-params', EXPORT_ALL_POLLING_DATA: 'export-all-polling-data', EXPORT_VARIANCE_POLLING_DATA: 'export-variance-polling-data', + EXPORT_MISSING_POLLING_DATA: 'export-missing-polling-data', LOCK_RECORDS_FISCAL_YEARS_DATA: 'lock-records-fiscal-years-data', VARIANCE_FILTERS: 'variance-filters', VARIANCE_LIST: 'variance-list', VARIANCE_LAST_EVALUATED_KEY: 'variance-last-evaluated-key', - VARIANCE_WARNING_TRIGGERED_FIELDS: 'variance-warning-triggered-fields' + VARIANCE_WARNING_TRIGGERED_FIELDS: 'variance-warning-triggered-fields', }; public static readonly ApplicationRoles: any = { ADMIN: 'sysadmin', }; - public static readonly timezone: string = 'America/Vancouver' + public static readonly timezone: string = 'America/Vancouver'; public static readonly ActivityTypes: any = [ 'Frontcountry Camping',