From 6f1886aeafce34ed397ee1dde01c18bd71c7ee2b Mon Sep 17 00:00:00 2001 From: Konstantinos Markopoulos Date: Wed, 23 Oct 2024 17:28:57 +0300 Subject: [PATCH] Populate submitted values in site details --- packages/api/src/sites/sites.entity.ts | 4 +- packages/api/src/sites/sites.service.ts | 42 ++++++++--- packages/api/src/sites/sites.spec.ts | 10 +++ .../website/src/common/Forms/TextField.tsx | 8 ++- .../Site/SiteInfo/EditForm/index.tsx | 69 +++++++++---------- packages/website/src/store/Sites/types.ts | 1 + 6 files changed, 88 insertions(+), 46 deletions(-) diff --git a/packages/api/src/sites/sites.entity.ts b/packages/api/src/sites/sites.entity.ts index 5cd74cb09..70d1b39a6 100644 --- a/packages/api/src/sites/sites.entity.ts +++ b/packages/api/src/sites/sites.entity.ts @@ -151,10 +151,12 @@ export class Site { @Column({ nullable: true, select: false, type: 'character varying' }) contactInformation?: string | null; - hasHobo: boolean; + hasHobo?: boolean; collectionData?: CollectionDataDto; + maskedSpotterApiToken?: 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 64c852537..d152a8ce0 100644 --- a/packages/api/src/sites/sites.service.ts +++ b/packages/api/src/sites/sites.service.ts @@ -207,13 +207,18 @@ export class SitesService { } async findOne(id: number): Promise { - const site = await getSite(id, this.sitesRepository, [ - 'region', - 'admins', - 'historicalMonthlyMean', - 'siteApplication', - 'sketchFab', - ]); + const site = await getSite( + id, + this.sitesRepository, + [ + 'region', + 'admins', + 'historicalMonthlyMean', + 'siteApplication', + 'sketchFab', + ], + true, + ); // Typeorm returns undefined instead of [] for // OneToMany relations, so we fix it to match OpenAPI specs: @@ -227,8 +232,29 @@ export class SitesService { this.latestDataRepository, ); + const maskedSpotterApiToken = site.spotterApiToken + ? `****${site.spotterApiToken?.slice(-4)}` + : undefined; + return { - ...site, + id: site.id, + name: site.name, + sensorId: site.sensorId, + polygon: site.polygon, + nearestNOAALocation: site.nearestNOAALocation, + depth: site.depth, + iframe: site.iframe, + status: site.status, + maxMonthlyMean: site.maxMonthlyMean, + timezone: site.timezone, + display: site.display, + createdAt: site.createdAt, + updatedAt: site.updatedAt, + region: site.region, + admins: site.admins, + siteApplication: site.siteApplication, + sketchFab: site.sketchFab, + maskedSpotterApiToken, surveys, historicalMonthlyMean, videoStream, diff --git a/packages/api/src/sites/sites.spec.ts b/packages/api/src/sites/sites.spec.ts index 76e694c2c..cd4f0cc4a 100644 --- a/packages/api/src/sites/sites.spec.ts +++ b/packages/api/src/sites/sites.spec.ts @@ -110,6 +110,7 @@ export const siteTests = () => { }); expect(rsp.body.historicalMonthlyMean).toBeDefined(); expect(rsp.body.historicalMonthlyMean.length).toBe(12); + expect(rsp.body.maskedSpotterApiToken).toBeUndefined(); }); it('GET /:id/daily_data', async () => { @@ -135,12 +136,21 @@ export const siteTests = () => { .put(`/sites/${siteId}`) .send({ name: updatedSiteName, + spotterApiToken: '123456789', }); expect(rsp.status).toBe(200); expect(rsp.body).toMatchObject({ name: updatedSiteName }); }); + it('GET /:id retrieve masked spotterApiToken', async () => { + const rsp = await request(app.getHttpServer()).get(`/sites/${siteId}`); + + expect(rsp.status).toBe(200); + expect(rsp.body.maskedSpotterApiToken).toBe('****6789'); + expect(rsp.body.spotterApiToken).toBeUndefined(); + }); + it.each([ ['https://aqualink.org', 200], ['http:aqualink.org/protocol', 400], diff --git a/packages/website/src/common/Forms/TextField.tsx b/packages/website/src/common/Forms/TextField.tsx index 9597cfb58..806c6401f 100644 --- a/packages/website/src/common/Forms/TextField.tsx +++ b/packages/website/src/common/Forms/TextField.tsx @@ -1,4 +1,4 @@ -import React, { ChangeEvent } from 'react'; +import React, { ChangeEvent, FocusEvent } from 'react'; import { withStyles, WithStyles, @@ -20,6 +20,8 @@ const CustomTextfield = ({ size, disabled, onChange, + onBlur, + onFocus, classes, select = false, }: CustomTextfieldProps) => { @@ -33,6 +35,8 @@ const CustomTextfield = ({ type={isNumeric ? 'number' : 'text'} value={formField.value} onChange={onChange} + onBlur={onBlur} + onFocus={onFocus} label={label} placeholder={placeholder} name={name} @@ -65,6 +69,8 @@ interface CustomTextfieldIncomingProps { onChange: ( event: ChangeEvent, ) => void; + onBlur?: (event: ChangeEvent) => void; + onFocus?: (event: FocusEvent) => void; select?: boolean; } diff --git a/packages/website/src/routes/SiteRoutes/Site/SiteInfo/EditForm/index.tsx b/packages/website/src/routes/SiteRoutes/Site/SiteInfo/EditForm/index.tsx index 5b1876136..f88ed73cf 100644 --- a/packages/website/src/routes/SiteRoutes/Site/SiteInfo/EditForm/index.tsx +++ b/packages/website/src/routes/SiteRoutes/Site/SiteInfo/EditForm/index.tsx @@ -39,8 +39,9 @@ const EditForm = ({ const { latitude: draftLatitude, longitude: draftLongitude } = draftSite?.coordinates || {}; - const [editToken, setEditToken] = React.useState(false); - const [useDefaultToken, setUseDefaultToken] = React.useState(false); + const [editToken, setEditToken] = React.useState( + !!site.maskedSpotterApiToken, + ); const [editContactInfo, setEditContactInfo] = React.useState(false); @@ -88,11 +89,13 @@ const EditForm = ({ ); const [siteSpotterApiToken, setSiteSpotterApiToken] = useFormField( - '', + site.maskedSpotterApiToken ?? '', ['maxLength'], ); - const [status, setStatus] = useFormField('', []); + const [apiTokenChanged, setApiTokenChanged] = React.useState(false); + + const [status, setStatus] = useFormField(site.status, []); const [display, setDisplay] = useFormField(site.display, []); @@ -110,6 +113,18 @@ const EditForm = ({ 'maxLength', ]); + const onApiTokenBlur = () => { + if (siteSpotterApiToken.value === '' && site.maskedSpotterApiToken) { + setSiteSpotterApiToken(site.maskedSpotterApiToken); + setApiTokenChanged(false); + } + }; + const onApiTokenFocus = () => { + if (!apiTokenChanged) { + setSiteSpotterApiToken(''); + } + }; + const formSubmit = (event: FormEvent) => { if ( siteName.value && @@ -117,11 +132,10 @@ const EditForm = ({ siteLatitude.value && siteLongitude.value ) { - const insertedTokenValue = siteSpotterApiToken.value + const insertedTokenValue = apiTokenChanged ? siteSpotterApiToken.value : undefined; - const tokenValue = useDefaultToken ? null : insertedTokenValue; - const spotterApiToken = editToken ? tokenValue : undefined; + const spotterApiToken = editToken ? insertedTokenValue : null; // fields need to be undefined in order not be affected by the update. // siteSensorId.value here can be which our api does not accept const sensorId = siteSensorId.value || undefined; @@ -178,6 +192,7 @@ const EditForm = ({ break; case 'spotterApiToken': setSiteSpotterApiToken(newValue); + setApiTokenChanged(true); break; case 'status': setStatus(newValue); @@ -266,35 +281,17 @@ const EditForm = ({ /> {editToken && ( - <> - - - - - setUseDefaultToken(!useDefaultToken)} - /> - } - label="Use default token" - /> - - + + + )} diff --git a/packages/website/src/store/Sites/types.ts b/packages/website/src/store/Sites/types.ts index f18c8d2d9..78ec3cbe9 100644 --- a/packages/website/src/store/Sites/types.ts +++ b/packages/website/src/store/Sites/types.ts @@ -285,6 +285,7 @@ export interface Site { sketchFab?: SiteSketchFab; display: boolean; contactInformation?: string; + maskedSpotterApiToken?: string; iframe?: string | null; }