Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions .spelling.dic
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,4 @@ unpairing
unrs
unsubscription
xmark
csrftoken
5 changes: 5 additions & 0 deletions proxy.conf.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ export default {
changeOrigin: true,
logLevel: 'debug',
secure: false,
onProxyReq: (proxyReq) => {
const target = process.env.SEED_HOST ?? 'http://127.0.0.1:8000'
proxyReq.setHeader('origin', target)
proxyReq.setHeader('referer', `${target}/`)
},
},
'/media/': {
target: process.env.SEED_HOST ?? 'http://127.0.0.1:8000',
Expand Down
2 changes: 2 additions & 0 deletions src/@seed/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export * from './geocode'
export * from './filter-group'
export * from './groups'
export * from './inventory'
export * from './inventory-report'
export * from './label'
export * from './mapping'
export * from './matching'
Expand All @@ -21,6 +22,7 @@ export * from './organization'
export * from './pairing'
export * from './postoffice'
export * from './program'
export * from './report-configuration'
export * from './progress'
export * from './salesforce'
export * from './scenario'
Expand Down
2 changes: 2 additions & 0 deletions src/@seed/api/inventory-report/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './inventory-report.service'
export * from './inventory-report.types'
86 changes: 86 additions & 0 deletions src/@seed/api/inventory-report/inventory-report.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import type { HttpErrorResponse } from '@angular/common/http'
import { HttpClient, HttpParams } from '@angular/common/http'
import { inject, Injectable } from '@angular/core'
import type { Observable } from 'rxjs'
import { catchError, map } from 'rxjs'
import { ErrorService } from '@seed/services'
import type { AggregatedReportDataResponse, ReportDataResponse } from './inventory-report.types'

@Injectable({ providedIn: 'root' })
export class InventoryReportService {
private _errorService = inject(ErrorService)
private _httpClient = inject(HttpClient)

getReportData(
orgId: number,
xVar: string,
yVar: string,
cycleIds: number[],
accessLevelInstanceId: number | null,
filterGroupId: number | null,
): Observable<ReportDataResponse['data']> {
const url = `/api/v3/organizations/${orgId}/report/`
let params = new HttpParams().set('x_var', xVar).set('y_var', yVar)
for (const id of cycleIds) {
params = params.append('cycle_ids', id)
}
if (accessLevelInstanceId != null) params = params.set('access_level_instance_id', accessLevelInstanceId)
if (filterGroupId != null) params = params.set('filter_group_id', filterGroupId)

return this._httpClient.get<ReportDataResponse>(url, { params }).pipe(
map(({ data }) => data),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error fetching report data')
}),
)
}

getAggregatedReportData(
orgId: number,
xVar: string,
yVar: string,
cycleIds: number[],
accessLevelInstanceId: number | null,
filterGroupId: number | null,
aggregationType: string,
): Observable<AggregatedReportDataResponse['aggregated_data']> {
const url = `/api/v3/organizations/${orgId}/report_aggregated/`
let params = new HttpParams().set('x_var', xVar).set('y_var', yVar).set('aggregationType', aggregationType)
for (const id of cycleIds) {
params = params.append('cycle_ids', id)
}
if (accessLevelInstanceId != null) params = params.set('access_level_instance_id', accessLevelInstanceId)
if (filterGroupId != null) params = params.set('filter_group_id', filterGroupId)

return this._httpClient.get<AggregatedReportDataResponse>(url, { params }).pipe(
map(({ aggregated_data }) => aggregated_data),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error fetching aggregated report data')
}),
)
}

exportReportData(
orgId: number,
xVar: string,
xLabel: string,
yVar: string,
yLabel: string,
cycleIds: number[],
filterGroupId: number | null,
): Observable<Blob> {
const url = `/api/v3/organizations/${orgId}/report_export/`
let params = new HttpParams().set('x_var', xVar).set('x_label', xLabel).set('y_var', yVar).set('y_label', yLabel)
for (const id of cycleIds) {
params = params.append('cycle_ids', id)
}
if (filterGroupId != null) params = params.set('filter_group_id', filterGroupId)

return this._httpClient.get(url, { params, responseType: 'arraybuffer' }).pipe(
map((buffer) => new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error exporting report data')
}),
)
}
}
52 changes: 52 additions & 0 deletions src/@seed/api/inventory-report/inventory-report.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
export type ReportChartPoint = {
id: number;
yr_e: string;
x: number | string;
y: number | string;
display_name?: string;
}

export type ReportPropertyCount = {
yr_e: string;
cycle: string;
num_properties: number;
'num_properties_w-data': number;
color?: string;
}

export type ReportAxisStatValues = {
values: number[];
children?: Record<string, number[]>;
}

export type ReportAxisData = Record<string, Record<string, ReportAxisStatValues>>

export type ReportDataResponse = {
status: string;
data: {
chart_data: ReportChartPoint[];
property_counts: ReportPropertyCount[];
axis_data: ReportAxisData;
};
}

