Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Committee Detail Links #301

Merged
merged 7 commits into from
May 25, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { provideMockStore } from '@ngrx/store/testing';
import { CommitteeAccount } from 'app/shared/models/committee-account.model';
import { AccountInfoComponent } from './account-info.component';
import { DividerModule } from 'primeng/divider';
import { HttpClientTestingModule } from '@angular/common/http/testing';

describe('AccountInfoComponent', () => {
let component: AccountInfoComponent;
Expand Down Expand Up @@ -78,7 +79,7 @@ describe('AccountInfoComponent', () => {
}),
],
declarations: [AccountInfoComponent],
imports: [DividerModule],
imports: [DividerModule, HttpClientTestingModule],
}).compileComponents();
});

Expand Down
28 changes: 19 additions & 9 deletions front-end/src/app/profile/account-info/account-info.component.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,44 @@
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { CommitteeAccount } from 'app/shared/models/committee-account.model';
import { FecFiling } from 'app/shared/models/fec-filing.model';
import { FecApiService } from 'app/shared/services/fec-api.service';
import { selectCommitteeAccount } from 'app/store/committee-account.selectors';
import { Observable } from 'rxjs';
import { Observable, switchMap } from 'rxjs';

@Component({
selector: 'app-profile',
templateUrl: './account-info.component.html',
styleUrls: ['./account-info.component.scss']
styleUrls: ['./account-info.component.scss'],
})
export class AccountInfoComponent implements OnInit {
committeeAccount$: Observable<CommitteeAccount> | null = null;
mostRecentFilingPdfUrl: string | null | undefined = undefined;

constructor(private store: Store) { }
constructor(private store: Store, private fecApiService: FecApiService) {}

ngOnInit(): void {
this.committeeAccount$ = this.store.select(selectCommitteeAccount);
this.committeeAccount$
.pipe(switchMap((committeeAccount) => this.fecApiService.getCommitteeRecentFiling(committeeAccount.committee_id)))
.subscribe((mostRecentFiling: FecFiling | undefined) => {
this.mostRecentFilingPdfUrl = mostRecentFiling?.pdf_url;
});
}

/**
* This sends the user to their F3X on fec.gov.
*/
* This sends the user to their F3X on fec.gov.
*/
viewF3X(): void {
return;
if (this.mostRecentFilingPdfUrl) {
window.open(this.mostRecentFilingPdfUrl, '_blank');
}
}

/**
* This sends the user to their F3X on fec.gov.
*/
* This sends the user to their F3X on fec.gov.
*/
updateForm1(): void {
return;
window.open('https://webforms.fec.gov/webforms/form1/index.htm', '_blank');
}
}
19 changes: 10 additions & 9 deletions front-end/src/app/shared/models/fec-api.model.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { CommitteeAccount } from "./committee-account.model";
import { CommitteeAccount } from './committee-account.model';
import { FecFiling } from './fec-filing.model';

export type FecApiPagination = {
page: number;
per_page: number;
count: number;
pages: number;
}
page: number;
per_page: number;
count: number;
pages: number;
};

export type FecApiPaginatedResponse = {
api_version: string;
pagination: FecApiPagination
results: CommitteeAccount[];
api_version: string;
pagination: FecApiPagination;
results: CommitteeAccount[] | FecFiling[];
};
72 changes: 72 additions & 0 deletions front-end/src/app/shared/models/fec-filing.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { plainToClass } from 'class-transformer';
import { BaseModel } from './base.model';

