Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
21 changes: 21 additions & 0 deletions src/@seed/api/cache/cache.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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 { catchError } from 'rxjs'
import { ErrorService } from '@seed/services'

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

getCacheEntry(orgId: number, uniqueId: number): Observable<unknown> {
const url = `/api/v3/cache_entries/${uniqueId}/?organization_id=${orgId}`
return this._httpClient.get<unknown>(url).pipe(
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error fetching cache entry')
}),
)
}
}
1 change: 1 addition & 0 deletions src/@seed/api/cache/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './cache.service'
1 change: 1 addition & 0 deletions src/@seed/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './analysis'
export * from './audit-template'
export * from './cache'
export * from './column'
export * from './column-mapping-profile'
export * from './config'
Expand Down
14 changes: 2 additions & 12 deletions src/@seed/api/inventory/inventory.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,19 +352,9 @@ export class InventoryService {
)
}

startInventoryExport(orgId: number): Observable<ProgressResponse> {
startInventoryExport(orgId: number, data: InventoryExportData): Observable<ProgressResponse> {
const url = `/api/v3/tax_lot_properties/start_export/?organization_id=${orgId}`
return this._httpClient.get<ProgressResponse>(url).pipe(
take(1),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error starting export')
}),
)
}

exportInventory(orgId: number, type: InventoryType, data: InventoryExportData): Observable<Blob> {
const url = `/api/v3/tax_lot_properties/export/?inventory_type=${type}&organization_id=${orgId}`
return this._httpClient.post(url, data, { responseType: 'blob' }).pipe(
return this._httpClient.post<ProgressResponse>(url, data).pipe(
take(1),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error starting export')
Expand Down
6 changes: 3 additions & 3 deletions src/@seed/api/mapping/mapping.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { HttpClient } from '@angular/common/http'
import { inject, Injectable } from '@angular/core'
import { catchError, map, type Observable } from 'rxjs'
import { ErrorService } from '@seed/services'
import type { MappedData, MappingResultsResponse } from '../dataset'
import type { MappedData } from '../dataset'
import type { ProgressResponse, SubProgressResponse } from '../progress'
import { UserService } from '../user'
import type { FirstFiveRowsResponse, MappingSuggestionsResponse, MatchingResultsResponse, RawColumnNamesResponse } from './mapping.types'
Expand Down Expand Up @@ -66,9 +66,9 @@ export class MappingService {
)
}

mappingResults(orgId: number, importFileId: number): Observable<MappingResultsResponse> {
mappingResults(orgId: number, importFileId: number): Observable<ProgressResponse> {
const url = `/api/v3/import_files/${importFileId}/mapping_results/?organization_id=${orgId}`
return this._httpClient.post<MappingResultsResponse>(url, {})
return this._httpClient.post<ProgressResponse>(url, {})
.pipe(
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error fetching mapping results')
Expand Down
23 changes: 22 additions & 1 deletion src/@seed/services/uploader/uploader.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { ProgressResponse } from '@seed/api'
import { ErrorService } from '../error'
import type {
CheckProgressLoopParams,
ExportDataType,
GreenButtonMeterPreview,
MeterPreviewResponse,
ProgressBarObj,
Expand Down Expand Up @@ -51,7 +52,7 @@ export class UploaderService {
switchMap(() => this.checkProgress(progressKey)),
tap((response) => {
this._updateProgressBarObj({ data: response, offset, multiplier, progressBarObj })
if (response.status === 'success') successFn()
if (response.status === 'success') successFn(response)
}),
catchError(() => {
// TODO the interval needs to continue if the error was network-related
Expand Down Expand Up @@ -176,6 +177,26 @@ export class UploaderService {
)
}

stringToBlob(data: string, exportType: ExportDataType) {
const base64ToBlob = (base64: string): Blob => {
const binary = atob(base64)
const bytes = new Uint8Array(binary.length)
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i)
}

return new Blob([bytes], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
}

const blobMap: Record<ExportDataType, () => Blob> = {
csv: () => new Blob([data], { type: 'text/csv' }),
xlsx: () => base64ToBlob(data),
geojson: () => new Blob([JSON.stringify(data, null, '\t')], { type: 'application/geo+json' }),
}

return blobMap[exportType]()
}

/*
* Updates the progress bar object with incoming progress data.
*/
Expand Down
4 changes: 3 additions & 1 deletion src/@seed/services/uploader/uploader.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export type CheckProgressLoopParams = {
progressKey: string;
offset?: number;
multiplier?: number;
successFn?: () => void;
successFn?: (response: ProgressResponse) => void;
failureFn?: () => void;
progressBarObj: ProgressBarObj;
subProgress?: boolean;
Expand Down Expand Up @@ -68,3 +68,5 @@ export type MeterPreviewResponse = {
unlinkable_pm_ids: number[];
validated_type_units: ValidatedTypeUnit[];
}

export type ExportDataType = 'csv' | 'xlsx' | 'geojson'
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
</mat-step>

<mat-step [completed]="completed[2]" label="Mapping" editable="false">
<seed-progress-bar [progress]="progressBarObj.progress" [total]="progressBarObj.total" title="Mapping Data..."></seed-progress-bar>
<seed-progress-bar [progress]="progressBarObj.progress" [total]="progressBarObj.total" [title]="progressTitle"></seed-progress-bar>
</mat-step>

<mat-step [completed]="completed[3]" label="Review Mappings" editable="false">
Expand Down
28 changes: 22 additions & 6 deletions src/app/modules/datasets/data-mappings/data-mapping.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ActivatedRoute } from '@angular/router'
import { AgGridAngular } from 'ag-grid-angular'
import { catchError, filter, forkJoin, of, Subject, switchMap, take, takeUntil, tap } from 'rxjs'
import type { Column, ColumnMappingProfile, ColumnMappingProfileType, Cycle, ImportFile, MappingResultsResponse, MappingSuggestionsResponse, Organization, ProgressResponse } from '@seed/api'
import { ColumnMappingProfileService, ColumnService, CycleService, DatasetService, MappingService, OrganizationService, UserService } from '@seed/api'
import { CacheService, ColumnMappingProfileService, ColumnService, CycleService, DatasetService, MappingService, OrganizationService, UserService } from '@seed/api'
import { PageComponent, ProgressBarComponent } from '@seed/components'
import { MaterialImports } from '@seed/materials'
import { UploaderService } from '@seed/services/uploader'
Expand Down Expand Up @@ -40,6 +40,7 @@ export class DataMappingComponent implements OnDestroy, OnInit {
@ViewChild(MapDataComponent) mapDataComponent!: MapDataComponent
@ViewChild(MatchMergeComponent) matchMergeComponent!: MatchMergeComponent
private readonly _unsubscribeAll$ = new Subject<void>()
private _cacheService = inject(CacheService)
private _columnMappingProfileService = inject(ColumnMappingProfileService)
private _columnService = inject(ColumnService)
private _cycleService = inject(CycleService)
Expand Down Expand Up @@ -71,12 +72,12 @@ export class DataMappingComponent implements OnDestroy, OnInit {
matchingTaxLotColumns: string[] = []
org: Organization
orgId: number
progressBarObj = this._uploaderService.defaultProgressBarObj
progressTitle = 'Mapping Data...'
propertyColumns: Column[]
rawColumnNames: string[] = []
taxlotColumns: Column[]

progressBarObj = this._uploaderService.defaultProgressBarObj

ngOnInit(): void {
// this._userService.currentOrganizationId$
this._organizationService.currentOrganization$
Expand Down Expand Up @@ -181,7 +182,6 @@ export class DataMappingComponent implements OnDestroy, OnInit {
this._snackBar.alert('Error starting mapping')
}
const successFn = () => {
this.nextStep(2)
this.getMappingResults()
}

Expand Down Expand Up @@ -214,10 +214,26 @@ export class DataMappingComponent implements OnDestroy, OnInit {
}

getMappingResults(): void {
this.nextStep(2)
this.progressTitle = 'Fetching Mapping Results...'
const successFn = ({ unique_id }: ProgressResponse) => {
this._cacheService.getCacheEntry(this.orgId, unique_id)
.pipe(
tap((response) => {
this.mappingResultsResponse = response as MappingResultsResponse
this.nextStep(2)
}),
take(1),
)
.subscribe()
}

this._mappingService.mappingResults(this.orgId, this.fileId)
.pipe(
tap((mappingResultsResponse) => { this.mappingResultsResponse = mappingResultsResponse }),
switchMap(({ progress_key }) => this._uploaderService.checkProgressLoop({
progressKey: progress_key,
successFn,
progressBarObj: this.progressBarObj,
})),
)
.subscribe()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,3 @@
>
</ag-grid-angular>
}

@if (loading) {
<div class="m-10">
<div class="mb-5 flex items-center space-x-2">
<mat-icon class="text-primary-600" svgIcon="fa-solid:hourglass-half"></mat-icon>
<div class="text-secondary text-xl font-medium leading-6">Fetching Mapping Results...</div>
</div>
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,7 @@ export class FormModalComponent implements OnInit {
this._ubidService
.update(this.data.orgId, this.data.viewId, this.data.ubid.id, ubidDetails, this.data.type)
.pipe(
tap((response) => {
console.log('response', response)
this.close(preferred)
}),
tap(() => { this.close(preferred) }),
)
.subscribe()
} else {
Expand All @@ -74,10 +71,7 @@ export class FormModalComponent implements OnInit {
this._ubidService
.create(this.data.orgId, this.data.viewId, ubidDetails, this.data.type)
.pipe(
tap((response) => {
console.log('response', response)
this.close(preferred)
}),
tap(() => { this.close(preferred) }),
)
.subscribe()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@
<mat-button-toggle class="flex-1" value="geojson">GeoJSON</mat-button-toggle>
</mat-button-toggle-group>

<mat-checkbox formControlName="include_notes">Include Notes</mat-checkbox>
@if (form.value.export_type === 'geojson') {
<mat-checkbox formControlName="include_meter_readings"
>Include Meter Readings <span class="text-secondary"> (Only recommended for small exports)</span>
</mat-checkbox>
} @else if (form.value.export_type === 'csv') {
<mat-checkbox formControlName="include_meter_readings"> Include Label Header</mat-checkbox>
} @else {
<div class="h-10"></div>
}
Expand Down
54 changes: 31 additions & 23 deletions src/app/modules/inventory/actions/export-modal.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { Component, inject, ViewChild } from '@angular/core'
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'
import type { MatStepper } from '@angular/material/stepper'
import { catchError, combineLatest, EMPTY, finalize, Subject, switchMap, takeUntil, tap } from 'rxjs'
import { InventoryService } from '@seed/api'
import { catchError, EMPTY, Subject, switchMap, take, takeUntil, tap } from 'rxjs'
import type { ProgressResponse } from '@seed/api'
import { CacheService, InventoryService } from '@seed/api'
import { ModalHeaderComponent, ProgressBarComponent } from '@seed/components'
import { MaterialImports } from '@seed/materials'
import { UploaderService } from '@seed/services'
Expand All @@ -18,6 +19,7 @@ import type { InventoryExportData, InventoryType } from '../inventory.types'
})
export class ExportModalComponent implements OnDestroy {
@ViewChild('stepper') stepper!: MatStepper
private _cacheService = inject(CacheService)
private _dialogRef = inject(MatDialogRef<ExportModalComponent>)
private _inventoryService = inject(InventoryService)
private _snackBar = inject(SnackBarService)
Expand All @@ -36,41 +38,48 @@ export class ExportModalComponent implements OnDestroy {
}

form = new FormGroup({
name: new FormControl<string>(null, Validators.required),
include_notes: new FormControl(true),
export_type: new FormControl<'csv' | 'xlsx' | 'geojson'>('csv', Validators.required),
include_label_header: new FormControl(false),
include_notes: new FormControl(false),
include_meter_readings: new FormControl(false),
name: new FormControl<string>(null, Validators.required),
})

export() {
this._inventoryService.startInventoryExport(this.data.orgId)
const successFn = ({ unique_id }: ProgressResponse) => {
this._cacheService.getCacheEntry(this.data.orgId, unique_id).pipe(
tap((response: { data: string }) => {
const blob = this.getBlob(response.data)
this.downloadData(blob)
this.close()
}),
take(1),
).subscribe()
}

this.initExport()
this._inventoryService.startInventoryExport(this.data.orgId, this.exportData)
.pipe(
tap(({ progress_key }) => { this.initExport(progress_key) }),
switchMap(() => this.pollExport()),
tap((response) => { this.downloadData(response[0]) }),
switchMap(({ progress_key }) => this._uploaderService.checkProgressLoop({
progressKey: progress_key,
successFn,
failureFn: () => { this.close() },
progressBarObj: this.progressBarObj,
})),
takeUntil(this._unsubscribeAll$),
catchError(() => { return EMPTY }),
finalize(() => { this.close() }),
)
.subscribe()
}

initExport(progress_key: string) {
initExport() {
this.stepper.next()
this.formatFilename()
this.formatExportData(this.filename, progress_key)
this.formatExportData(this.filename)
}

pollExport() {
const { orgId, type } = this.data
return combineLatest([
this._inventoryService.exportInventory(orgId, type, this.exportData),
this._uploaderService.checkProgressLoop({
progressKey: this.exportData.progress_key,
progressBarObj: this.progressBarObj,
}),
])
getBlob(data: string): Blob {
const exportType = this.form.value.export_type
return this._uploaderService.stringToBlob(data, exportType)
}

downloadData(data: Blob) {
Expand All @@ -91,15 +100,14 @@ export class ExportModalComponent implements OnDestroy {
}
}

formatExportData(filename: string, progress_key: string) {
formatExportData(filename: string) {
this.exportData = {
export_type: this.form.value.export_type,
filename,
ids: this.data.viewIds,
include_meter_readings: this.form.value.include_meter_readings,
include_notes: this.form.value.include_notes,
profile_id: this.data.profileId,
progress_key,
}
}

Expand Down
1 change: 0 additions & 1 deletion src/app/modules/inventory/inventory.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,5 +263,4 @@ export type InventoryExportData = {
include_meter_readings: boolean;
include_notes: boolean;
profile_id: number;
progress_key: string;
}