From a7df1287c0bb07402c6dc4f06b684089ace4223d Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Fri, 11 Oct 2024 11:00:05 -0700 Subject: [PATCH 1/8] feat: wip dashboard climate admin page --- .../dashboard/src/app/data/navLinks.ts | 6 + .../src/app/modules/climate/climate.module.ts | 5 + .../climate/pages/admin/admin.component.html | 17 +++ .../climate/pages/admin/admin.component.scss | 0 .../pages/admin/admin.component.spec.ts | 22 +++ .../climate/pages/admin/admin.component.ts | 132 ++++++++++++++++++ .../rainfall-summary/rainfall-summary.html | 2 +- .../rainfall-summary/rainfall-summary.ts | 27 +--- .../rainfall-summary.utils.ts | 21 +++ .../src/app/modules/climate/types/db.d.ts | 6 + 10 files changed, 214 insertions(+), 24 deletions(-) create mode 100644 apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.html create mode 100644 apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.scss create mode 100644 apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.spec.ts create mode 100644 apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.ts create mode 100644 apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.utils.ts diff --git a/apps/picsa-apps/dashboard/src/app/data/navLinks.ts b/apps/picsa-apps/dashboard/src/app/data/navLinks.ts index c0feb141c..be5304250 100644 --- a/apps/picsa-apps/dashboard/src/app/data/navLinks.ts +++ b/apps/picsa-apps/dashboard/src/app/data/navLinks.ts @@ -46,6 +46,12 @@ export const DASHBOARD_NAV_LINKS: INavLink[] = [ label: 'Forecasts', href: '/forecast', }, + { + label: 'Admin', + href: '/admin', + // TODO - auth role + // TODO - import from module? + }, ], }, { diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts index 8430cb47f..65ea0fa64 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts @@ -16,6 +16,11 @@ import { StationDetailsPageComponent } from './pages/station-details/station-det redirectTo: 'station', pathMatch: 'full', }, + { + path: 'admin', + loadComponent: () => import('./pages/admin/admin.component').then((m) => m.ClimateAdminPageComponent), + // TODO - add auth route guards + }, { path: 'station', component: ClimateStationPageComponent, diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.html b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.html new file mode 100644 index 000000000..f6b297598 --- /dev/null +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.html @@ -0,0 +1,17 @@ +
+

Admin

+
+

(WiP) dashboard to allow quick download of Rainfall Summaries to include in the app

+ +
+ + + + + +
diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.scss b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.spec.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.spec.ts new file mode 100644 index 000000000..6e49b1c25 --- /dev/null +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ClimateAdminPageComponent } from './admin.component'; + +describe('ClimateAdminPageComponent', () => { + let component: ClimateAdminPageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ClimateAdminPageComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ClimateAdminPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.ts new file mode 100644 index 000000000..3da808b8e --- /dev/null +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.ts @@ -0,0 +1,132 @@ +import { CommonModule } from '@angular/common'; +import { Component, computed, effect, signal } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { IDataTableOptions, PicsaDataTableComponent } from '@picsa/shared/features'; +import { SupabaseService } from '@picsa/shared/services/core/supabase'; +import { arrayToHashmapArray } from '@picsa/utils'; +import download from 'downloadjs'; +import JSZip from 'jszip'; +import { unparse } from 'papaparse'; + +import { DeploymentDashboardService } from '../../../deployment/deployment.service'; +import { ClimateService } from '../../climate.service'; +import type { IAnnualRainfallSummariesData, IClimateProductRow, IStationRow } from '../../types'; +import { hackConvertAPIDataToLegacyFormat } from '../station-details/components/rainfall-summary/rainfall-summary.utils'; + +/** + * TODOs + * - [ ] Improve types throughout + * - [ ] Add auth and navlink route guards + * - [ ] Add button to force api fetch + * - [ ] Update table to include country_code for filtered query + * - [ ] Update table to include updated_at + * - [ ] Consider rename climate_products table to just be climate_rainfall_summary (separate data/metadata cols) + * - [ ] Improve data conversion so raw data can be displayed in dashboard (and climate products use) + */ + +@Component({ + selector: 'dashboard-climate-admin-page', + standalone: true, + imports: [CommonModule, MatButtonModule, MatIconModule, PicsaDataTableComponent], + templateUrl: './admin.component.html', + styleUrl: './admin.component.scss', +}) +export class ClimateAdminPageComponent { + public tableData = computed(() => { + const stations = this.service.stations(); + const products = this.climateProducts(); + return this.generateTableSummaryData(stations, products); + }); + public tableOptions: IDataTableOptions = { + displayColumns: ['station_id', 'type', 'updated_at', 'start_year', 'end_year', 'csv'], + }; + + private climateProducts = signal([]); + + constructor( + private service: ClimateService, + private deploymentService: DeploymentDashboardService, + private supabase: SupabaseService + ) { + effect( + () => { + const country_code = this.deploymentService.activeDeployment()?.country_code; + if (country_code) { + this.loadRainfallSummaries(country_code); + } + }, + { allowSignalWrites: true } + ); + } + private get db() { + return this.supabase.db.table('climate_products'); + } + + public async downloadAllStationsCSV() { + const zip = new JSZip(); + for (const summary of this.tableData()) { + const csvData = this.generateStationCSVDownload(summary); + if (csvData) { + zip.file(`${summary.station_id}.csv`, csvData); + } + } + const blob = await zip.generateAsync({ type: 'blob' }); + const country_code = this.deploymentService.activeDeployment()?.country_code; + download(blob, `${country_code}_rainfall_summaries.zip`); + } + + public downloadStationCSV(summary) { + const csv = this.generateStationCSVDownload(summary); + if (csv) { + download(csv, summary.station_id, 'text/csv'); + } + } + private generateStationCSVDownload(summary) { + if (summary.data && summary.data.length > 0) { + const csvData = hackConvertAPIDataToLegacyFormat(summary.data); + const columns = Object.keys(csvData[0]); + const csv = unparse(csvData, { columns }); + return csv; + } + return undefined; + } + + private generateTableSummaryData(stations: IStationRow[], products: IClimateProductRow[]) { + if (stations.length > 0 && products.length > 0) { + // NOTE - only single entry for rainfallSummary (not hashmapArray) + const productsHashmap = arrayToHashmapArray(products, 'station_id'); + const summary = stations.map((station) => { + const { station_id } = station; + const rainfallSummary = productsHashmap[station_id]?.find((p) => p.type === 'rainfallSummary'); + if (rainfallSummary) { + const { data, station_id, type } = rainfallSummary; + const entries: IAnnualRainfallSummariesData[] = data?.['data'] || []; + const start_year = entries[0]?.year; + const end_year = entries[entries.length - 1]?.year; + return { station_id, type, updated_at: '', start_year, end_year, csv: '', data: data?.['data'] }; + } else { + return { station_id }; + } + }); + console.log(summary); + return summary; + } + return []; + } + + private async loadRainfallSummaries(country_code: string) { + const { data, error } = await this.db.select<'*', IClimateProductRow>('*'); + + if (error) { + throw error; + } + const filtered = data + .filter((el) => el.type === 'rainfallSummary' && el.station_id.startsWith(`${country_code}/`)) + .map((station) => { + station.station_id = station.station_id.replace(`${country_code}/`, ''); + return station; + }); + this.climateProducts.set(filtered); + } +} diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.html b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.html index da805040c..40e033c0d 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.html +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.html @@ -8,7 +8,7 @@

Rainfall Summary

view_list Table - + diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.ts index e79881efd..9ea8f1d67 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.ts @@ -5,7 +5,7 @@ import { MatIconModule } from '@angular/material/icon'; import { MatTabsModule } from '@angular/material/tabs'; import { generateChartConfig } from '@picsa/climate/src/app/utils'; import { CLIMATE_CHART_DEFINTIONS } from '@picsa/data/climate/chart_definitions'; -import { IChartMeta, IStationData } from '@picsa/models/src'; +import { IChartMeta } from '@picsa/models/src'; import { PicsaChartComponent } from '@picsa/shared/features'; import { IDataTableOptions, PicsaDataTableComponent } from '@picsa/shared/features/data-table'; import { SupabaseService } from '@picsa/shared/services/core/supabase'; @@ -13,9 +13,8 @@ import { ChartConfiguration } from 'c3'; import { ClimateService } from '../../../../climate.service'; import { DashboardClimateApiStatusComponent, IApiStatusOptions } from '../../../../components/api-status/api-status'; -import { APITypes, IClimateProductRow, IStationRow } from '../../../../types'; - -type AnnualRainfallSummariesdata = APITypes.components['schemas']['AnnualRainfallSummariesdata']; +import { IClimateProductRow, IStationRow } from '../../../../types'; +import { hackConvertAPIDataToLegacyFormat } from './rainfall-summary.utils'; interface IRainfallSummary { // TODO - improve typings @@ -110,29 +109,11 @@ export class RainfallSummaryComponent { console.log('load data', summary); this.tableOptions.exportFilename = `${this.activeStation.id}.csv`; const { data, metadata } = summary; - this.summaryData = this.convertAPIDataToLegacyFormat(data); + this.summaryData = hackConvertAPIDataToLegacyFormat(data); // this.summaryData = data; this.summaryMetadata = metadata; const { country_code } = this.activeStation; const definitions = CLIMATE_CHART_DEFINTIONS[country_code] || CLIMATE_CHART_DEFINTIONS.default; this.chartDefintions = Object.values(definitions); } - - // TODO - refactor components to use modern format - private convertAPIDataToLegacyFormat(apiData: AnnualRainfallSummariesdata[] = []) { - const data: Partial[] = apiData.map((el) => ({ - Year: el.year, - // HACK - use either end_rains or end_season depending on which has data populated - // TODO - push for single value to be populated at api level - End: el.end_rains_doy || el.end_season_doy, - // HACK - extreme events not currently supported - // Extreme_events: null as any, - Length: el.season_length, - // HACK - replace 0mm with null value - // HACK - newer api returning seasonal_rain instead of annual_rain - Rainfall: el.seasonal_rain || undefined, - Start: el.start_rains_doy, - })); - return data; - } } diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.utils.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.utils.ts new file mode 100644 index 000000000..0e64b081c --- /dev/null +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.utils.ts @@ -0,0 +1,21 @@ +import { IStationData } from '@picsa/models/src'; + +import { IAnnualRainfallSummariesData } from '../../../../types'; + +// TODO - refactor components to use modern format +export function hackConvertAPIDataToLegacyFormat(apiData: IAnnualRainfallSummariesData[] = []) { + const data: Partial[] = apiData.map((el) => ({ + Year: el.year, + // HACK - use either end_rains or end_season depending on which has data populated + // TODO - push for single value to be populated at api level + End: el.end_rains_doy || el.end_season_doy, + // HACK - extreme events not currently supported + // Extreme_events: null as any, + Length: el.season_length, + // HACK - replace 0mm with null value + // HACK - mw uses seasonal_rain but zm uses annual_rainfall - API should return consistent + Rainfall: el.seasonal_rain || el.annual_rain || undefined, + Start: el.start_rains_doy, + })); + return data; +} diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/types/db.d.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/types/db.d.ts index 32c0e1db3..e57349153 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/types/db.d.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/types/db.d.ts @@ -1,6 +1,9 @@ // eslint-disable-next-line @nx/enforce-module-boundaries import type { Database } from '@picsa/server-types'; +import * as APITypes from './api'; + +// DB types export type IClimateProductRow = Database['public']['Tables']['climate_products']['Row']; export type IClimateProductInsert = Database['public']['Tables']['climate_products']['Insert']; @@ -9,3 +12,6 @@ export type IForecastInsert = Database['public']['Tables']['climate_forecasts'][ export type IStationRow = Database['public']['Tables']['climate_stations']['Row']; export type IStationInsert = Database['public']['Tables']['climate_stations']['Insert']; + +// API Types +export type IAnnualRainfallSummariesData = APITypes.components['schemas']['AnnualRainfallSummariesdata']; From b6c9a974b6efa6f96008a49ff160163ef3c6a1c7 Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Fri, 11 Oct 2024 11:02:09 -0700 Subject: [PATCH 2/8] chore: code tidying --- .../app/modules/climate/pages/admin/admin.component.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.ts index 3da808b8e..8e6d4bbd7 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.ts @@ -15,14 +15,7 @@ import type { IAnnualRainfallSummariesData, IClimateProductRow, IStationRow } fr import { hackConvertAPIDataToLegacyFormat } from '../station-details/components/rainfall-summary/rainfall-summary.utils'; /** - * TODOs - * - [ ] Improve types throughout - * - [ ] Add auth and navlink route guards - * - [ ] Add button to force api fetch - * - [ ] Update table to include country_code for filtered query - * - [ ] Update table to include updated_at - * - [ ] Consider rename climate_products table to just be climate_rainfall_summary (separate data/metadata cols) - * - [ ] Improve data conversion so raw data can be displayed in dashboard (and climate products use) + * TODOs - See #333 */ @Component({ From f528c2076e4db6e2c659b3e8bc10ce2afac1b016 Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Fri, 11 Oct 2024 11:09:04 -0700 Subject: [PATCH 3/8] chore: code tidying --- .../src/app/modules/climate/pages/admin/admin.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.ts index 8e6d4bbd7..7dd67fe31 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.ts @@ -102,7 +102,6 @@ export class ClimateAdminPageComponent { return { station_id }; } }); - console.log(summary); return summary; } return []; From 311ad74d8ab4cd6b03d2af9395079b7c87625b6c Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Oct 2024 11:00:44 +0100 Subject: [PATCH 4/8] feat: refresh all rainfall summaries --- .vscode/settings.json | 1 - .../climate/pages/admin/admin.component.html | 17 +++- .../climate/pages/admin/admin.component.ts | 77 ++++++++++++------- .../supabase/data/deployments_rows.csv | 8 +- 4 files changed, 68 insertions(+), 35 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e6c1e9928..9439716aa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,6 @@ { "java.configuration.updateBuildConfiguration": "automatic", "cSpell.enabled": false, - "eslint.runtime": "node", // Deno configuration for supabase functions // NOTE - could be refactored to separate code-workspace // https://supabase.com/docs/guides/functions/local-development#setting-up-your-environment diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.html b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.html index f6b297598..aead2cd70 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.html +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.html @@ -2,7 +2,18 @@

Admin

(WiP) dashboard to allow quick download of Rainfall Summaries to include in the app

- + + + +
Admin [options]="tableOptions" [valueTemplates]="{ csv: csvTemplate }" > - - + + diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.ts index 7dd67fe31..aa77fb92d 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.ts @@ -4,7 +4,7 @@ import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { IDataTableOptions, PicsaDataTableComponent } from '@picsa/shared/features'; import { SupabaseService } from '@picsa/shared/services/core/supabase'; -import { arrayToHashmapArray } from '@picsa/utils'; +import { _wait, arrayToHashmapArray } from '@picsa/utils'; import download from 'downloadjs'; import JSZip from 'jszip'; import { unparse } from 'papaparse'; @@ -14,6 +14,17 @@ import { ClimateService } from '../../climate.service'; import type { IAnnualRainfallSummariesData, IClimateProductRow, IStationRow } from '../../types'; import { hackConvertAPIDataToLegacyFormat } from '../station-details/components/rainfall-summary/rainfall-summary.utils'; +interface IStationAdminSummary { + station_id: string; + row: IStationRow; + updated_at: string; + rainfall_summary?: IClimateProductRow; + start_year?: number; + end_year?: number; +} + +const DISPLAY_COLUMNS: (keyof IStationAdminSummary)[] = ['station_id', 'updated_at', 'start_year', 'end_year']; + /** * TODOs - See #333 */ @@ -32,8 +43,9 @@ export class ClimateAdminPageComponent { return this.generateTableSummaryData(stations, products); }); public tableOptions: IDataTableOptions = { - displayColumns: ['station_id', 'type', 'updated_at', 'start_year', 'end_year', 'csv'], + displayColumns: DISPLAY_COLUMNS, }; + public refreshCount = signal(-1); private climateProducts = signal([]); @@ -61,7 +73,7 @@ export class ClimateAdminPageComponent { for (const summary of this.tableData()) { const csvData = this.generateStationCSVDownload(summary); if (csvData) { - zip.file(`${summary.station_id}.csv`, csvData); + zip.file(`${summary.row.station_id}.csv`, csvData); } } const blob = await zip.generateAsync({ type: 'blob' }); @@ -69,15 +81,30 @@ export class ClimateAdminPageComponent { download(blob, `${country_code}_rainfall_summaries.zip`); } - public downloadStationCSV(summary) { - const csv = this.generateStationCSVDownload(summary); + public downloadStationCSV(station: IStationAdminSummary) { + const csv = this.generateStationCSVDownload(station); if (csv) { - download(csv, summary.station_id, 'text/csv'); + download(csv, station.row.station_id, 'text/csv'); } } - private generateStationCSVDownload(summary) { - if (summary.data && summary.data.length > 0) { - const csvData = hackConvertAPIDataToLegacyFormat(summary.data); + + public async refreshAllStations() { + this.refreshCount.set(0); + const promises = this.tableData().map(async (station, i) => { + // hack - instead of queueing apply small offset between requests to reduce blocking + await _wait(200 * i); + await this.service.loadFromAPI.rainfallSummaries(station.row); + this.refreshCount.update((v) => v + 1); + }); + await Promise.all(promises); + await this.loadRainfallSummaries(this.deploymentService.activeDeployment()?.country_code as string); + } + + private generateStationCSVDownload(summary: IStationAdminSummary) { + const { rainfall_summary } = summary; + if (rainfall_summary && rainfall_summary.data) { + const data = rainfall_summary.data['data'] as any[]; + const csvData = hackConvertAPIDataToLegacyFormat(data); const columns = Object.keys(csvData[0]); const csv = unparse(csvData, { columns }); return csv; @@ -86,25 +113,21 @@ export class ClimateAdminPageComponent { } private generateTableSummaryData(stations: IStationRow[], products: IClimateProductRow[]) { - if (stations.length > 0 && products.length > 0) { - // NOTE - only single entry for rainfallSummary (not hashmapArray) - const productsHashmap = arrayToHashmapArray(products, 'station_id'); - const summary = stations.map((station) => { - const { station_id } = station; - const rainfallSummary = productsHashmap[station_id]?.find((p) => p.type === 'rainfallSummary'); - if (rainfallSummary) { - const { data, station_id, type } = rainfallSummary; - const entries: IAnnualRainfallSummariesData[] = data?.['data'] || []; - const start_year = entries[0]?.year; - const end_year = entries[entries.length - 1]?.year; - return { station_id, type, updated_at: '', start_year, end_year, csv: '', data: data?.['data'] }; - } else { - return { station_id }; - } - }); + // NOTE - only single entry for rainfallSummary (not hashmapArray) + const productsHashmap = arrayToHashmapArray(products, 'station_id'); + return stations.map((row) => { + const { station_id } = row; + const summary: IStationAdminSummary = { station_id, row, updated_at: '' }; + const rainfallSummary = productsHashmap[station_id]?.find((p) => p.type === 'rainfallSummary'); + if (rainfallSummary) { + summary.rainfall_summary = rainfallSummary; + const { data } = rainfallSummary; + const entries: IAnnualRainfallSummariesData[] = data?.['data'] || []; + summary.start_year = entries[0]?.year; + summary.end_year = entries[entries.length - 1]?.year; + } return summary; - } - return []; + }); } private async loadRainfallSummaries(country_code: string) { diff --git a/apps/picsa-server/supabase/data/deployments_rows.csv b/apps/picsa-server/supabase/data/deployments_rows.csv index 02bf38b00..79520af51 100644 --- a/apps/picsa-server/supabase/data/deployments_rows.csv +++ b/apps/picsa-server/supabase/data/deployments_rows.csv @@ -2,9 +2,9 @@ id,country_code,label,configuration,variant,access_key_md5,public,icon_path demo_dev,demo,Extension,{},extension,,false,global/images/tutorial.svg global_extension,global,Extension,{},extension,,false,global/images/flags/global.svg global_farmer,global,Farmer,{},farmer,,false,global/images/flags/global.svg -mw_extension,mw,Extension,{},extension,,false,global/images/flags/mw.svg mw_farmer,mw,Farmer,{},farmer,,false,global/images/flags/mw.svg -mw_workshop,mw,Malawi Workshop,"{""climate_country_code"":""mw_workshops""}",extension,,true,global/images/flags/mw.svg -zm_extension,zm,Extension,{},extension,,false,global/images/flags/zm.svg zm_farmer,zm,Farmer,{},farmer,,false,global/images/flags/zm.svg -zm_workshop,zm,Zambia Workshop,"{""climate_country_code"":""zm_workshops""}",extension,,true,global/images/flags/zm.svg \ No newline at end of file +mw_extension,mw,Malawi,{},extension,,true,global/images/flags/mw.svg +mw_workshop,mw,Malawi Workshop,"{""climate_country_code"":""mw_workshops""}",extension,,false,global/images/flags/mw.svg +zm_extension,zm,Zambia,{},extension,,true,global/images/flags/zm.svg +zm_workshop,zm,Zambia Workshop,"{""climate_country_code"":""zm_workshops""}",extension,,false,global/images/flags/zm.svg \ No newline at end of file From ad78b95a0c90b89c93c882f52d8993d0ac3044a1 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Oct 2024 12:27:21 +0100 Subject: [PATCH 5/8] refactor: climateSummaryRainfall data --- .../modules/climate/climate-api.mapping.ts | 30 +-- .../app/modules/climate/climate.service.ts | 4 +- .../climate/pages/admin/admin.component.html | 5 +- .../climate/pages/admin/admin.component.ts | 54 ++--- .../rainfall-summary/rainfall-summary.ts | 11 +- .../src/app/modules/climate/types/db.d.ts | 9 +- .../src/app/modules/climate/types/index.ts | 6 +- .../edit/resource-file-edit.component.ts | 1 + ...0241014110300_climate_summary_rainfall.sql | 28 +++ apps/picsa-server/supabase/types/index.ts | 206 +++++++++++++++--- 10 files changed, 260 insertions(+), 94 deletions(-) create mode 100644 apps/picsa-server/supabase/migrations/20241014110300_climate_summary_rainfall.sql diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.mapping.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.mapping.ts index db88bb339..7764328d5 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.mapping.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.mapping.ts @@ -5,20 +5,18 @@ import { IDeploymentRow } from '../deployment/types'; import { ClimateService } from './climate.service'; import type { ClimateApiService } from './climate-api.service'; import { - IClimateProductInsert, - IClimateProductRow, + IAPICountryCode, + IClimateSummaryRainfallInsert, + IClimateSummaryRainfallRow, IForecastInsert, IForecastRow, IStationInsert, IStationRow, } from './types'; -import type { components as ApiComponents } from './types/api'; export type IApiMapping = ReturnType; export type IApiMappingName = keyof IApiMapping; -export type IAPICountryCode = ApiComponents['schemas']['StationAndDefintionResponce']['country_code']; - // TODO - certain amount of boilerplate could be reduced // TODO - depends on climate api updates // TODO - most of these should be run on server as server functions @@ -38,7 +36,7 @@ export const ApiMapping = ( rainfallSummaries: async (station: IStationRow) => { const { country_code, station_id, station_name, id } = station; // TODO - add model type definitions for server rainfall summary response body - const { data, error } = await api + const { data: apiData, error } = await api .getObservableClient(`rainfallSummary_${id}`) .POST('/v1/annual_rainfall_summaries/', { body: { @@ -51,20 +49,22 @@ export const ApiMapping = ( }); if (error) throw error; // HACK - API issue returning huge data for some stations - if (data.data.length > 1000) { - console.error({ country_code, station_id, station_name, total_rows: data.data.length }); - throw new Error(`[rainfallSummary] Too many rows | ${station_name} ${data.data.length}`); + const { data, metadata } = apiData; + if (data.length > 1000) { + console.error({ country_code, station_id, station_name, total_rows: data.length }); + throw new Error(`[rainfallSummary] Too many rows | ${station_name} ${data.length}`); } // TODO - gen types and handle mapping - const entry: IClimateProductInsert = { - data: data as any, + const entry: IClimateSummaryRainfallInsert = { + data: data as any[], + metadata, station_id: id as string, - type: 'rainfallSummary', + country_code: country_code as any, }; const { data: dbData, error: dbError } = await db - .table('climate_products') - .upsert(entry) - .select<'*', IClimateProductRow>('*'); + .table('climate_summary_rainfall') + .upsert(entry) + .select<'*', IClimateSummaryRainfallRow>('*'); if (dbError) throw dbError; return dbData || []; }, diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/climate.service.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/climate.service.ts index 4f77e6b95..55d2c66fd 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/climate.service.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/climate.service.ts @@ -9,9 +9,9 @@ import { map } from 'rxjs'; import { DeploymentDashboardService } from '../deployment/deployment.service'; import { IDeploymentRow } from '../deployment/types'; -import { ApiMapping, IAPICountryCode } from './climate-api.mapping'; +import { ApiMapping } from './climate-api.mapping'; import { ClimateApiService } from './climate-api.service'; -import { IStationRow } from './types'; +import { IAPICountryCode, IStationRow } from './types'; @Injectable({ providedIn: 'root' }) export class ClimateService extends PicsaAsyncService { diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.html b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.html index aead2cd70..04ce4a555 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.html +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.html @@ -19,10 +19,13 @@

Admin

style="margin-top: 1em" [data]="tableData()" [options]="tableOptions" - [valueTemplates]="{ csv: csvTemplate }" + [valueTemplates]="{ csv: csvTemplate, updated_at: updatedAtTemplate }" > + + {{ value | date: 'mediumDate' }} + diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.ts index aa77fb92d..dae36d53a 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.ts @@ -1,24 +1,24 @@ -import { CommonModule } from '@angular/common'; +import { CommonModule, DatePipe } from '@angular/common'; import { Component, computed, effect, signal } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { IDataTableOptions, PicsaDataTableComponent } from '@picsa/shared/features'; import { SupabaseService } from '@picsa/shared/services/core/supabase'; -import { _wait, arrayToHashmapArray } from '@picsa/utils'; +import { _wait, arrayToHashmap } from '@picsa/utils'; import download from 'downloadjs'; import JSZip from 'jszip'; import { unparse } from 'papaparse'; import { DeploymentDashboardService } from '../../../deployment/deployment.service'; import { ClimateService } from '../../climate.service'; -import type { IAnnualRainfallSummariesData, IClimateProductRow, IStationRow } from '../../types'; +import type { IAnnualRainfallSummariesData, IClimateSummaryRainfallRow, IStationRow } from '../../types'; import { hackConvertAPIDataToLegacyFormat } from '../station-details/components/rainfall-summary/rainfall-summary.utils'; interface IStationAdminSummary { station_id: string; row: IStationRow; - updated_at: string; - rainfall_summary?: IClimateProductRow; + updated_at?: string; + rainfall_summary?: IClimateSummaryRainfallRow; start_year?: number; end_year?: number; } @@ -32,22 +32,22 @@ const DISPLAY_COLUMNS: (keyof IStationAdminSummary)[] = ['station_id', 'updated_ @Component({ selector: 'dashboard-climate-admin-page', standalone: true, - imports: [CommonModule, MatButtonModule, MatIconModule, PicsaDataTableComponent], + imports: [CommonModule, DatePipe, MatButtonModule, MatIconModule, PicsaDataTableComponent], templateUrl: './admin.component.html', styleUrl: './admin.component.scss', }) export class ClimateAdminPageComponent { public tableData = computed(() => { const stations = this.service.stations(); - const products = this.climateProducts(); - return this.generateTableSummaryData(stations, products); + const rainfallSummaries = this.rainfallSummaries(); + return this.generateTableSummaryData(stations, rainfallSummaries); }); public tableOptions: IDataTableOptions = { displayColumns: DISPLAY_COLUMNS, }; public refreshCount = signal(-1); - private climateProducts = signal([]); + private rainfallSummaries = signal([]); constructor( private service: ClimateService, @@ -65,7 +65,7 @@ export class ClimateAdminPageComponent { ); } private get db() { - return this.supabase.db.table('climate_products'); + return this.supabase.db.table('climate_summary_rainfall'); } public async downloadAllStationsCSV() { @@ -103,7 +103,7 @@ export class ClimateAdminPageComponent { private generateStationCSVDownload(summary: IStationAdminSummary) { const { rainfall_summary } = summary; if (rainfall_summary && rainfall_summary.data) { - const data = rainfall_summary.data['data'] as any[]; + const data = rainfall_summary.data as any[]; const csvData = hackConvertAPIDataToLegacyFormat(data); const columns = Object.keys(csvData[0]); const csv = unparse(csvData, { columns }); @@ -112,17 +112,18 @@ export class ClimateAdminPageComponent { return undefined; } - private generateTableSummaryData(stations: IStationRow[], products: IClimateProductRow[]) { + private generateTableSummaryData(stations: IStationRow[], rainfallSummaries: IClimateSummaryRainfallRow[]) { // NOTE - only single entry for rainfallSummary (not hashmapArray) - const productsHashmap = arrayToHashmapArray(products, 'station_id'); + const rainfallSummariesHashmap = arrayToHashmap(rainfallSummaries, 'station_id'); return stations.map((row) => { - const { station_id } = row; - const summary: IStationAdminSummary = { station_id, row, updated_at: '' }; - const rainfallSummary = productsHashmap[station_id]?.find((p) => p.type === 'rainfallSummary'); + const { station_id, id } = row; + const summary: IStationAdminSummary = { station_id, row }; + const rainfallSummary = rainfallSummariesHashmap[station_id]; if (rainfallSummary) { + const { data, updated_at } = rainfallSummary; + summary.updated_at = updated_at; summary.rainfall_summary = rainfallSummary; - const { data } = rainfallSummary; - const entries: IAnnualRainfallSummariesData[] = data?.['data'] || []; + const entries = data as IAnnualRainfallSummariesData[]; summary.start_year = entries[0]?.year; summary.end_year = entries[entries.length - 1]?.year; } @@ -131,17 +132,16 @@ export class ClimateAdminPageComponent { } private async loadRainfallSummaries(country_code: string) { - const { data, error } = await this.db.select<'*', IClimateProductRow>('*'); - + const { data, error } = await this.db.select<'*', IClimateSummaryRainfallRow>('*').eq('country_code', country_code); if (error) { throw error; } - const filtered = data - .filter((el) => el.type === 'rainfallSummary' && el.station_id.startsWith(`${country_code}/`)) - .map((station) => { - station.station_id = station.station_id.replace(`${country_code}/`, ''); - return station; - }); - this.climateProducts.set(filtered); + this.rainfallSummaries.set( + data.map((el) => { + // HACK - to keep parent ref id includes full country prefix, remove for lookup + el.station_id = el.station_id.replace(`${country_code}/`, ''); + return el; + }) + ); } } diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.ts index 9ea8f1d67..0279d95f6 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.ts @@ -13,7 +13,7 @@ import { ChartConfiguration } from 'c3'; import { ClimateService } from '../../../../climate.service'; import { DashboardClimateApiStatusComponent, IApiStatusOptions } from '../../../../components/api-status/api-status'; -import { IClimateProductRow, IStationRow } from '../../../../types'; +import { IClimateSummaryRainfallRow, IStationRow } from '../../../../types'; import { hackConvertAPIDataToLegacyFormat } from './rainfall-summary.utils'; interface IRainfallSummary { @@ -67,19 +67,18 @@ export class RainfallSummaryComponent { } private get db() { - return this.supabase.db.table('climate_products'); + return this.supabase.db.table('climate_summary_rainfall'); } private async loadActiveStation(station: IStationRow) { // Load data stored in supabase db if available. Otherwise load from api // TODO - nicer if could include db lookups as part of mapping doc const { data, error } = await this.db - .select<'*', IClimateProductRow>('*') + .select<'*', IClimateSummaryRainfallRow>('*') .eq('station_id', station.id) - .eq('type', 'rainfallSummary') .single(); if (data) { - this.loadData((data.data as any) || { data: [], metadata: {} }); + this.loadData(data); this.cdr.markForCheck(); } else { await this.refreshData(); @@ -93,7 +92,7 @@ export class RainfallSummaryComponent { const data = await this.service.loadFromAPI.rainfallSummaries(this.activeStation); const summary = data?.[0]; if (summary) { - this.loadData(summary.data as any); + this.loadData(summary); } } } diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/types/db.d.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/types/db.d.ts index e57349153..5836e6d23 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/types/db.d.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/types/db.d.ts @@ -1,17 +1,12 @@ // eslint-disable-next-line @nx/enforce-module-boundaries import type { Database } from '@picsa/server-types'; -import * as APITypes from './api'; - // DB types -export type IClimateProductRow = Database['public']['Tables']['climate_products']['Row']; -export type IClimateProductInsert = Database['public']['Tables']['climate_products']['Insert']; +export type IClimateSummaryRainfallRow = Database['public']['Tables']['climate_summary_rainfall']['Row']; +export type IClimateSummaryRainfallInsert = Database['public']['Tables']['climate_summary_rainfall']['Insert']; export type IForecastRow = Database['public']['Tables']['climate_forecasts']['Row']; export type IForecastInsert = Database['public']['Tables']['climate_forecasts']['Insert']; export type IStationRow = Database['public']['Tables']['climate_stations']['Row']; export type IStationInsert = Database['public']['Tables']['climate_stations']['Insert']; - -// API Types -export type IAnnualRainfallSummariesData = APITypes.components['schemas']['AnnualRainfallSummariesdata']; diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/types/index.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/types/index.ts index c5794de5c..a0365ecef 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/types/index.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/types/index.ts @@ -1,4 +1,8 @@ import * as APITypes from './api'; export * from './db'; -export { APITypes }; +// Re-export specific API Types +type schemas = APITypes.components['schemas']; +export type IAnnualRainfallSummariesData = schemas['AnnualRainfallSummariesdata']; + +export type IAPICountryCode = schemas['StationDataResponce']['country_code']; diff --git a/apps/picsa-apps/dashboard/src/app/modules/resources/pages/files/edit/resource-file-edit.component.ts b/apps/picsa-apps/dashboard/src/app/modules/resources/pages/files/edit/resource-file-edit.component.ts index 95ac79c99..220e79f52 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/resources/pages/files/edit/resource-file-edit.component.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/resources/pages/files/edit/resource-file-edit.component.ts @@ -84,6 +84,7 @@ export class ResourceFileEditComponent implements OnInit { modified_at: '', sort_order: 1, ...formValues, + country_code: formValues.country_code as any, }; return value; } diff --git a/apps/picsa-server/supabase/migrations/20241014110300_climate_summary_rainfall.sql b/apps/picsa-server/supabase/migrations/20241014110300_climate_summary_rainfall.sql new file mode 100644 index 000000000..2537a4f6e --- /dev/null +++ b/apps/picsa-server/supabase/migrations/20241014110300_climate_summary_rainfall.sql @@ -0,0 +1,28 @@ +-- Replace generic `climate_products` table with more specific summaries + +-- BREAKING - will require re-seeding data +drop table if exists public.climate_products; + + +create table + public.climate_summary_rainfall ( + created_at timestamp with time zone not null default now(), + updated_at timestamp with time zone not null default now(), + station_id text not null, + country_code country_code not null, + metadata jsonb not null, + data jsonb[] not null, + constraint climate_summary_rainfall_pkey primary key ( + country_code, + station_id + ), + constraint climate_summary_rainfall_station_id_fkey foreign key (station_id) references climate_stations (id) on delete cascade + ) tablespace pg_default; + +-- Enable moddatetime extension to automatically populated `updated_at` columns +-- https://dev.to/paullaros/updating-timestamps-automatically-in-supabase-5f5o +CREATE EXTENSION IF NOT EXISTS "moddatetime" SCHEMA extensions; + +-- NOTE - required extensions - allow moddatetime +create trigger handle_updated_at before update on public.climate_summary_rainfall + for each row execute procedure moddatetime (updated_at); \ No newline at end of file diff --git a/apps/picsa-server/supabase/types/index.ts b/apps/picsa-server/supabase/types/index.ts index aa6ad2a89..42152ade3 100644 --- a/apps/picsa-server/supabase/types/index.ts +++ b/apps/picsa-server/supabase/types/index.ts @@ -82,35 +82,6 @@ export type Database = { }, ] } - climate_products: { - Row: { - created_at: string - data: Json - station_id: string - type: string - } - Insert: { - created_at?: string - data: Json - station_id: string - type: string - } - Update: { - created_at?: string - data?: Json - station_id?: string - type?: string - } - Relationships: [ - { - foreignKeyName: "climate_products_station_id_fkey" - columns: ["station_id"] - isOneToOne: false - referencedRelation: "climate_stations" - referencedColumns: ["id"] - }, - ] - } climate_stations: { Row: { country_code: string @@ -144,6 +115,41 @@ export type Database = { } Relationships: [] } + climate_summary_rainfall: { + Row: { + country_code: Database["public"]["Enums"]["country_code"] + created_at: string + data: Json[] + metadata: Json + station_id: string + updated_at: string + } + Insert: { + country_code: Database["public"]["Enums"]["country_code"] + created_at?: string + data: Json[] + metadata: Json + station_id: string + updated_at?: string + } + Update: { + country_code?: Database["public"]["Enums"]["country_code"] + created_at?: string + data?: Json[] + metadata?: Json + station_id?: string + updated_at?: string + } + Relationships: [ + { + foreignKeyName: "climate_summary_rainfall_station_id_fkey" + columns: ["station_id"] + isOneToOne: false + referencedRelation: "climate_stations" + referencedColumns: ["id"] + }, + ] + } crop_data: { Row: { created_at: string @@ -438,7 +444,7 @@ export type Database = { } resource_files: { Row: { - country_code: string | null + country_code: Database["public"]["Enums"]["country_code"] cover_image: string | null created_at: string description: string | null @@ -455,7 +461,7 @@ export type Database = { title: string | null } Insert: { - country_code?: string | null + country_code?: Database["public"]["Enums"]["country_code"] cover_image?: string | null created_at?: string description?: string | null @@ -472,7 +478,7 @@ export type Database = { title?: string | null } Update: { - country_code?: string | null + country_code?: Database["public"]["Enums"]["country_code"] cover_image?: string | null created_at?: string description?: string | null @@ -507,7 +513,7 @@ export type Database = { } resource_files_child: { Row: { - country_code: string | null + country_code: Database["public"]["Enums"]["country_code"] cover_image: string | null created_at: string description: string | null @@ -525,7 +531,7 @@ export type Database = { title: string | null } Insert: { - country_code?: string | null + country_code?: Database["public"]["Enums"]["country_code"] cover_image?: string | null created_at?: string description?: string | null @@ -543,7 +549,7 @@ export type Database = { title?: string | null } Update: { - country_code?: string | null + country_code?: Database["public"]["Enums"]["country_code"] cover_image?: string | null created_at?: string description?: string | null @@ -897,6 +903,101 @@ export type Database = { }, ] } + s3_multipart_uploads: { + Row: { + bucket_id: string + created_at: string + id: string + in_progress_size: number + key: string + owner_id: string | null + upload_signature: string + version: string + } + Insert: { + bucket_id: string + created_at?: string + id: string + in_progress_size?: number + key: string + owner_id?: string | null + upload_signature: string + version: string + } + Update: { + bucket_id?: string + created_at?: string + id?: string + in_progress_size?: number + key?: string + owner_id?: string | null + upload_signature?: string + version?: string + } + Relationships: [ + { + foreignKeyName: "s3_multipart_uploads_bucket_id_fkey" + columns: ["bucket_id"] + isOneToOne: false + referencedRelation: "buckets" + referencedColumns: ["id"] + }, + ] + } + s3_multipart_uploads_parts: { + Row: { + bucket_id: string + created_at: string + etag: string + id: string + key: string + owner_id: string | null + part_number: number + size: number + upload_id: string + version: string + } + Insert: { + bucket_id: string + created_at?: string + etag: string + id?: string + key: string + owner_id?: string | null + part_number: number + size?: number + upload_id: string + version: string + } + Update: { + bucket_id?: string + created_at?: string + etag?: string + id?: string + key?: string + owner_id?: string | null + part_number?: number + size?: number + upload_id?: string + version?: string + } + Relationships: [ + { + foreignKeyName: "s3_multipart_uploads_parts_bucket_id_fkey" + columns: ["bucket_id"] + isOneToOne: false + referencedRelation: "buckets" + referencedColumns: ["id"] + }, + { + foreignKeyName: "s3_multipart_uploads_parts_upload_id_fkey" + columns: ["upload_id"] + isOneToOne: false + referencedRelation: "s3_multipart_uploads" + referencedColumns: ["id"] + }, + ] + } } Views: { [_ in never]: never @@ -936,6 +1037,41 @@ export type Database = { bucket_id: string }[] } + list_multipart_uploads_with_delimiter: { + Args: { + bucket_id: string + prefix_param: string + delimiter_param: string + max_keys?: number + next_key_token?: string + next_upload_token?: string + } + Returns: { + key: string + id: string + created_at: string + }[] + } + list_objects_with_delimiter: { + Args: { + bucket_id: string + prefix_param: string + delimiter_param: string + max_keys?: number + start_after?: string + next_token?: string + } + Returns: { + name: string + id: string + metadata: Json + updated_at: string + }[] + } + operation: { + Args: Record + Returns: string + } search: { Args: { prefix: string From 0bd3fe3def6e7fdfd5e9553b83770f355144838d Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Oct 2024 12:27:33 +0100 Subject: [PATCH 6/8] chore: update husky --- package.json | 2 +- yarn.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 939b638c0..9b25f0e24 100644 --- a/package.json +++ b/package.json @@ -173,7 +173,7 @@ "eslint-plugin-simple-import-sort": "^10.0.0", "execa": "^5.1.1", "fs-extra": "^10.1.0", - "husky": "^8.0.3", + "husky": "^9.1.6", "jest": "29.4.3", "jest-circus": "^29.5.0", "jest-environment-jsdom": "29.4.3", diff --git a/yarn.lock b/yarn.lock index 519b5d18a..e4703d330 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15538,12 +15538,12 @@ __metadata: languageName: node linkType: hard -"husky@npm:^8.0.3": - version: 8.0.3 - resolution: "husky@npm:8.0.3" +"husky@npm:^9.1.6": + version: 9.1.6 + resolution: "husky@npm:9.1.6" bin: - husky: lib/bin.js - checksum: 837bc7e4413e58c1f2946d38fb050f5d7324c6f16b0fd66411ffce5703b294bd21429e8ba58711cd331951ee86ed529c5be4f76805959ff668a337dbfa82a1b0 + husky: bin.js + checksum: 421ccd8850378231aaefd70dbe9e4f1549b84ffe3a6897f93a202242bbc04e48bd498169aef43849411105d9fcf7c192b757d42661e28d06b934a609a4eb8771 languageName: node linkType: hard @@ -19689,7 +19689,7 @@ __metadata: glob: ^10.3.10 hls.js: ^1.4.12 html2canvas: ^1.4.1 - husky: ^8.0.3 + husky: ^9.1.6 intro.js: ^7.0.1 jest: 29.4.3 jest-circus: ^29.5.0 From c73a2f526df37ae4834b9febed618fb4bf249207 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 14 Oct 2024 17:06:12 +0100 Subject: [PATCH 7/8] chore: code tidying --- .../climate/pages/admin/admin.component.html | 2 +- .../climate/pages/admin/admin.component.ts | 7 ++++- .../rainfall-summary/rainfall-summary.ts | 18 +++++++----- .../rainfall-summary.utils.ts | 29 +++++++++++++------ .../src/app/modules/climate/types/db.d.ts | 9 ++++-- .../src/app/modules/climate/types/index.ts | 1 + 6 files changed, 46 insertions(+), 20 deletions(-) diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.html b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.html index 04ce4a555..185d92899 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.html +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/admin/admin.component.html @@ -1,7 +1,7 @@

Admin

-

(WiP) dashboard to allow quick download of Rainfall Summaries to include in the app

+

See the table below for rainfall summary data for all stations