From c4d135b08fb19f4e7212f90832a2359fe18b5f3c Mon Sep 17 00:00:00 2001 From: Yao Ding Date: Fri, 8 Nov 2024 14:32:22 -0500 Subject: [PATCH 1/3] Add water quality data to site model and use it to power site filter --- packages/api/src/sites/sites.entity.ts | 2 + packages/api/src/sites/sites.service.ts | 6 +++ packages/api/src/utils/site.utils.ts | 47 ++++++++++++++++++++++- packages/website/src/helpers/siteUtils.ts | 2 + packages/website/src/store/Sites/types.ts | 2 + 5 files changed, 58 insertions(+), 1 deletion(-) diff --git a/packages/api/src/sites/sites.entity.ts b/packages/api/src/sites/sites.entity.ts index b12e129fc..ee4d9238f 100644 --- a/packages/api/src/sites/sites.entity.ts +++ b/packages/api/src/sites/sites.entity.ts @@ -167,6 +167,8 @@ export class Site { maskedSpotterApiToken?: string; + waterQuality?: string[]; + @Expose() get applied(): boolean { return !!this.siteApplication?.permitRequirements; diff --git a/packages/api/src/sites/sites.service.ts b/packages/api/src/sites/sites.service.ts index 4332db4ef..281b55d38 100644 --- a/packages/api/src/sites/sites.service.ts +++ b/packages/api/src/sites/sites.service.ts @@ -25,6 +25,7 @@ import { filterMetricDataByDate, getConflictingExclusionDates, hasHoboDataSubQuery, + hasWaterQualityDataSubQuery, getLatestData, getSite, createSite, @@ -198,11 +199,16 @@ export class SitesService { const hasHoboDataSet = await hasHoboDataSubQuery(this.sourceRepository); + const waterQualityDataSet = await hasWaterQualityDataSubQuery( + this.latestDataRepository, + ); + return res.map((site) => ({ ...site, applied: site.applied, collectionData: mappedSiteData[site.id], hasHobo: hasHoboDataSet.has(site.id), + waterQuality: waterQualityDataSet.get(site.id), })); } diff --git a/packages/api/src/utils/site.utils.ts b/packages/api/src/utils/site.utils.ts index 089121437..147a9e22f 100644 --- a/packages/api/src/utils/site.utils.ts +++ b/packages/api/src/utils/site.utils.ts @@ -1,3 +1,5 @@ +/* eslint-disable fp/no-mutating-methods */ +/* eslint-disable fp/no-mutation */ import { Client, AddressType, @@ -10,7 +12,7 @@ import { NotFoundException, } from '@nestjs/common'; import { ObjectLiteral, Repository } from 'typeorm'; -import { mapValues, some } from 'lodash'; +import { groupBy, mapValues, some, camelCase } from 'lodash'; import geoTz from 'geo-tz'; import { Region } from '../regions/regions.entity'; import { ExclusionDates } from '../sites/exclusion-dates.entity'; @@ -278,6 +280,49 @@ export const hasHoboDataSubQuery = async ( return hasHoboDataSet; }; +export const hasWaterQualityDataSubQuery = async ( + latestDataRepository: Repository, +): Promise> => { + const latestData: LatestData[] = await latestDataRepository + .createQueryBuilder('water_quality_data') + .select('site_id', 'siteId') + .addSelect('metric') + .addSelect('source') + .where(`source != '${SourceType.HOBO}'`) + .getRawMany(); + + const sondeMetrics = [ + 'odoConcentration', + 'cholorophyllConcentration', + 'ph', + 'salinity', + 'turbidity', + ]; + + const waterQualityDataSet = new Map(); + + Object.entries(groupBy(latestData, (o) => o.siteId)).forEach( + ([siteId, data]) => { + let sondeMetricsCount = 0; + const id = Number(siteId); + waterQualityDataSet.set(id, []); + data.forEach((siteData) => { + if (siteData.source === 'hui') { + waterQualityDataSet.get(id)!.push('hui'); + } + if (sondeMetrics.includes(camelCase(siteData.metric))) { + sondeMetricsCount += 1; + if (sondeMetricsCount >= 3) { + waterQualityDataSet.get(id)!.push('sonde'); + } + } + }); + }, + ); + + return waterQualityDataSet; +}; + export const getLatestData = async ( site: Site, latestDataRepository: Repository, diff --git a/packages/website/src/helpers/siteUtils.ts b/packages/website/src/helpers/siteUtils.ts index 85bf2e273..93031ca28 100644 --- a/packages/website/src/helpers/siteUtils.ts +++ b/packages/website/src/helpers/siteUtils.ts @@ -152,6 +152,8 @@ export const sitesFilterFn = ( return hasDeployedSpotter(s); case 'HOBO loggers': return s?.hasHobo; + case 'Water quality': + return s?.waterQuality?.length; default: console.error(`Unhandled Option: ${filter}`); return true; diff --git a/packages/website/src/store/Sites/types.ts b/packages/website/src/store/Sites/types.ts index 78ec3cbe9..1f8121f4c 100644 --- a/packages/website/src/store/Sites/types.ts +++ b/packages/website/src/store/Sites/types.ts @@ -287,6 +287,7 @@ export interface Site { contactInformation?: string; maskedSpotterApiToken?: string; iframe?: string | null; + waterQuality?: string[]; } export interface SiteSketchFab { @@ -456,4 +457,5 @@ export const siteOptions = [ 'Live streams', '3D Models', 'HOBO loggers', + 'Water quality', ] as const; From 8c99b3ba65363640bb04ae1e62b0dbb9573452fb Mon Sep 17 00:00:00 2001 From: Yao Ding Date: Sat, 9 Nov 2024 10:55:41 -0500 Subject: [PATCH 2/3] update snapshots --- .../SiteTable/__snapshots__/index.test.tsx.snap | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/website/src/routes/HomeMap/SiteTable/__snapshots__/index.test.tsx.snap b/packages/website/src/routes/HomeMap/SiteTable/__snapshots__/index.test.tsx.snap index f75d91714..efc2fdfb2 100644 --- a/packages/website/src/routes/HomeMap/SiteTable/__snapshots__/index.test.tsx.snap +++ b/packages/website/src/routes/HomeMap/SiteTable/__snapshots__/index.test.tsx.snap @@ -120,6 +120,17 @@ exports[`SiteTable should render with given state from Redux store 1`] = ` HOBO loggers + + + Water quality + + From a17524a8262f600353e83c9c2ef1ba72cdf83e7b Mon Sep 17 00:00:00 2001 From: Yao Ding Date: Tue, 17 Dec 2024 11:45:30 -0500 Subject: [PATCH 3/3] address comments --- packages/api/src/utils/site.utils.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/api/src/utils/site.utils.ts b/packages/api/src/utils/site.utils.ts index 147a9e22f..3312327de 100644 --- a/packages/api/src/utils/site.utils.ts +++ b/packages/api/src/utils/site.utils.ts @@ -1,5 +1,3 @@ -/* eslint-disable fp/no-mutating-methods */ -/* eslint-disable fp/no-mutation */ import { Client, AddressType, @@ -308,11 +306,14 @@ export const hasWaterQualityDataSubQuery = async ( waterQualityDataSet.set(id, []); data.forEach((siteData) => { if (siteData.source === 'hui') { + // eslint-disable-next-line fp/no-mutating-methods waterQualityDataSet.get(id)!.push('hui'); } if (sondeMetrics.includes(camelCase(siteData.metric))) { + // eslint-disable-next-line fp/no-mutation sondeMetricsCount += 1; if (sondeMetricsCount >= 3) { + // eslint-disable-next-line fp/no-mutating-methods waterQualityDataSet.get(id)!.push('sonde'); } }