Skip to content

Commit

Permalink
refactor: climate api meta
Browse files Browse the repository at this point in the history
  • Loading branch information
chrismclarke committed Jan 12, 2024
1 parent 841b266 commit fe52724
Show file tree
Hide file tree
Showing 8 changed files with 47 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,24 @@ const API_ENDPOINT = 'https://api.epicsa.idems.international';
type ICallbackClient = (id:string)=>ReturnType<typeof createClient<paths>>

/** Type-safe http client with added support for callbacks */
type IClient = ReturnType<typeof createClient<paths>> & {useCallback:ICallbackClient}
type IClient = ReturnType<typeof createClient<paths>> & {useMeta:ICallbackClient}



interface IMetaEntry{
status:'pending' | 'success' | 'error' | 'unknown',
rawResponse?:Response,
}

type ICallbackStatus = 'pending' | 'success' | 'error' | 'unknown'

/**
* Service to interact with external PICSA Climate API
* All methods are exposed through a type-safe `client` property, or can additionally use
* a custom client that includes status notification updates via the `useCallback` method
* a custom client that includes status notification updates via the `useMeta` method
* @example
* Use custom callback that will show user notifications on error and record to service
* ```ts
* const {response, data, error} = await api.useCallback('myRequestId').POST(...)
* const {response, data, error} = await api.useMeta('myRequestId').POST(...)
* ```
* Use default client without additional callbacks
* ```ts
Expand All @@ -31,38 +37,38 @@ type ICallbackStatus = 'pending' | 'success' | 'error' | 'unknown'
@Injectable({ providedIn: 'root' })
export class ClimateDataApiService {

/** List of monitored callbacks with status */
public cb:Record<string,ICallbackStatus>={}
/** Request additional meta by id */
public meta:Record<string ,IMetaEntry>={}

/** Http client with type-definitions for API endpoints */
public client:IClient