export class FecFiling extends BaseModel {
additional_bank_names: string[] | null = null;
amendment_chain: number[] | null = null;
amendment_indicator: string | null = null;
amendment_version: number | null = null;
bank_depository_city: string | null = null;
bank_depository_name: string | null = null;
bank_depository_state: string | null = null;
bank_depository_street_1: string | null = null;
bank_depository_street_2: string | null = null;
bank_depository_zip: string | null = null;
beginning_image_number: string | null = null;
candidate_id: string | null = null;
candidate_name: string | null = null;
cash_on_hand_beginning_period: number | null = null;
cash_on_hand_end_period: number | null = null;
committee_id: string | null = null;
committee_name: string | null = null;
committee_type: string | null = null;
coverage_end_date: Date | null = null;
coverage_start_date: Date | null = null;
csv_url: string | null = null;
cycle: number | null = null;
debts_owed_by_committee: number | null = null;
debts_owed_to_committee: number | null = null;
document_description: string | null = null;
document_type: string | null = null;
document_type_full: string | null = null;
election_year: number | null = null;
ending_image_number: string | null = null;
fec_file_id: string | null = null;
fec_url: string | null = null;
file_number: number | null = null;
form_category: string | null = null;
form_type: string | null = null;
house_personal_funds: number | null = null;
html_url: string | null = null;
is_amended: boolean | null = null;
means_filed: string | null = null;
most_recent: boolean | null = null;
most_recent_file_number: number | null = null;
net_donations: number | null = null;
office: string | null = null;
opposition_personal_funds: number | null = null;
pages: number | null = null;
party: string | null = null;
pdf_url: string | null = null;
previous_file_number: number | null = null;
primary_general_indicator: string | null = null;
receipt_date: Date | null = null;
report_type: string | null = null;
report_type_full: string | null = null;
report_year: number | null = null;
request_type: string | null = null;
senate_personal_funds: number | null = null;
state: string | null = null;
sub_id: string | null = null;
total_communication_cost: number | null = null;
total_disbursements: number | null = null;
total_independent_expenditures: number | null = null;
total_individual_contributions: number | null = null;
total_receipts: number | null = null;
treasurer_name: string | null = null;
update_date: Date | null = null;
// prettier-ignore
static fromJSON(json: any): FecFiling { // eslint-disable-line @typescript-eslint/no-explicit-any
return plainToClass(FecFiling, json);
}
}
88 changes: 80 additions & 8 deletions front-end/src/app/shared/services/fec-api.service.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,107 @@
import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpParams } from '@angular/common/http';
import { environment } from '../../../environments/environment';
import { Injectable } from '@angular/core';
import { map, Observable } from 'rxjs';
import { map, Observable, of, switchMap } from 'rxjs';
import { CommitteeAccount } from '../models/committee-account.model';
import { FecApiPaginatedResponse } from 'app/shared/models/fec-api.model';
import { FecFiling } from '../models/fec-filing.model';

export type QueryParamsType = { [param: string]: string | number | boolean | readonly (string | number | boolean)[] };

