From dd07db4a92ab8844c2cdd641e6e0f52ed1ef36c1 Mon Sep 17 00:00:00 2001 From: toddlees Date: Thu, 5 May 2022 09:10:16 -0400 Subject: [PATCH 01/12] use query params instead of heards --- front-end/src/app/shared/services/f3x-summary.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front-end/src/app/shared/services/f3x-summary.service.ts b/front-end/src/app/shared/services/f3x-summary.service.ts index 4db6fddb7b..32a2f4dff3 100644 --- a/front-end/src/app/shared/services/f3x-summary.service.ts +++ b/front-end/src/app/shared/services/f3x-summary.service.ts @@ -19,14 +19,14 @@ export class F3xSummaryService { public create(f3xSummary: F3xSummary, fieldsToValidate: string[] = []): Observable { const payload = f3xSummary.toJson(); return this.apiService - .post(`/f3x-summaries/`, payload, { fields_to_validate: fieldsToValidate }) + .post(`/f3x-summaries/?fields_to_validate=${fieldsToValidate}`, payload) .pipe(map((response) => F3xSummary.fromJSON(response))); } public update(f3xSummary: F3xSummary, fieldsToValidate: string[] = []): Observable { const payload = f3xSummary.toJson(); return this.apiService - .put(`/f3x-summaries/${f3xSummary.id}/`, payload, { fields_to_validate: fieldsToValidate }) + .put(`/f3x-summaries/${f3xSummary.id}/?fields_to_validate=${fieldsToValidate}`, payload) .pipe(map((response) => F3xSummary.fromJSON(response))); } From 77a081a9a6f465892e178dcee5c7e2d39669c503 Mon Sep 17 00:00:00 2001 From: toddlees Date: Thu, 5 May 2022 09:21:06 -0400 Subject: [PATCH 02/12] update unit tests --- .../create-workflow/create-f3x-step1.component.spec.ts | 4 +++- .../create-workflow/create-f3x-step2.component.spec.ts | 8 ++++++-- .../src/app/shared/services/f3x-summary.service.spec.ts | 6 ++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/front-end/src/app/reports/f3x/create-workflow/create-f3x-step1.component.spec.ts b/front-end/src/app/reports/f3x/create-workflow/create-f3x-step1.component.spec.ts index 517465c4b1..47d1e7b8f8 100644 --- a/front-end/src/app/reports/f3x/create-workflow/create-f3x-step1.component.spec.ts +++ b/front-end/src/app/reports/f3x/create-workflow/create-f3x-step1.component.spec.ts @@ -84,7 +84,9 @@ describe('CreateF3XStep1Component', () => { it('#save should save a new f3x record', () => { component.form.patchValue({ ...f3x }); component.save(); - const req = httpTestingController.expectOne(`${environment.apiUrl}/f3x-summaries/`); + const req = httpTestingController.expectOne( + `${environment.apiUrl}/f3x-summaries/?fields_to_validate=filing_frequency,report_type_category,report_code,coverage_from_date,coverage_through_date,date_of_election,state_of_election,form_type` + ); expect(req.request.method).toEqual('POST'); httpTestingController.verify(); }); diff --git a/front-end/src/app/reports/f3x/create-workflow/create-f3x-step2.component.spec.ts b/front-end/src/app/reports/f3x/create-workflow/create-f3x-step2.component.spec.ts index 4e6052b47b..9ee7e5aa7d 100644 --- a/front-end/src/app/reports/f3x/create-workflow/create-f3x-step2.component.spec.ts +++ b/front-end/src/app/reports/f3x/create-workflow/create-f3x-step2.component.spec.ts @@ -94,14 +94,18 @@ describe('CreateF3xStep2Component', () => { component.form.patchValue({ change_of_address: 'X' }); component.save('back'); - let req = httpTestingController.expectOne(`${environment.apiUrl}/f3x-summaries/${component.report.id}/`); + let req = httpTestingController.expectOne( + `${environment.apiUrl}/f3x-summaries/${component.report.id}/?fields_to_validate=change_of_address,street_1,street_2,city,state,zip,memo_checkbox,memo` + ); expect(req.request.method).toEqual('PUT'); req.flush(component.report); expect(navigateSpy).toHaveBeenCalledWith('/reports'); navigateSpy.calls.reset(); component.save('continue'); - req = httpTestingController.expectOne(`${environment.apiUrl}/f3x-summaries/${component.report.id}/`); + req = httpTestingController.expectOne( + `${environment.apiUrl}/f3x-summaries/${component.report.id}/?fields_to_validate=change_of_address,street_1,street_2,city,state,zip,memo_checkbox,memo` + ); expect(req.request.method).toEqual('PUT'); req.flush(component.report); expect(navigateSpy).toHaveBeenCalledWith('/reports'); diff --git a/front-end/src/app/shared/services/f3x-summary.service.spec.ts b/front-end/src/app/shared/services/f3x-summary.service.spec.ts index a006dd28ee..4ec665288b 100644 --- a/front-end/src/app/shared/services/f3x-summary.service.spec.ts +++ b/front-end/src/app/shared/services/f3x-summary.service.spec.ts @@ -57,7 +57,7 @@ describe('F3xSummaryService', () => { expect(response).toEqual(f3xSummary); }); - const req = httpTestingController.expectOne(`${environment.apiUrl}/f3x-summaries/`); + const req = httpTestingController.expectOne(`${environment.apiUrl}/f3x-summaries/?fields_to_validate=`); expect(req.request.method).toEqual('POST'); req.flush(f3xSummary); httpTestingController.verify(); @@ -70,7 +70,9 @@ describe('F3xSummaryService', () => { expect(response).toEqual(f3xSummary); }); - const req = httpTestingController.expectOne(`${environment.apiUrl}/f3x-summaries/${f3xSummary.id}/`); + const req = httpTestingController.expectOne( + `${environment.apiUrl}/f3x-summaries/${f3xSummary.id}/?fields_to_validate=` + ); expect(req.request.method).toEqual('PUT'); req.flush(f3xSummary); httpTestingController.verify(); From 3ae30bb6a6f583e816171b179b86d8f504d9e1ae Mon Sep 17 00:00:00 2001 From: toddlees Date: Fri, 6 May 2022 10:42:04 -0400 Subject: [PATCH 03/12] move query parameter assignment to api service --- .../src/app/shared/services/api.service.ts | 20 ++++++++++++------- .../shared/services/f3x-summary.service.ts | 4 ++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/front-end/src/app/shared/services/api.service.ts b/front-end/src/app/shared/services/api.service.ts index 153055e37d..790cfcdb2d 100644 --- a/front-end/src/app/shared/services/api.service.ts +++ b/front-end/src/app/shared/services/api.service.ts @@ -1,4 +1,4 @@ -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable, of } from 'rxjs'; import { tap, delay, switchMap } from 'rxjs/operators'; @@ -28,21 +28,27 @@ export class ApiService { return { ...baseHeaders, ...headersToAdd }; } + getQueryParams(queryParams: any = {}) { + return new HttpParams({ fromObject: queryParams }); + } + public get(endpoint: string): Observable { const headers = this.getHeaders(); return this.http.get(`${environment.apiUrl}${endpoint}`, { headers: headers }); } // prettier-ignore - public post(endpoint: string, payload: any, headersToAdd: any = {}): Observable { // eslint-disable-line @typescript-eslint/no-explicit-any - const headers = this.getHeaders(headersToAdd); - return this.http.post(`${environment.apiUrl}${endpoint}`, payload, { headers: headers }); + public post(endpoint: string, payload: any, queryParams: any = {}): Observable { // eslint-disable-line @typescript-eslint/no-explicit-any + const headers = this.getHeaders(); + const params = this.getQueryParams(queryParams); + return this.http.post(`${environment.apiUrl}${endpoint}`, payload, { headers: headers , params: params}); } // prettier-ignore - public put(endpoint: string, payload: any, headersToAdd: any = {}): Observable { // eslint-disable-line @typescript-eslint/no-explicit-any - const headers = this.getHeaders(headersToAdd); - return this.http.put(`${environment.apiUrl}${endpoint}`, payload, { headers: headers }); + public put(endpoint: string, payload: any, queryParams: any = {}): Observable { // eslint-disable-line @typescript-eslint/no-explicit-any + const headers = this.getHeaders(); + const params = this.getQueryParams(queryParams); + return this.http.put(`${environment.apiUrl}${endpoint}`, payload, { headers: headers , params: params}); } public delete(endpoint: string): Observable { diff --git a/front-end/src/app/shared/services/f3x-summary.service.ts b/front-end/src/app/shared/services/f3x-summary.service.ts index 32a2f4dff3..4db6fddb7b 100644 --- a/front-end/src/app/shared/services/f3x-summary.service.ts +++ b/front-end/src/app/shared/services/f3x-summary.service.ts @@ -19,14 +19,14 @@ export class F3xSummaryService { public create(f3xSummary: F3xSummary, fieldsToValidate: string[] = []): Observable { const payload = f3xSummary.toJson(); return this.apiService - .post(`/f3x-summaries/?fields_to_validate=${fieldsToValidate}`, payload) + .post(`/f3x-summaries/`, payload, { fields_to_validate: fieldsToValidate }) .pipe(map((response) => F3xSummary.fromJSON(response))); } public update(f3xSummary: F3xSummary, fieldsToValidate: string[] = []): Observable { const payload = f3xSummary.toJson(); return this.apiService - .put(`/f3x-summaries/${f3xSummary.id}/?fields_to_validate=${fieldsToValidate}`, payload) + .put(`/f3x-summaries/${f3xSummary.id}/`, payload, { fields_to_validate: fieldsToValidate }) .pipe(map((response) => F3xSummary.fromJSON(response))); } From df3b75766b9609e035ba3c37f7c5cb83b89780a6 Mon Sep 17 00:00:00 2001 From: toddlees Date: Fri, 6 May 2022 11:39:15 -0400 Subject: [PATCH 04/12] pass array param as single string --- front-end/src/app/shared/services/f3x-summary.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front-end/src/app/shared/services/f3x-summary.service.ts b/front-end/src/app/shared/services/f3x-summary.service.ts index 4db6fddb7b..bba4e7385a 100644 --- a/front-end/src/app/shared/services/f3x-summary.service.ts +++ b/front-end/src/app/shared/services/f3x-summary.service.ts @@ -19,14 +19,14 @@ export class F3xSummaryService { public create(f3xSummary: F3xSummary, fieldsToValidate: string[] = []): Observable { const payload = f3xSummary.toJson(); return this.apiService - .post(`/f3x-summaries/`, payload, { fields_to_validate: fieldsToValidate }) + .post(`/f3x-summaries/`, payload, { fields_to_validate: fieldsToValidate.join(',') }) .pipe(map((response) => F3xSummary.fromJSON(response))); } public update(f3xSummary: F3xSummary, fieldsToValidate: string[] = []): Observable { const payload = f3xSummary.toJson(); return this.apiService - .put(`/f3x-summaries/${f3xSummary.id}/`, payload, { fields_to_validate: fieldsToValidate }) + .put(`/f3x-summaries/${f3xSummary.id}/`, payload, { fields_to_validate: fieldsToValidate.join(',') }) .pipe(map((response) => F3xSummary.fromJSON(response))); } From 840835252ae3c6603c8613a6e740481b0100f4b6 Mon Sep 17 00:00:00 2001 From: toddlees Date: Fri, 6 May 2022 11:43:26 -0400 Subject: [PATCH 05/12] adhere to typescript strict typing --- front-end/src/app/shared/services/api.service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/front-end/src/app/shared/services/api.service.ts b/front-end/src/app/shared/services/api.service.ts index 790cfcdb2d..c51f6de571 100644 --- a/front-end/src/app/shared/services/api.service.ts +++ b/front-end/src/app/shared/services/api.service.ts @@ -28,7 +28,9 @@ export class ApiService { return { ...baseHeaders, ...headersToAdd }; } - getQueryParams(queryParams: any = {}) { + getQueryParams( + queryParams: { [param: string]: string | number | boolean | readonly (string | number | boolean)[] } = {} + ) { return new HttpParams({ fromObject: queryParams }); } From d1ac4157770be328dc5c85d3596e276c046c5654 Mon Sep 17 00:00:00 2001 From: Matt Travers Date: Fri, 6 May 2022 19:56:08 -0400 Subject: [PATCH 06/12] Added resolver to workflow step components --- .../create-f3x-step1.component.ts | 21 +++----- .../create-f3x-step2.component.html | 1 + .../create-f3x-step2.component.spec.ts | 7 ++- .../create-f3x-step2.component.ts | 48 +++++++------------ .../src/app/reports/reports-routing.module.ts | 7 +++ .../shared/resolvers/report.resolver.spec.ts | 21 ++++++++ .../app/shared/resolvers/report.resolver.ts | 25 ++++++++++ .../shared/resolvers/report.resolver.ts.SAVE | 25 ++++++++++ .../src/app/shared/services/api.service.ts | 8 ++-- 9 files changed, 113 insertions(+), 50 deletions(-) create mode 100644 front-end/src/app/shared/resolvers/report.resolver.spec.ts create mode 100644 front-end/src/app/shared/resolvers/report.resolver.ts create mode 100644 front-end/src/app/shared/resolvers/report.resolver.ts.SAVE diff --git a/front-end/src/app/reports/f3x/create-workflow/create-f3x-step1.component.ts b/front-end/src/app/reports/f3x/create-workflow/create-f3x-step1.component.ts index 98acd959f0..d0bd03c2b3 100644 --- a/front-end/src/app/reports/f3x/create-workflow/create-f3x-step1.component.ts +++ b/front-end/src/app/reports/f3x/create-workflow/create-f3x-step1.component.ts @@ -14,7 +14,7 @@ import { LabelList, LabelUtils, PrimeOptions, StatesCodeLabels } from 'app/share import { selectCommitteeAccount } from 'app/store/committee-account.selectors'; import { ValidateService } from 'app/shared/services/validate.service'; import { schema as f3xSchema } from 'fecfile-validate/fecfile_validate_js/dist/F3X'; -import { Subject, takeUntil, combineLatest, switchMap, Observable, of } from 'rxjs'; +import { Subject, takeUntil } from 'rxjs'; import { Store } from '@ngrx/store'; import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { environment } from 'environments/environment'; @@ -90,18 +90,11 @@ export class CreateF3XStep1Component implements OnInit, OnDestroy { ) {} ngOnInit(): void { - const existingReport$: Observable = this.activatedRoute.paramMap.pipe( - switchMap((paramMap): Observable => { - const id = paramMap.get('id'); - if (id) { - return this.f3xSummaryService.get(parseInt(id)); - } - return of(undefined); - }) - ); - combineLatest([this.store.select(selectCommitteeAccount), existingReport$]) + const report: F3xSummary = this.activatedRoute.snapshot.data['report']; + this.store + .select(selectCommitteeAccount) .pipe(takeUntil(this.destroy$)) - .subscribe(([committeeAccount, existingReport]) => { + .subscribe((committeeAccount) => { const filingFrequency = this.userCanSetFilingFrequency ? 'Q' : committeeAccount.filing_frequency; this.form.addControl('filing_frequency', new FormControl()); this.form.addControl('report_type_category', new FormControl()); @@ -125,8 +118,8 @@ export class CreateF3XStep1Component implements OnInit, OnDestroy { report_code: this.getReportCodes()[0], }); }); - if (existingReport) { - this.form.patchValue(existingReport); + if (report) { + this.form.patchValue(report); } }); this.stateOptions = LabelUtils.getPrimeOptions(StatesCodeLabels); diff --git a/front-end/src/app/reports/f3x/create-workflow/create-f3x-step2.component.html b/front-end/src/app/reports/f3x/create-workflow/create-f3x-step2.component.html index 8ab5eaf34a..8f1fcf7ca6 100644 --- a/front-end/src/app/reports/f3x/create-workflow/create-f3x-step2.component.html +++ b/front-end/src/app/reports/f3x/create-workflow/create-f3x-step2.component.html @@ -30,6 +30,7 @@

Committee address

[form]="form" fieldName="change_of_address" [formSubmitted]="formSubmitted" + patternErrorMessage="Please select 'Yes' or 'No'" > diff --git a/front-end/src/app/reports/f3x/create-workflow/create-f3x-step2.component.spec.ts b/front-end/src/app/reports/f3x/create-workflow/create-f3x-step2.component.spec.ts index 9ee7e5aa7d..7b8c88ce62 100644 --- a/front-end/src/app/reports/f3x/create-workflow/create-f3x-step2.component.spec.ts +++ b/front-end/src/app/reports/f3x/create-workflow/create-f3x-step2.component.spec.ts @@ -26,7 +26,6 @@ describe('CreateF3xStep2Component', () => { let httpTestingController: HttpTestingController; let reportService: F3xSummaryService; const committeeAccount: CommitteeAccount = CommitteeAccount.fromJSON({}); - const mockParamMap = new Map([['id', 1]]); beforeEach(async () => { const userLoginData: UserLoginData = { @@ -65,7 +64,11 @@ describe('CreateF3xStep2Component', () => { { provide: ActivatedRoute, useValue: { - paramMap: of(mockParamMap), + snapshot: { + data: { + report: F3xSummary.fromJSON({}), + }, + }, }, }, ], diff --git a/front-end/src/app/reports/f3x/create-workflow/create-f3x-step2.component.ts b/front-end/src/app/reports/f3x/create-workflow/create-f3x-step2.component.ts index 9afb35f340..73fb74cd6a 100644 --- a/front-end/src/app/reports/f3x/create-workflow/create-f3x-step2.component.ts +++ b/front-end/src/app/reports/f3x/create-workflow/create-f3x-step2.component.ts @@ -1,16 +1,16 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; import { FormBuilder, FormGroup } from '@angular/forms'; -import { mergeMap, Observable, Subject, takeUntil, skipUntil } from 'rxjs'; +import { switchMap, combineLatest, Observable, Subject, takeUntil, of } from 'rxjs'; import { Store } from '@ngrx/store'; import { refreshCommitteeAccountDetailsAction } from '../../../store/committee-account.actions'; -import { CommitteeAccount } from 'app/shared/models/committee-account.model'; import { selectCommitteeAccount } from 'app/store/committee-account.selectors'; import { LabelUtils, PrimeOptions, StatesCodeLabels, CountryCodeLabels } from 'app/shared/utils/label.utils'; import { ValidateService } from 'app/shared/services/validate.service'; import { schema as f3xSchema } from 'fecfile-validate/fecfile_validate_js/dist/F3X'; import { F3xSummary } from 'app/shared/models/f3x-summary.model'; import { F3xSummaryService } from 'app/shared/services/f3x-summary.service'; +import { CommitteeAccount } from 'app/shared/models/committee-account.model'; @Component({ selector: 'app-create-f3x-step2', @@ -18,7 +18,6 @@ import { F3xSummaryService } from 'app/shared/services/f3x-summary.service'; styleUrls: ['./create-f3x-step2.component.scss'], }) export class CreateF3xStep2Component implements OnInit, OnDestroy { - report: F3xSummary | null = null; formProperties: string[] = [ 'change_of_address', 'street_1', @@ -29,19 +28,19 @@ export class CreateF3xStep2Component implements OnInit, OnDestroy { 'memo_checkbox', 'memo', ]; + report: F3xSummary | undefined; stateOptions: PrimeOptions = []; countryOptions: PrimeOptions = []; formSubmitted = false; destroy$: Subject = new Subject(); - committeeAccount: CommitteeAccount | null = null; - committeeAccount$: Observable | null = null; + committeeAccount$: Observable = this.store.select(selectCommitteeAccount); form: FormGroup = this.fb.group(this.validateService.getFormGroupFields(this.formProperties)); constructor( private router: Router, - private route: ActivatedRoute, - private reportService: F3xSummaryService, + private activatedRoute: ActivatedRoute, + private f3xSummaryService: F3xSummaryService, private validateService: ValidateService, private fb: FormBuilder, private store: Store @@ -50,32 +49,21 @@ export class CreateF3xStep2Component implements OnInit, OnDestroy { ngOnInit(): void { // Refresh committee account details whenever page loads this.store.dispatch(refreshCommitteeAccountDetailsAction()); - - this.committeeAccount$ = this.store.select(selectCommitteeAccount); - this.committeeAccount$ - .pipe(takeUntil(this.destroy$)) - .subscribe((committeeAccount: CommitteeAccount) => (this.committeeAccount = committeeAccount)); this.stateOptions = LabelUtils.getPrimeOptions(StatesCodeLabels); this.countryOptions = LabelUtils.getPrimeOptions(CountryCodeLabels); - this.route.paramMap - .pipe( - takeUntil(this.destroy$), - skipUntil(this.committeeAccount$), - mergeMap((params): Observable => { - const id = Number(params.get('id')); - return this.reportService.get(id); - }) - ) - .subscribe((report: F3xSummary) => { - this.report = report; + this.report = this.activatedRoute.snapshot.data['report']; + this.store + .select(selectCommitteeAccount) + .pipe(takeUntil(this.destroy$)) + .subscribe((committeeAccount) => { this.form.patchValue({ - change_of_address: this.report.change_of_address ? this.report.change_of_address : 'not-selected', - street_1: this.report.street_1 ? this.report.street_1 : this.committeeAccount?.street_1, - street_2: this.report.street_2 ? this.report.street_2 : this.committeeAccount?.street_2, - city: this.report.city ? this.report.city : this.committeeAccount?.city, - state: this.report.state ? this.report.state : this.committeeAccount?.state, - zip: this.report.zip ? this.report.zip : this.committeeAccount?.zip, + change_of_address: this.report?.change_of_address ? this.report.change_of_address : 'not-selected', + street_1: this.report?.street_1 ? this.report.street_1 : committeeAccount?.street_1, + street_2: this.report?.street_2 ? this.report.street_2 : committeeAccount?.street_2, + city: this.report?.city ? this.report.city : committeeAccount?.city, + state: this.report?.state ? this.report.state : committeeAccount?.state, + zip: this.report?.zip ? this.report.zip : committeeAccount?.zip, memo_checkbox: false, memo: '', }); @@ -103,7 +91,7 @@ export class CreateF3xStep2Component implements OnInit, OnDestroy { ...this.form.value, }); - this.reportService.update(payload, this.formProperties).subscribe(() => { + this.f3xSummaryService.update(payload, this.formProperties).subscribe(() => { if (jump === 'continue' && this.report?.id) { this.router.navigateByUrl('/reports'); } diff --git a/front-end/src/app/reports/reports-routing.module.ts b/front-end/src/app/reports/reports-routing.module.ts index eabd871367..d06af73792 100644 --- a/front-end/src/app/reports/reports-routing.module.ts +++ b/front-end/src/app/reports/reports-routing.module.ts @@ -3,6 +3,7 @@ import { Routes, RouterModule } from '@angular/router'; import { ReportListComponent } from './report-list/report-list.component'; import { CreateF3XStep1Component } from './f3x/create-workflow/create-f3x-step1.component'; import { CreateF3xStep2Component } from './f3x/create-workflow/create-f3x-step2.component'; +import { ReportResolver } from 'app/shared/resolvers/report.resolver'; const routes: Routes = [ { @@ -14,9 +15,15 @@ const routes: Routes = [ path: 'f3x/create/step1', component: CreateF3XStep1Component, }, + { + path: 'f3x/create/step1/:id', + component: CreateF3XStep1Component, + resolve: { report: ReportResolver }, + }, { path: 'f3x/create/step2/:id', component: CreateF3xStep2Component, + resolve: { report: ReportResolver }, }, { path: '**', redirectTo: '' }, ]; diff --git a/front-end/src/app/shared/resolvers/report.resolver.spec.ts b/front-end/src/app/shared/resolvers/report.resolver.spec.ts new file mode 100644 index 0000000000..bebedb1e61 --- /dev/null +++ b/front-end/src/app/shared/resolvers/report.resolver.spec.ts @@ -0,0 +1,21 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { provideMockStore } from '@ngrx/store/testing'; +import { F3xSummaryService } from '../services/f3x-summary.service'; +import { ReportResolver } from './report.resolver'; + +describe('ReportResolver', () => { + let resolver: ReportResolver; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [F3xSummaryService, provideMockStore({})], + }); + resolver = TestBed.inject(ReportResolver); + }); + + it('should be created', () => { + expect(resolver).toBeTruthy(); + }); +}); diff --git a/front-end/src/app/shared/resolvers/report.resolver.ts b/front-end/src/app/shared/resolvers/report.resolver.ts new file mode 100644 index 0000000000..6d1d4dc66a --- /dev/null +++ b/front-end/src/app/shared/resolvers/report.resolver.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@angular/core'; +import { Resolve, ActivatedRouteSnapshot } from '@angular/router'; +import { Observable, of } from 'rxjs'; +import { F3xSummary } from '../models/f3x-summary.model'; +import { F3xSummaryService } from '../services/f3x-summary.service'; + +@Injectable({ + providedIn: 'root', +}) +export class ReportResolver implements Resolve { + constructor(private f3xSummaryService: F3xSummaryService) {} + + /** + * Returns the report record for the id passed in the URL + * @param {ActivatedRouteSnapshot} route + * @returns {Observable} + */ + resolve(route: ActivatedRouteSnapshot): Observable { + const id = route.paramMap.get('id'); + if (id) { + return this.f3xSummaryService.get(Number(id)); + } + return of(undefined); + } +} diff --git a/front-end/src/app/shared/resolvers/report.resolver.ts.SAVE b/front-end/src/app/shared/resolvers/report.resolver.ts.SAVE new file mode 100644 index 0000000000..6d1d4dc66a --- /dev/null +++ b/front-end/src/app/shared/resolvers/report.resolver.ts.SAVE @@ -0,0 +1,25 @@ +import { Injectable } from '@angular/core'; +import { Resolve, ActivatedRouteSnapshot } from '@angular/router'; +import { Observable, of } from 'rxjs'; +import { F3xSummary } from '../models/f3x-summary.model'; +import { F3xSummaryService } from '../services/f3x-summary.service'; + +@Injectable({ + providedIn: 'root', +}) +export class ReportResolver implements Resolve { + constructor(private f3xSummaryService: F3xSummaryService) {} + + /** + * Returns the report record for the id passed in the URL + * @param {ActivatedRouteSnapshot} route + * @returns {Observable} + */ + resolve(route: ActivatedRouteSnapshot): Observable { + const id = route.paramMap.get('id'); + if (id) { + return this.f3xSummaryService.get(Number(id)); + } + return of(undefined); + } +} diff --git a/front-end/src/app/shared/services/api.service.ts b/front-end/src/app/shared/services/api.service.ts index c51f6de571..a6aae407e2 100644 --- a/front-end/src/app/shared/services/api.service.ts +++ b/front-end/src/app/shared/services/api.service.ts @@ -74,20 +74,20 @@ export class ApiService { } // prettier-ignore - public spinnerPost(endpoint: string, payload: any): Observable { // eslint-disable-line @typescript-eslint/no-explicit-any + public spinnerPost(endpoint: string, payload: any, queryParams: any = {}): Observable { // eslint-disable-line @typescript-eslint/no-explicit-any return of(null).pipe( delay(0), tap(() => this.store.dispatch(spinnerOnAction())), - switchMap(() => this.post(endpoint, payload).pipe(tap(() => this.store.dispatch(spinnerOffAction())))) + switchMap(() => this.post(endpoint, payload, queryParams).pipe(tap(() => this.store.dispatch(spinnerOffAction())))) ); } // prettier-ignore - public spinnerPut(endpoint: string, payload: any): Observable { // eslint-disable-line @typescript-eslint/no-explicit-any + public spinnerPut(endpoint: string, payload: any, queryParams: any = {}): Observable { // eslint-disable-line @typescript-eslint/no-explicit-any return of(null).pipe( delay(0), tap(() => this.store.dispatch(spinnerOnAction())), - switchMap(() => this.put(endpoint, payload).pipe(tap(() => this.store.dispatch(spinnerOffAction())))) + switchMap(() => this.put(endpoint, payload, queryParams).pipe(tap(() => this.store.dispatch(spinnerOffAction())))) ); } From be703526ddb05e08b7ab40ce3f684b422b421975 Mon Sep 17 00:00:00 2001 From: Matt Travers Date: Fri, 6 May 2022 20:02:23 -0400 Subject: [PATCH 07/12] Fixed linting errors --- .../reports/f3x/create-workflow/create-f3x-step2.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front-end/src/app/reports/f3x/create-workflow/create-f3x-step2.component.ts b/front-end/src/app/reports/f3x/create-workflow/create-f3x-step2.component.ts index 73fb74cd6a..24e37aa50b 100644 --- a/front-end/src/app/reports/f3x/create-workflow/create-f3x-step2.component.ts +++ b/front-end/src/app/reports/f3x/create-workflow/create-f3x-step2.component.ts @@ -1,7 +1,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; import { FormBuilder, FormGroup } from '@angular/forms'; -import { switchMap, combineLatest, Observable, Subject, takeUntil, of } from 'rxjs'; +import { Observable, Subject, takeUntil } from 'rxjs'; import { Store } from '@ngrx/store'; import { refreshCommitteeAccountDetailsAction } from '../../../store/committee-account.actions'; import { selectCommitteeAccount } from 'app/store/committee-account.selectors'; From dc090a90391d5fcfb0a5dcca86256eeb8c0bd7a7 Mon Sep 17 00:00:00 2001 From: Matt Travers Date: Fri, 6 May 2022 20:12:27 -0400 Subject: [PATCH 08/12] Moved api spinner methods to code backlog --- .../src/app/shared/services/api.service.ts | 45 +------------------ 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/front-end/src/app/shared/services/api.service.ts b/front-end/src/app/shared/services/api.service.ts index a6aae407e2..24329b8b5a 100644 --- a/front-end/src/app/shared/services/api.service.ts +++ b/front-end/src/app/shared/services/api.service.ts @@ -1,12 +1,10 @@ import { HttpClient, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { Observable, of } from 'rxjs'; -import { tap, delay, switchMap } from 'rxjs/operators'; +import { Observable } from 'rxjs'; import { environment } from '../../../environments/environment'; import { Store } from '@ngrx/store'; import { selectUserLoginData } from 'app/store/login.selectors'; import { UserLoginData } from '../models/user.model'; -import { spinnerOnAction, spinnerOffAction } from 'app/store/spinner.actions'; @Injectable({ providedIn: 'root', @@ -57,45 +55,4 @@ export class ApiService { const headers = this.getHeaders(); return this.http.delete(`${environment.apiUrl}${endpoint}`, { headers: headers }); } - - public spinnerGet(endpoint: string): Observable { - // For spinnerGet, spinnerPost, spinnerPut, and spinnerDelete methods there is an - // issue with the *ngIf that hides/shows the spinner in the layout.component.html - // template that triggers the "Expression has changed after it was checked" error. - // The debug(0) in the return statement allows the view generation process - // to finish before the dispatch is made of the spinnerOnAction which updates - // the flag in the view template to turn the spinner on. - // Read about the issue here: https://blog.angular-university.io/angular-debugging/ - return of(null).pipe( - delay(0), - tap(() => this.store.dispatch(spinnerOnAction())), - switchMap(() => this.get(endpoint).pipe(tap(() => this.store.dispatch(spinnerOffAction())))) - ); - } - - // prettier-ignore - public spinnerPost(endpoint: string, payload: any, queryParams: any = {}): Observable { // eslint-disable-line @typescript-eslint/no-explicit-any - return of(null).pipe( - delay(0), - tap(() => this.store.dispatch(spinnerOnAction())), - switchMap(() => this.post(endpoint, payload, queryParams).pipe(tap(() => this.store.dispatch(spinnerOffAction())))) - ); - } - - // prettier-ignore - public spinnerPut(endpoint: string, payload: any, queryParams: any = {}): Observable { // eslint-disable-line @typescript-eslint/no-explicit-any - return of(null).pipe( - delay(0), - tap(() => this.store.dispatch(spinnerOnAction())), - switchMap(() => this.put(endpoint, payload, queryParams).pipe(tap(() => this.store.dispatch(spinnerOffAction())))) - ); - } - - public spinnerDelete(endpoint: string): Observable { - return of(null).pipe( - delay(0), - tap(() => this.store.dispatch(spinnerOnAction())), - switchMap(() => this.delete(endpoint).pipe(tap(() => this.store.dispatch(spinnerOffAction())))) - ); - } } From 3d43fd7bce06c8d6760cdedf7cb937ad92856ddd Mon Sep 17 00:00:00 2001 From: Matt Travers Date: Fri, 6 May 2022 20:35:13 -0400 Subject: [PATCH 09/12] Increased unit test coverage --- .../shared/resolvers/report.resolver.spec.ts | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/front-end/src/app/shared/resolvers/report.resolver.spec.ts b/front-end/src/app/shared/resolvers/report.resolver.spec.ts index bebedb1e61..ab9f7ac828 100644 --- a/front-end/src/app/shared/resolvers/report.resolver.spec.ts +++ b/front-end/src/app/shared/resolvers/report.resolver.spec.ts @@ -1,21 +1,52 @@ import { TestBed } from '@angular/core/testing'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { ActivatedRouteSnapshot, convertToParamMap } from '@angular/router'; import { provideMockStore } from '@ngrx/store/testing'; import { F3xSummaryService } from '../services/f3x-summary.service'; import { ReportResolver } from './report.resolver'; +import { F3xSummary } from '../models/f3x-summary.model'; +import { environment } from '../../../environments/environment'; describe('ReportResolver', () => { let resolver: ReportResolver; + let httpTestingController: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [F3xSummaryService, provideMockStore({})], }); + httpTestingController = TestBed.inject(HttpTestingController); resolver = TestBed.inject(ReportResolver); }); it('should be created', () => { expect(resolver).toBeTruthy(); }); + + it('should return an F3X report', () => { + const f3xSummary: F3xSummary = F3xSummary.fromJSON({ id: 999 }); + const route = { + paramMap: convertToParamMap({ id: 999 }), + }; + + resolver.resolve(route as ActivatedRouteSnapshot).subscribe((response: F3xSummary | undefined) => { + expect(response).toEqual(f3xSummary); + }); + + const req = httpTestingController.expectOne(`${environment.apiUrl}/f3x-summaries/${f3xSummary.id}`); + expect(req.request.method).toEqual('GET'); + req.flush(f3xSummary); + httpTestingController.verify(); + }); + + it('should return undefined', () => { + const route = { + paramMap: convertToParamMap({ id: undefined }), + }; + + resolver.resolve(route as ActivatedRouteSnapshot).subscribe((response: F3xSummary | undefined) => { + expect(response).toEqual(undefined); + }); + }); }); From c3ef8a9d3f804ef6edc811351b823289f6843c29 Mon Sep 17 00:00:00 2001 From: Matt Travers Date: Fri, 6 May 2022 20:57:23 -0400 Subject: [PATCH 10/12] Expanded code coverage --- .../create-workflow/create-f3x-step1.component.spec.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/front-end/src/app/reports/f3x/create-workflow/create-f3x-step1.component.spec.ts b/front-end/src/app/reports/f3x/create-workflow/create-f3x-step1.component.spec.ts index 47d1e7b8f8..1060dbdf2a 100644 --- a/front-end/src/app/reports/f3x/create-workflow/create-f3x-step1.component.spec.ts +++ b/front-end/src/app/reports/f3x/create-workflow/create-f3x-step1.component.spec.ts @@ -1,6 +1,7 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; +import { of } from 'rxjs'; import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; import { RouterTestingModule } from '@angular/router/testing'; import { provideMockStore } from '@ngrx/store/testing'; @@ -14,6 +15,7 @@ import { CalendarModule } from 'primeng/calendar'; import { RadioButtonModule } from 'primeng/radiobutton'; import { SelectButtonModule } from 'primeng/selectbutton'; import { CreateF3XStep1Component, F3xReportTypeCategories } from './create-f3x-step1.component'; +import { F3xSummaryService } from 'app/shared/services/f3x-summary.service'; describe('CreateF3XStep1Component', () => { let httpTestingController: HttpTestingController; @@ -91,6 +93,13 @@ describe('CreateF3XStep1Component', () => { httpTestingController.verify(); }); + it('#save should not save with invalid f3x record', () => { + component.form.patchValue({ ...f3x }); + component.form.patchValue({ form_type: 'NO-GOOD' }); + component.save(); + expect(component.form.invalid).toBe(true); + }); + it('back button should go back to report list page', () => { const navigateSpy = spyOn(router, 'navigateByUrl'); component.goBack(); From 67397701e296d2338c215c6beb2f1a02f030cdac Mon Sep 17 00:00:00 2001 From: Matt Travers Date: Fri, 6 May 2022 21:05:11 -0400 Subject: [PATCH 11/12] Fixed lint errors --- .../f3x/create-workflow/create-f3x-step1.component.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/front-end/src/app/reports/f3x/create-workflow/create-f3x-step1.component.spec.ts b/front-end/src/app/reports/f3x/create-workflow/create-f3x-step1.component.spec.ts index 1060dbdf2a..12cadf17d9 100644 --- a/front-end/src/app/reports/f3x/create-workflow/create-f3x-step1.component.spec.ts +++ b/front-end/src/app/reports/f3x/create-workflow/create-f3x-step1.component.spec.ts @@ -1,7 +1,6 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; -import { of } from 'rxjs'; import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; import { RouterTestingModule } from '@angular/router/testing'; import { provideMockStore } from '@ngrx/store/testing'; @@ -15,7 +14,6 @@ import { CalendarModule } from 'primeng/calendar'; import { RadioButtonModule } from 'primeng/radiobutton'; import { SelectButtonModule } from 'primeng/selectbutton'; import { CreateF3XStep1Component, F3xReportTypeCategories } from './create-f3x-step1.component'; -import { F3xSummaryService } from 'app/shared/services/f3x-summary.service'; describe('CreateF3XStep1Component', () => { let httpTestingController: HttpTestingController; From 9e86cedf34a9dee6d4e19a7ea7632fab13aaf290 Mon Sep 17 00:00:00 2001 From: Matt Travers Date: Sat, 7 May 2022 12:50:26 -0400 Subject: [PATCH 12/12] Increased code coverage --- .../create-f3x-step1.component.spec.ts | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/front-end/src/app/reports/f3x/create-workflow/create-f3x-step1.component.spec.ts b/front-end/src/app/reports/f3x/create-workflow/create-f3x-step1.component.spec.ts index 12cadf17d9..8431b0b443 100644 --- a/front-end/src/app/reports/f3x/create-workflow/create-f3x-step1.component.spec.ts +++ b/front-end/src/app/reports/f3x/create-workflow/create-f3x-step1.component.spec.ts @@ -1,14 +1,15 @@ -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; import { RouterTestingModule } from '@angular/router/testing'; import { provideMockStore } from '@ngrx/store/testing'; +import { of } from 'rxjs'; import { F3xReportCodes, F3xSummary } from 'app/shared/models/f3x-summary.model'; +import { F3xSummaryService } from 'app/shared/services/f3x-summary.service'; import { UserLoginData } from 'app/shared/models/user.model'; import { LabelPipe } from 'app/shared/pipes/label.pipe'; import { SharedModule } from 'app/shared/shared.module'; -import { environment } from 'environments/environment'; import { MessageService } from 'primeng/api'; import { CalendarModule } from 'primeng/calendar'; import { RadioButtonModule } from 'primeng/radiobutton'; @@ -16,11 +17,12 @@ import { SelectButtonModule } from 'primeng/selectbutton'; import { CreateF3XStep1Component, F3xReportTypeCategories } from './create-f3x-step1.component'; describe('CreateF3XStep1Component', () => { - let httpTestingController: HttpTestingController; let component: CreateF3XStep1Component; let router: Router; let fixture: ComponentFixture; + let f3xSummaryService: F3xSummaryService; const f3x: F3xSummary = F3xSummary.fromJSON({ + id: 999, coverage_from_date: '20220525', form_type: 'F3XN', report_code: 'Q1', @@ -44,6 +46,7 @@ describe('CreateF3XStep1Component', () => { ], declarations: [CreateF3XStep1Component, LabelPipe], providers: [ + F3xSummaryService, FormBuilder, MessageService, provideMockStore({ @@ -52,11 +55,11 @@ describe('CreateF3XStep1Component', () => { }), ], }).compileComponents(); - httpTestingController = TestBed.inject(HttpTestingController); }); beforeEach(() => { router = TestBed.inject(Router); + f3xSummaryService = TestBed.inject(F3xSummaryService); fixture = TestBed.createComponent(CreateF3XStep1Component); component = fixture.componentInstance; fixture.detectChanges(); @@ -82,16 +85,20 @@ describe('CreateF3XStep1Component', () => { }); it('#save should save a new f3x record', () => { + spyOn(f3xSummaryService, 'create').and.returnValue(of(f3x)); + const navigateSpy = spyOn(router, 'navigateByUrl'); component.form.patchValue({ ...f3x }); component.save(); - const req = httpTestingController.expectOne( - `${environment.apiUrl}/f3x-summaries/?fields_to_validate=filing_frequency,report_type_category,report_code,coverage_from_date,coverage_through_date,date_of_election,state_of_election,form_type` - ); - expect(req.request.method).toEqual('POST'); - httpTestingController.verify(); + expect(navigateSpy).toHaveBeenCalledWith('/reports'); + + navigateSpy.calls.reset(); + component.form.patchValue({ ...f3x }); + component.save('continue'); + expect(navigateSpy).toHaveBeenCalledWith('/reports/f3x/create/step2/999'); }); it('#save should not save with invalid f3x record', () => { + spyOn(f3xSummaryService, 'create').and.returnValue(of(f3x)); component.form.patchValue({ ...f3x }); component.form.patchValue({ form_type: 'NO-GOOD' }); component.save();