export type AggregatedChartPoint = {
x: number | string;
y: number | string;
yr_e: string;
}

export type AggregatedReportDataResponse = {
status: string;
aggregated_data: {
chart_data: AggregatedChartPoint[];
property_counts: ReportPropertyCount[];
};
}

export type AxisVariable = {
name: string;
label: string;
varName: string;
axisLabel: string;
}
2 changes: 2 additions & 0 deletions src/@seed/api/report-configuration/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './report-configuration.service'
export * from './report-configuration.types'
94 changes: 94 additions & 0 deletions src/@seed/api/report-configuration/report-configuration.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import type { HttpErrorResponse } from '@angular/common/http'
import { HttpClient } from '@angular/common/http'
import { inject, Injectable } from '@angular/core'
import type { Observable } from 'rxjs'
import { BehaviorSubject, catchError, map, tap } from 'rxjs'
import { ErrorService } from '@seed/services'
import { naturalSort } from '@seed/utils'
import { SnackBarService } from 'app/core/snack-bar/snack-bar.service'
import { UserService } from '../user'
import type {
ReportConfiguration,
ReportConfigurationResponse,
ReportConfigurationsResponse,
ReportConfigurationUpsertPayload,
} from './report-configuration.types'

@Injectable({ providedIn: 'root' })
export class ReportConfigurationService {
private _errorService = inject(ErrorService)
private _httpClient = inject(HttpClient)
private _reportConfigurations = new BehaviorSubject<ReportConfiguration[]>([])
private _snackBar = inject(SnackBarService)
private _userService = inject(UserService)

reportConfigurations$ = this._reportConfigurations.asObservable()

constructor() {
this._userService.currentOrganizationId$
.pipe(
tap((orgId) => {
this.list(orgId)
}),
)
.subscribe()
}

list(orgId: number) {
const url = `/api/v3/organizations/${orgId}/report_configurations`
this._httpClient
.get<ReportConfigurationsResponse>(url)
.pipe(
map(({ data }) => {
const configs = data.toSorted((a, b) => naturalSort(a.name, b.name))
this._reportConfigurations.next(configs)
return configs
}),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error fetching report configurations')
}),
)
.subscribe()
}

create(orgId: number, data: ReportConfigurationUpsertPayload): Observable<ReportConfiguration> {
const url = `/api/v3/report_configurations/?organization_id=${orgId}`
return this._httpClient.post<ReportConfigurationResponse>(url, data).pipe(
map(({ data: config }) => config),
tap(() => {
this.list(orgId)
this._snackBar.success('Created report configuration')
}),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error creating report configuration')
}),
)
}

update(orgId: number, id: number, data: ReportConfigurationUpsertPayload): Observable<ReportConfiguration> {
const url = `/api/v3/report_configurations/${id}/?organization_id=${orgId}`
return this._httpClient.put<ReportConfigurationResponse>(url, data).pipe(
map(({ data: config }) => config),
tap(() => {
this.list(orgId)
this._snackBar.success('Updated report configuration')
}),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error updating report configuration')
}),
)
}

delete(orgId: number, id: number): Observable<unknown> {
const url = `/api/v3/report_configurations/${id}/?organization_id=${orgId}`
return this._httpClient.delete<unknown>(url).pipe(
tap(() => {
this.list(orgId)
this._snackBar.success('Deleted report configuration')
}),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error deleting report configuration')
}),
)
}
}
22 changes: 22 additions & 0 deletions src/@seed/api/report-configuration/report-configuration.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export type ReportConfiguration = {
id: number | null;
name: string;
x_column: string | null;
y_column: string | null;
access_level_instance_id: number | null;
access_level_depth: number | null;
cycles: number[];
filter_group_id: number | null;
}

export type ReportConfigurationUpsertPayload = Omit<ReportConfiguration, 'id'>

export type ReportConfigurationsResponse = {
status: string;
data: ReportConfiguration[];
}

export type ReportConfigurationResponse = {
status: string;
data: ReportConfiguration;
}
7 changes: 5 additions & 2 deletions src/app/core/auth/auth.provider.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { provideHttpClient, withInterceptors } from '@angular/common/http'
import { provideHttpClient, withInterceptors, withXsrfConfiguration } from '@angular/common/http'
import type { EnvironmentProviders, Provider } from '@angular/core'
import { inject, provideEnvironmentInitializer } from '@angular/core'
import { authInterceptor } from 'app/core/auth/auth.interceptor'
import { AuthService } from 'app/core/auth/auth.service'

export const provideAuth = (): (Provider | EnvironmentProviders)[] => {
return [provideHttpClient(withInterceptors([authInterceptor])), provideEnvironmentInitializer(() => inject(AuthService))]
return [
provideHttpClient(withInterceptors([authInterceptor]), withXsrfConfiguration({ cookieName: 'csrftoken', headerName: 'X-CSRFToken' })),
provideEnvironmentInitializer(() => inject(AuthService)),
]
}
Loading
Loading