@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class FecApiService {
private apiKey: string | null = environment.fecApiKey;

constructor(private http: HttpClient) { }
constructor(private http: HttpClient) {}

getHeaders() {
return {
'Content-Type': 'application/json',
};
}

getQueryParams(queryParams: QueryParamsType = {}) {
const allParams: QueryParamsType = { ...{ api_key: this.apiKey || '' }, ...queryParams };
return new HttpParams({ fromObject: allParams });
}

/**
* Gets the commitee account details.
*
* @return {Observable} The commitee details.
*/
public getDetails(committeeId: string | null): Observable<CommitteeAccount > {
public getDetails(committeeId: string | null): Observable<CommitteeAccount> {
if (!committeeId) {
throw new Error('No Committee Id provided.')
throw new Error('No Committee Id provided.');
}
const headers = this.getHeaders();
return this.http.get<FecApiPaginatedResponse>(`${environment.fecApiCommitteeUrl}/${committeeId}/?api_key=${this.apiKey}`, { headers: headers })
.pipe(map((response: FecApiPaginatedResponse) => response.results[0] || undefined));
const params = this.getQueryParams();
return this.http
.get<FecApiPaginatedResponse>(`${environment.fecApiUrl}committee/${committeeId}/`, {
headers: headers,
params: params,
})
.pipe(map((response: FecApiPaginatedResponse) => (response.results[0] as CommitteeAccount) || undefined));
}

/**
* Gets the most recent F1 filing
*
* Fist checks the realtime enpdpoint for a recent F1 filing. If none is found, a request is
* made to a different endpoint that is updated nightly. The realtime endpoint will have
* more recent filings, but does not provide filings older than 6 months.
* The nightly endpoint keeps a longer history
*
* @return {Observable<FecFiling | undefined>} Most recent F1 filing.
*/
public getCommitteeRecentFiling(committeeId: string | null): Observable<FecFiling | undefined> {
if (!committeeId) {
throw new Error('No Committee Id provided.');
}
const headers = this.getHeaders();
const nightlyEndpointParams = this.getQueryParams({
sort: '-receipt_date',
per_page: 1,
page: 1,
committee_id: committeeId,
form_type: 'F1',
});

const nightlyEndpoint = this.http
.get<FecApiPaginatedResponse>(`${environment.fecApiUrl}filings/`, {
headers: headers,
params: nightlyEndpointParams,
})
.pipe(
map((response: FecApiPaginatedResponse) => {
return (response.results as FecFiling[]).find((filing: FecFiling) => filing.form_type?.startsWith('F1'));
})
);

const realTimeEndpoint = this.http
.get<FecApiPaginatedResponse>(`${environment.fecApiUrl}efile/filings/`, {
headers: headers,
params: this.getQueryParams({
committee_id: committeeId,
sort: '-receipt_date',
}),
})
.pipe(
map((response: FecApiPaginatedResponse) => {
return (response.results as FecFiling[]).find((filing: FecFiling) => filing.form_type?.startsWith('F1'));
})
);

// Check real time endpoint for result. If it doesn't have one, check the nightly endpoint
return realTimeEndpoint.pipe(
switchMap((realTimeResult) => {
if (realTimeResult) {
return of(realTimeResult);
}
return nightlyEndpoint;
})
);
}
}
2 changes: 1 addition & 1 deletion front-end/src/environments/environment.cloud.gov.dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const environment = {
apiUrl: 'https://fecfile-web-api-dev.app.cloud.gov/api/v1',
appTitle: 'FECfile',
dcfConverterApiUrl: 'https://dev-efile-api.efdev.fec.gov/dcf_converter/v1',
fecApiCommitteeUrl: 'https://api.open.fec.gov/v1/committee',
fecApiUrl: 'https://api.open.fec.gov/v1/',
fecApiKey: 'DVoLzo07NBfrbDZPj0LJs3PS0GIRL3fk4eOp7Zo6',
userCanSetFilingFrequency: true,
};
Expand Down
2 changes: 1 addition & 1 deletion front-end/src/environments/environment.cloud.gov.prod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const environment = {
apiUrl: 'https://fecfile-web-api-prod.app.cloud.gov/api/v1',
appTitle: 'FECfile',
dcfConverterApiUrl: 'https://dev-efile-api.efdev.fec.gov/dcf_converter/v1',
fecApiCommitteeUrl: 'https://api.open.fec.gov/v1/committee',
fecApiUrl: 'https://api.open.fec.gov/v1/',
fecApiKey: 'EjSyhVzlpDG06QCcWaMtLdDv8qLu2GAPbffHrXRF',
userCanSetFilingFrequency: true,
};
Expand Down
2 changes: 1 addition & 1 deletion front-end/src/environments/environment.cloud.gov.stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const environment = {
apiUrl: 'https://fecfile-web-api-stage.app.cloud.gov/api/v1',
appTitle: 'FECfile',
dcfConverterApiUrl: 'https://dev-efile-api.efdev.fec.gov/dcf_converter/v1',
fecApiCommitteeUrl: 'https://api.open.fec.gov/v1/committee',
fecApiUrl: 'https://api.open.fec.gov/v1/',
fecApiKey: 'fWkpM7VN0mjtZXsGyfCJy2zeVtgePB2QhiyZcx2X',
userCanSetFilingFrequency: true,
};
Expand Down
2 changes: 1 addition & 1 deletion front-end/src/environments/environment.local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const environment = {
apiUrl: 'http://localhost:8080/api/v1',
appTitle: 'FECfile',
dcfConverterApiUrl: 'https://dev-efile-api.efdev.fec.gov/dcf_converter/v1',
fecApiCommitteeUrl: 'https://api.open.fec.gov/v1/committee',
fecApiUrl: 'https://api.open.fec.gov/v1/',
fecApiKey: 'DEMO_KEY',
userCanSetFilingFrequency: true,
};
2 changes: 1 addition & 1 deletion front-end/src/environments/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const environment = {
apiUrl: 'https://localhost/api/v1',
appTitle: 'FECfile',
dcfConverterApiUrl: 'https://dev-efile-api.efdev.fec.gov/dcf_converter/v1',
fecApiCommitteeUrl: 'https://api.open.fec.gov/v1/committee',
fecApiUrl: 'https://api.open.fec.gov/v1/',
fecApiKey: 'DEMO_KEY',
userCanSetFilingFrequency: true,
};
Expand Down