diff --git a/package-lock.json b/package-lock.json index 998af62..6c6ff18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12109,10 +12109,11 @@ } }, "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", "dev": true, + "license": "MIT", "dependencies": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", diff --git a/src/app.js b/src/app.js index 40f2a27..b379c4e 100644 --- a/src/app.js +++ b/src/app.js @@ -8,6 +8,7 @@ import { ControlPanel } from '@components/control-panel'; import { ComparePanel } from '@components/compare-panel'; import { MapLegend } from '@components/legend'; import { AlertUser } from '@components/alert-user'; +import { Config } from '@components/config'; /** * renders the main content @@ -17,7 +18,7 @@ import { AlertUser } from '@components/alert-user'; */ const Content = () => { // install the selected observation list from the layer context - const { selectedObservations } = useLayers(); + const { selectedObservations, defaultInstanceName } = useLayers(); // render all the application content return ( @@ -32,10 +33,13 @@ const Content = () => { return ; }) } + - - + {/* here we are waiting for the retrieval of the default Instance name + before rendering these components */} + { (defaultInstanceName != null) && } + { (defaultInstanceName != null) && } diff --git a/src/components/alert-user/alert-user.js b/src/components/alert-user/alert-user.js index de554c8..51c772b 100644 --- a/src/components/alert-user/alert-user.js +++ b/src/components/alert-user/alert-user.js @@ -12,7 +12,7 @@ export const AlertUser = () => { setAlertMsg(null) }> - + { alertMsg['msg'] } diff --git a/src/components/config/config.js b/src/components/config/config.js new file mode 100644 index 0000000..b0af10d --- /dev/null +++ b/src/components/config/config.js @@ -0,0 +1,95 @@ +import { useEffect, useState } from "react"; +import { useLayers } from "@context"; +import { getNamespacedEnvParam } from "@utils"; +import { useQuery } from '@tanstack/react-query'; +import axios from 'axios'; + +/** + * gets the default instance name for startup layers + * + */ +export const getDefaultInstanceName = () => { + // init the return + let ret_val = ''; + + // get the state variable that suppresses using the instance name + const { + defaultInstanceName + } = useLayers(); + + // if there is a valid default instance name + if (!defaultInstanceName.includes('Error') && defaultInstanceName.length) { + // build the extended query string + ret_val = '&instance_name=' + defaultInstanceName; + } + + // return the query string addition + return ret_val; +}; + +/** + * handles getting the default instance name + * + * @returns JSX.Element + * @constructor + */ +export const Config = () => { + // get the message alert details from state + const { setDefaultInstanceName } = useLayers(); + + // use this to trigger the data retrieval + const [ dataUrl, setDataUrl ] = useState(null); + + /** + * create a url to get the instance name + */ + useEffect( () => { + // get the site branding for the query string + const theUrl = 'get_ui_instance_name?reset=false&site_branding=' + (window.location.href.includes('nopp') ? 'NOPP' : 'APSViz'); + + // set the data url. this will spawn a data request + setDataUrl(getNamespacedEnvParam('REACT_APP_UI_DATA_URL') + theUrl); + }, [] ); + + /** + * grab the default instance name + */ + useQuery( { + // specify the data key and url to use + queryKey: ['get_ui_instance_name', dataUrl], + + // create the function to call for data + queryFn: async () => { + // create the authorization header + const requestOptions = { + method: 'GET', + headers: { Authorization: `Bearer ${ getNamespacedEnvParam('REACT_APP_UI_DATA_TOKEN') }`} + }; + + // make the call to get the data + const ret_val = await axios + // make the call to get the data + .get(dataUrl, requestOptions) + // use the data returned + .then (( response ) => { + // return the data + return response.data; + }) + .catch (( error ) => { + // make sure we do not render anything + return error.response.status; + }); + + // if the retrieval did not have an issue + if (typeof ret_val === 'string' && !ret_val.includes('Error')) + // save the instance name value + setDefaultInstanceName(ret_val); + else + // blank the instance name on any http or data gathering error. + setDefaultInstanceName(''); + + // return something + return true; + }, refetchOnWindowFocus: false + }); +}; \ No newline at end of file diff --git a/src/components/config/index.js b/src/components/config/index.js new file mode 100644 index 0000000..f03c228 --- /dev/null +++ b/src/components/config/index.js @@ -0,0 +1 @@ +export * from './config'; diff --git a/src/components/control-panel/control-panel.js b/src/components/control-panel/control-panel.js index b2254ae..3877513 100644 --- a/src/components/control-panel/control-panel.js +++ b/src/components/control-panel/control-panel.js @@ -24,6 +24,7 @@ import { Waves as HIResMaxElevationIcon, } from '@mui/icons-material'; import { getBrandingHandler, getNamespacedEnvParam } from "@utils/map-utils"; +import { getDefaultInstanceName } from "@components/config"; import { Branding } from './branding'; const layerIcons = { @@ -44,7 +45,7 @@ export const ControlPanel = () => { toggleLayerVisibility, toggleHurricaneLayerVisibility } = useLayers(); - const data_url = `${ getNamespacedEnvParam('REACT_APP_UI_DATA_URL') }` + `get_ui_data_secure?limit=1&use_v3_sp=true${ getBrandingHandler() }`; + const data_url = `${ getNamespacedEnvParam('REACT_APP_UI_DATA_URL') }` + `get_ui_data_secure?limit=1&use_v3_sp=true${ getBrandingHandler() + getDefaultInstanceName() }`; const layers = [...defaultModelLayers]; const hurrLayers = [...hurricaneTrackLayers]; diff --git a/src/components/map/adcirc-raster-layer.js b/src/components/map/adcirc-raster-layer.js index 9b74c34..a7c1a78 100644 --- a/src/components/map/adcirc-raster-layer.js +++ b/src/components/map/adcirc-raster-layer.js @@ -63,9 +63,10 @@ export const AdcircRasterLayer = (layer) => { // get the observation points selected, default layers and alert message from state const { - selectedObservations, setSelectedObservations, defaultModelLayers, setAlertMsg, + selectedObservations, setSelectedObservations, + defaultSelected, leftPaneID, rightPaneID } = useLayers(); // capture the default layers @@ -87,7 +88,18 @@ export const AdcircRasterLayer = (layer) => { return (selectedObservations.find((o) => o.id === id) !== undefined); }; - // create a callback to handle a map click event + /** + * determines if the app is in compare mode + * + * @returns {boolean} + */ + const inCompareMode = () => { + return (leftPaneID !== defaultSelected && rightPaneID !== defaultSelected); + }; + + /** + * create a callback to handle a map click event + */ const onClick = useCallback((e) => { // get the visible layer on the map const layer = layers.find((layer) => layer.properties['product_type'] !== "obs" && layer.state.visible === true); @@ -101,55 +113,63 @@ export const AdcircRasterLayer = (layer) => { // if the point selected is new if (!isAlreadySelected(id)) { - // if this is a layer we can geo-point on - if (validLayerTypes.has(layer.properties['product_name'])) { - // create a marker target icon around the observation clicked - markClicked(map, e, id); - - // get the FQDN of the UI data server - const data_url = `${getNamespacedEnvParam('REACT_APP_UI_DATA_URL')}`; - - // split the URL - const split_url = layer.properties['tds_download_url'].split('/'); - - // generate the base TDS svr hostname/url - const tds_svr = split_url[0] + '//' + split_url[2] + '/thredds'; - - // create the correct TDS URL without the hostname - const tds_url = layer.properties['tds_download_url'].replace('catalog', 'dodsC').replace('catalog.html', - (layer.id.indexOf('swan') < 0 ? 'fort' : 'swan_HS') + '.63.nc').split('/thredds')[1]; - - // generate the full url - const fullTDSURL = data_url + "get_geo_point_data?lon=" + e.latlng.lng + "&lat=" + e.latlng.lat + "&ensemble=nowcast" + - '&tds_svr=' + tds_svr + '&url=' + tds_url; - - const l_props = layer.properties; - - // create a set of properties for this object - const pointProps = - { - "station_name": l_props['product_name'] + " " + id, - "lat": lat, - "lon": lon, - "location_name": layer.properties['product_name'].split(' ').slice(1).join(' ') + " at (lon, lat): " + id, - "model_run_id": layer.group, - "data_source": (l_props['event_type'] + '_' + l_props['grid_type']).toUpperCase(), - "source_name": l_props['model'], - "source_instance": l_props['instance_name'], - "source_archive": l_props['location'], - "forcing_metclass": l_props['met_class'], - "location_type": "GeoPoint", - "grid_name": l_props['grid_type'].toUpperCase(), - "csvurl": fullTDSURL, - "id": id - }; - - // populate selectedObservations list with the newly selected observation point - setSelectedObservations(previous => [...previous, pointProps]); - } else + // this can only happen when we are not in compare mode + if (!inCompareMode()) { + // if this is a good layer product + if (validLayerTypes.has(layer.properties['product_name'])) { + // create a marker target icon around the observation clicked + markClicked(map, e, id); + + // get the FQDN of the UI data server + const data_url = `${getNamespacedEnvParam('REACT_APP_UI_DATA_URL')}`; + + // split the URL + const split_url = layer.properties['tds_download_url'].split('/'); + + // generate the base TDS svr hostname/url + const tds_svr = split_url[0] + '//' + split_url[2] + '/thredds'; + + // create the correct TDS URL without the hostname + const tds_url = layer.properties['tds_download_url'].replace('catalog', 'dodsC').replace('catalog.html', + (layer.id.includes('swan') ? 'swan_HS' : 'fort') + '.63.nc').split('/thredds')[1]; + + // generate the full url + const fullTDSURL = data_url + "get_geo_point_data?lon=" + e.latlng.lng + "&lat=" + e.latlng.lat + "&ensemble=nowcast" + + '&tds_svr=' + tds_svr + '&url=' + tds_url; + + const l_props = layer.properties; + + // create a set of properties for this object + const pointProps = + { + "station_name": l_props['product_name'] + " " + id, + "lat": lat, + "lon": lon, + "location_name": layer.properties['product_name'].split(' ').slice(1).join(' ') + " at (lon, lat): " + id, + "model_run_id": layer.group, + "data_source": (l_props['event_type'] + '_' + l_props['grid_type']).toUpperCase(), + "source_name": l_props['model'], + "source_instance": l_props['instance_name'], + "source_archive": l_props['location'], + "forcing_metclass": l_props['met_class'], + "location_type": "GeoPoint", + "grid_name": l_props['grid_type'].toUpperCase(), + "csvurl": fullTDSURL, + "id": id + }; + + // populate selectedObservations list with the newly selected observation point + setSelectedObservations(previous => [...previous, pointProps]); + } else + setAlertMsg({ + 'severity': 'warning', + 'msg': 'Geo-point selection is not available for the ' + layer.properties['product_name'] + ' product.' + }); + } + else setAlertMsg({ 'severity': 'warning', - 'msg': 'Geo-point selection is not available for the ' + layer.properties['product_name'] + ' product.' + 'msg': 'Geo-point selection is not available while in compare mode.' }); } }); @@ -165,7 +185,7 @@ export const AdcircRasterLayer = (layer) => { sld_body: currentStyle, }), [currentStyle]); - return currentStyle && ( + return currentStyle && productType && ( { const { product_type } = layer.properties; @@ -93,7 +94,7 @@ export const DefaultLayers = () => { const shared_params = parseSharedURL(); // create the URLs to the data endpoints - const data_url = `${ getNamespacedEnvParam('REACT_APP_UI_DATA_URL') }get_ui_data_secure?limit=1&use_new_wb=true&use_v3_sp=true${ getBrandingHandler() }${ shared_params['run_id'] }`; + const data_url = `${ getNamespacedEnvParam('REACT_APP_UI_DATA_URL') }get_ui_data_secure?limit=1&use_new_wb=true&use_v3_sp=true${ getBrandingHandler() + getDefaultInstanceName() }${ shared_params['run_id'] }`; const gs_wfs_url = `${ getNamespacedEnvParam('REACT_APP_GS_DATA_URL') }`; // retrieve the catalog member with the provided id @@ -136,6 +137,7 @@ export const DefaultLayers = () => { } return(data); }; + useQuery({ queryKey: ['apsviz-default-data', data_url], queryFn: getDefaultLayers, diff --git a/src/context/map-context.js b/src/context/map-context.js index 0ad8e6e..48a3f57 100644 --- a/src/context/map-context.js +++ b/src/context/map-context.js @@ -40,14 +40,28 @@ const layerTypes = { }; export const LayersProvider = ({ children }) => { + // default and hurricane layer states const [defaultModelLayers, setDefaultModelLayers] = useState([]); const [hurricaneTrackLayers, setHurricaneTrackLayers] = useState([]); // this object contains data for graph rendering const [selectedObservations, setSelectedObservations] = useState([]); + // map reference state const [map, setMap] = useState(null); + // base map state + const [baseMap, setBaseMap] = useState(); + + // used to track the view state of the share comment + const [showShareComment, setShowShareComment] = useState(true); + + // used to show alerts + const [alertMsg, setAlertMsg] = useState(null); + + // state to capture the default startup instance name + const [defaultInstanceName, setDefaultInstanceName] = useState(null); + /** * this section is for the side-by-side compare mode items * @type {string} @@ -361,20 +375,14 @@ export const LayersProvider = ({ children }) => { setDefaultModelLayers([...newLayers]); }; - const [baseMap, setBaseMap] = React.useState(); - - // used to track the view state of the share comment - const [showShareComment, setShowShareComment] = useState(true); - - // used to show alerts - const [alertMsg, setAlertMsg] = useState(null); - return (