constructor(private notificationService:PicsaNotificationService) {
const client = createClient<paths>({ baseUrl: API_ENDPOINT,mode:'cors' });
this.client = {...client,useCallback:()=>{
this.client = {...client,useMeta:()=>{
return client
}}
}


/**
* Provide a callback id which will be monitored alongside requests
* and provide user notification on error
* Provide an id which which will be updated alongside requests.
* The cache will also include interceptors to provide user notification on error
**/
public useCallback(id:string){
public useMeta(id:string){
const customFetch = this.createCustomFetchClient(id)
const callbackClient = createClient<paths>({ baseUrl: API_ENDPOINT,mode:'cors',fetch:customFetch });
return callbackClient
const customClient = createClient<paths>({ baseUrl: API_ENDPOINT,mode:'cors',fetch:customFetch });
return customClient
}

/** Create a custom implementation of fetch client to handle status updates and notifications */
private createCustomFetchClient(id:string){
return async (...args:Parameters<typeof window['fetch']>)=>{
this.cb[id]='pending'
this.meta[id]={status:'pending'}
const response = await window.fetch(...args);
const callbackStatus = this.getCallbackStatus(response.status)
this.cb[id]= callbackStatus
if(callbackStatus==='error' ){
this.meta[id].status = this.getCallbackStatus(response.status)
this.meta[id].rawResponse = response
if(this.meta[id].status ==='error' ){
await this.showCustomFetchErrorMessage(id,response)
}
return response
Expand All @@ -84,7 +90,7 @@ export class ClimateDataApiService {
}
}

private getCallbackStatus(statusCode:number):ICallbackStatus{
private getCallbackStatus(statusCode:number):IMetaEntry['status']{
if(200 <= statusCode && statusCode <=299) return 'success'
if(400 <= statusCode && statusCode <=499) return 'error'
if(500 <= statusCode && statusCode <=599) return 'error'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ export class ClimateDataDashboardService extends PicsaAsyncService {
}

private async checkStatus() {
const { client } = this.api;
const { response } = await client.GET('/v1/status/');
this.apiStatus = response.status;
await this.api.useMeta('serverStatus').GET('/v1/status/');
}

private async listStations() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<div class="page-content">
<div style="display: flex; align-items: center">
<h2 style="flex: 1">Climate Data</h2>
@if(service.apiStatus; as status){
@if(api.meta.serverStatus; as meta){
<div class="server-status">
Server Status <span class="status-code" [attr.data-status]="status">{{ status }}</span>
Server Status
<span class="status-code" [attr.data-status]="meta.rawResponse?.status">{{ meta.rawResponse?.status }}</span>
</div>
}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { RouterModule } from '@angular/router';
import { IMapMarker, PicsaMapComponent } from '@picsa/shared/features/map/map';

import { ClimateDataDashboardService, IStationRow } from '../../climate-data.service';
import { ClimateDataApiService } from '../../climate-data-api.service';

@Component({
selector: 'dashboard-climate-data-home',
Expand All @@ -18,7 +19,7 @@ export class ClimateDataHomeComponent implements OnInit {

public mapMarkers: IMapMarker[];

constructor(public service: ClimateDataDashboardService) {}
constructor(public service: ClimateDataDashboardService, public api: ClimateDataApiService) {}

async ngOnInit() {
await this.service.ready();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div style="display: flex; align-items: center">
<h3 style="flex: 1">Rainfall Summary</h3>
<button mat-stroked-button (click)="refreshData()" [disabled]="api.cb['rainfallSummary']==='pending'">
<mat-icon [class.spin]="api.cb['rainfallSummary']==='pending'">autorenew</mat-icon>
<button mat-stroked-button (click)="refreshData()" [disabled]="res.status==='pending'">
<mat-icon [class.spin]="res.status==='pending'">autorenew</mat-icon>
Refresh Data
</button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,24 @@ export class RainfallSummaryComponent implements AfterViewInit {
paginatorSizes: [25, 50],
};

public get res() {
return this.api.meta.rainfallSummary || {};
}

ngAfterViewInit() {
// TODO - retrieve from server
this.loadData(RAINFALL_SUMMARY_MOCK);
}

public async refreshData() {
const { station_id, country_code } = this.service.activeStation;
const { response, data, error } = await this.api
.useCallback('rainfallSummary')
.POST('/v1/annual_rainfall_summaries/', {
body: {
country: `${country_code}` as any,
station_id: `${station_id}`,
summaries: ['annual_rain', 'start_rains', 'end_rains', 'end_season', 'seasonal_rain', 'seasonal_length'],
},
});
const { response, data, error } = await this.api.useMeta('rainfallSummary').POST('/v1/annual_rainfall_summaries/', {
body: {
country: `${country_code}` as any,
station_id: `${station_id}`,
summaries: ['annual_rain', 'start_rains', 'end_rains', 'end_season', 'seasonal_rain', 'seasonal_length'],
},
});
console.log({ response, data, error });
this.loadData(data as any);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ <h2>{{ station.station_name }}</h2>
}
</table>
<dashboard-climate-rainfall-summary style="margin-top: 2rem"></dashboard-climate-rainfall-summary>
} @else {
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
}
</div>
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { ActivatedRoute } from '@angular/router';
import { PicsaLoadingComponent } from '@picsa/shared/features/loading/loading';
import { PicsaNotificationService } from '@picsa/shared/services/core/notification.service';

import { ClimateDataDashboardService } from '../../climate-data.service';
Expand All @@ -9,7 +11,7 @@ import { RainfallSummaryComponent } from './components/rainfall-summary/rainfall
@Component({
selector: 'dashboard-station-page',
standalone: true,
imports: [CommonModule, RainfallSummaryComponent],
imports: [CommonModule, MatProgressBarModule, RainfallSummaryComponent, PicsaLoadingComponent],
templateUrl: './station-page.component.html',
styleUrls: ['./station-page.component.scss'],
})
Expand Down

0 comments on commit fe52724

Please sign in to comment.