From f4ef14a5f844ffa8ba1440f77f87f70fee906a96 Mon Sep 17 00:00:00 2001
From: roby <roby@ipac.caltech.edu>
Date: Sat, 3 Jun 2023 14:28:48 -0600
Subject: [PATCH 1/3] Firefly-1252: fixed issues with there are not spacial
 columns

  - changed the processing of tap.additional.services to better reflex the config orider
  - added hide property to tap.additional.services
  - temporal column check
---
 src/firefly/js/ui/tap/SpatialSearch.jsx      |  6 ++++-
 src/firefly/js/ui/tap/TableSearchHelpers.jsx |  3 +++
 src/firefly/js/ui/tap/TapUtil.js             | 21 ++++++++++------
 src/firefly/js/ui/tap/TemportalSearch.jsx    | 25 +++++++++++++++-----
 4 files changed, 41 insertions(+), 14 deletions(-)

diff --git a/src/firefly/js/ui/tap/SpatialSearch.jsx b/src/firefly/js/ui/tap/SpatialSearch.jsx
index 907f39e863..91620d510b 100644
--- a/src/firefly/js/ui/tap/SpatialSearch.jsx
+++ b/src/firefly/js/ui/tap/SpatialSearch.jsx
@@ -127,13 +127,14 @@ export function SpatialSearch({cols, serviceUrl, serviceLabel, columnsModel, tab
         }
     }, [searchParams.radiusInArcSec, searchParams.wp, searchParams.corners, searchParams.uploadInfo]);
 
-
     useEffect(() => {
         const {lon,lat} = formCenterColumns(columnsModel);
         setVal(CenterLonColumns, lon, {validator: getColValidator(cols, true, false), valid: true});
         setVal(CenterLatColumns, lat, {validator: getColValidator(cols, true, false), valid: true});
         const noDefaults= !lon || !lat;
         setVal(posOpenKey, (noDefaults) ? 'open' : 'closed');
+        if (noDefaults) checkHeaderCtl.setPanelActive(false);
+        checkHeaderCtl.setPanelOpen(!noDefaults);
         setPosOpenMsg(noDefaults?'':TAB_COLUMNS_MSG);
     }, [columnsModel, obsCoreEnabled]);
 
@@ -717,6 +718,9 @@ function makeSpatialConstraints(columnsModel, obsCoreEnabled, fldObj, uploadInfo
         const cenLat= cenLatField?.value;
         errList.checkForError(cenLonField);
         errList.checkForError(cenLatField);
+        if (!cenLon && !cenLat) errList.addError('Lon and Lat columns are not set');
+        else if (!cenLon) errList.addError('Lon column is not set');
+        else if (!cenLat) errList.addError('Lat column is not set');
         const ucdCoord = getUCDCoord(columnsModel, cenLon);
         const worldSys = posCol[ucdCoord.key].coord;
         const adqlCoordSys = posCol[ucdCoord.key].adqlCoord;
diff --git a/src/firefly/js/ui/tap/TableSearchHelpers.jsx b/src/firefly/js/ui/tap/TableSearchHelpers.jsx
index 8b6db7d06d..13d0b50891 100644
--- a/src/firefly/js/ui/tap/TableSearchHelpers.jsx
+++ b/src/firefly/js/ui/tap/TableSearchHelpers.jsx
@@ -160,9 +160,12 @@ export function makeCollapsibleCheckHeader(base) {
 
     retObj.CollapsibleCheckHeader= ({title,helpID,message,initialStateOpen, initialStateChecked,children}) => {
         const [getPanelActive, setPanelActive] = useFieldGroupValue(panelCheckKey);// eslint-disable-line react-hooks/rules-of-hooks
+        const [getPanelOpenStatus, setPanelOpenStatus] = useFieldGroupValue(panelKey);// eslint-disable-line react-hooks/rules-of-hooks
         const isActive= getPanelActive() === panelValue;
         retObj.isPanelActive= () => getPanelActive() === panelValue;
         retObj.setPanelActive= (active) => setPanelActive(active ? panelValue : '');
+        retObj.isPanelOpen= () => getPanelOpenStatus() === 'open';
+        retObj.setPanelOpen= (open) => setPanelOpenStatus(open?'open':'close');
         return (
             <InternalCollapsibleCheckHeader {...{title, helpID, checkKey:panelCheckKey, fieldKey:panelKey,
                                             message: isActive ? message:'', initialStateChecked, panelValue,
diff --git a/src/firefly/js/ui/tap/TapUtil.js b/src/firefly/js/ui/tap/TapUtil.js
index 1f217a1045..2589723e90 100644
--- a/src/firefly/js/ui/tap/TapUtil.js
+++ b/src/firefly/js/ui/tap/TapUtil.js
@@ -287,13 +287,20 @@ export const getAsEntryForTableName= (tableName) => tableName?.[0] ?? 'x';
 function mergeAdditionalServices(tapServices, additional) {
     if (!hasElements(additional)) return tapServices;
 
-    const modifiedOriginal= tapServices.map( (t) => {
-        const match= additional.find( (a) => a.label===t.label);
-        return match ? {...t,...match} : t;
-    });
-    const trulyAdditional= additional.filter( (a) => !tapServices.find( (t) => t.label===a.label) );
-
-    return [...trulyAdditional,...modifiedOriginal];
+    const mergeAdditional= additional.map( (a) => {
+        if (a.hide) return false;
+        const originalEntry= tapServices.find( (t) => a.label===t.label);
+        return originalEntry ? {...originalEntry, ...a} : a;
+    }).filter( (s) => s);
+
+    const unmodifiedOriginal= tapServices
+        .map( (t) => {
+            const match= additional.find( (a) => a.label===t.label);
+            return !match && t;
+        })
+        .filter( (s) => s);
+
+    return [...mergeAdditional,...unmodifiedOriginal];
 }
 
 export function getTapServices(webApiUserAddedService) {
diff --git a/src/firefly/js/ui/tap/TemportalSearch.jsx b/src/firefly/js/ui/tap/TemportalSearch.jsx
index d27a264343..d456f78af9 100644
--- a/src/firefly/js/ui/tap/TemportalSearch.jsx
+++ b/src/firefly/js/ui/tap/TemportalSearch.jsx
@@ -1,6 +1,7 @@
 import React, {useContext, useEffect, useState} from 'react';
 import PropTypes from 'prop-types';
 import {ColsShape, ColumnFld, getColValidator} from '../../charts/ui/ColumnOrExpression.jsx';
+import {getColumnIdx} from '../../tables/TableUtil.js';
 import {FieldGroupCtx, ForceFieldGroupValid} from '../FieldGroup.jsx';
 import {useFieldGroupRerender, useFieldGroupWatch} from '../SimpleComponent.jsx';
 import {checkExposureTime} from '../TimeUIUtil.js';
@@ -76,6 +77,18 @@ const {CollapsibleCheckHeader, collapsibleCheckHeaderKeys}= checkHeaderCtl;
 
 const fldAry=[TimeTo,TimeFrom,TemporalColumns];
 
+export function findTimeColumn(columnsTable) {
+    const ucdIdx= getColumnIdx(columnsTable,'ucd',true);
+    const nIdx= getColumnIdx(columnsTable,'column_name',true);
+    const unitIdx= getColumnIdx(columnsTable,'unit',true);
+    if (ucdIdx===-1 || nIdx===-1) return;
+    const timeRows= columnsTable.tableData.data.filter( (row) => row[ucdIdx]?.includes('time.epoch') && row[unitIdx].startsWith('d'));
+    if (!timeRows.length) return;
+    const mainRows= timeRows.filter( (row) => row[ucdIdx]?.includes('meta.main') && row[unitIdx].startsWith('d'));
+    return mainRows.length ? mainRows[0][nIdx] : timeRows[0][nIdx];
+}
+
+
 /**
  *
  * @param props
@@ -86,7 +99,7 @@ const fldAry=[TimeTo,TimeFrom,TemporalColumns];
 export function TemporalSearch({cols, columnsModel}) {
     const [constraintResult, setConstraintResult] = useState({});
 
-    const {setFld,getVal,makeFldObj}= useContext(FieldGroupCtx);
+    const {setFld,setVal,getVal,makeFldObj}= useContext(FieldGroupCtx);
     const {setConstraintFragment}= useContext(ConstraintContext);
     useFieldGroupRerender([...fldAry, ...collapsibleCheckHeaderKeys]); // force rerender on any change
     const timeCol= getVal(TemporalColumns);
@@ -97,10 +110,7 @@ export function TemporalSearch({cols, columnsModel}) {
         ([tcVal],isInit) => {
             if (!tcVal) return;
             const timeColumns = tcVal.split(',').map( (c) => c.trim()) ?? [];
-            if (timeColumns.length === 1) {
-                if (!isInit) checkHeaderCtl.setPanelActive(true);
-            }
-            else {
+            if (timeColumns.length > 1) {
                 setFld(TemporalColumns, {value:tcVal, valid:false, message: 'you may only choose one column'});
             }
         });
@@ -117,7 +127,10 @@ export function TemporalSearch({cols, columnsModel}) {
 
 
     useEffect(() => {
-        setFld(TemporalColumns, {validator: getColValidator(cols, true, false), valid: true});
+        const timeCol= findTimeColumn(columnsModel) ?? '';
+        setVal(TemporalColumns, timeCol, {validator: getColValidator(cols, true, false), valid: true});
+        checkHeaderCtl.setPanelOpen(Boolean(timeCol));
+        // checkHeaderCtl.setPanelActive(Boolean(timeCol));
     }, [columnsModel]);
 
 

From 907f99edc86d0dbb767119a109e3f54eb8653ff6 Mon Sep 17 00:00:00 2001
From: loi <loi@ipac.caltech.edu>
Date: Tue, 6 Jun 2023 14:51:09 -0700
Subject: [PATCH 2/3] - fixed regression bug when TAP result is not the first
 item of /results.  It should use /results/result instead.

---
 .../caltech/ipac/firefly/server/query/AsyncTapQuery.java   | 7 +++++++
 .../caltech/ipac/firefly/server/query/UwsJobProcessor.java | 2 +-
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/query/AsyncTapQuery.java b/src/firefly/java/edu/caltech/ipac/firefly/server/query/AsyncTapQuery.java
index 56fa49934e..ab96b8ecf2 100644
--- a/src/firefly/java/edu/caltech/ipac/firefly/server/query/AsyncTapQuery.java
+++ b/src/firefly/java/edu/caltech/ipac/firefly/server/query/AsyncTapQuery.java
@@ -3,9 +3,11 @@
  */
 package edu.caltech.ipac.firefly.server.query;
 
+import edu.caltech.ipac.firefly.core.background.JobInfo;
 import edu.caltech.ipac.firefly.data.TableServerRequest;
 import edu.caltech.ipac.firefly.server.ServerContext;
 import edu.caltech.ipac.firefly.server.network.HttpServiceInput;
+import edu.caltech.ipac.firefly.server.util.QueryUtil;
 import edu.caltech.ipac.table.DataGroup;
 import edu.caltech.ipac.table.DataType;
 import edu.caltech.ipac.table.TableUtil;
@@ -79,4 +81,9 @@ public HttpServiceInput createInput(TableServerRequest request) throws DataAcces
         }
         return inputs;
     }
+
+    @Override
+    public DataGroup getResult(TableServerRequest request) throws DataAccessException {
+        return getTableResult(getJobUrl() + "/results/result", QueryUtil.getTempDir(request));
+    }
 }
diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/query/UwsJobProcessor.java b/src/firefly/java/edu/caltech/ipac/firefly/server/query/UwsJobProcessor.java
index 8475956ab9..28b608ab76 100644
--- a/src/firefly/java/edu/caltech/ipac/firefly/server/query/UwsJobProcessor.java
+++ b/src/firefly/java/edu/caltech/ipac/firefly/server/query/UwsJobProcessor.java
@@ -171,7 +171,7 @@ static String getFilename(String urlStr) {
 //====================================================================
 //  UWS utils
 //====================================================================
-    private static DataAccessException createDax(String url, String title, String errMsg) {
+    static DataAccessException createDax(String url, String title, String errMsg) {
         String msg = String.format("%s from the URL: [%s]", title, url);
         if (errMsg != null) msg += "\n\t with exception: " + errMsg;
         return new DataAccessException(msg);

From 5bbc10a650ef08be07d287650be4974d64f5b0ae Mon Sep 17 00:00:00 2001
From: roby <roby@ipac.caltech.edu>
Date: Wed, 7 Jun 2023 09:30:00 -0600
Subject: [PATCH 3/3] Firefly-1252: column validator now accepts an error
 message parameter

  - also set the error messages for spatial and temporal
---
 src/firefly/js/charts/ui/ColumnOrExpression.jsx |  8 ++++----
 src/firefly/js/ui/tap/SpatialSearch.jsx         | 10 ++++++----
 src/firefly/js/ui/tap/TemportalSearch.jsx       |  4 ++--
 3 files changed, 12 insertions(+), 10 deletions(-)

diff --git a/src/firefly/js/charts/ui/ColumnOrExpression.jsx b/src/firefly/js/charts/ui/ColumnOrExpression.jsx
index 7566aea1bb..e6eb40af58 100644
--- a/src/firefly/js/charts/ui/ColumnOrExpression.jsx
+++ b/src/firefly/js/charts/ui/ColumnOrExpression.jsx
@@ -46,14 +46,14 @@ function parseSuggestboxContent(text) {
     return {token, priorContent};
 }
 
-export function getColValidator(cols, required=true, canBeExpression=true) {
+const DEFAULT_MSG= 'Can not be empty. Please provide value or expression';
+
+export function getColValidator(cols, required=true, canBeExpression=true, message=DEFAULT_MSG) {
     const colNames = cols.map((colVal) => {return colVal.name;});
     return (val) => {
         let retval = {valid: true, message: ''};
         if (!val) {
-            if (required) {
-                return {valid: false, message: 'Can not be empty. Please provide value or expression'};
-            }
+            if (required) return {valid: false, message};
         } else if (colNames.indexOf(val) < 0) {
             if (canBeExpression) {
                 const expr = new Expression(val, colNames);
diff --git a/src/firefly/js/ui/tap/SpatialSearch.jsx b/src/firefly/js/ui/tap/SpatialSearch.jsx
index 91620d510b..459a64e2ac 100644
--- a/src/firefly/js/ui/tap/SpatialSearch.jsx
+++ b/src/firefly/js/ui/tap/SpatialSearch.jsx
@@ -120,8 +120,9 @@ export function SpatialSearch({cols, serviceUrl, serviceLabel, columnsModel, tab
             const columns = searchParams.uploadInfo.columns;
             const centerCols = findTableCenterColumns({tableData:{columns}}) ?? {};
             const {lonCol='', latCol=''}= centerCols;
-            setVal(UploadCenterLonColumns, lonCol, {validator: getColValidator(searchParams.uploadInfo.columns, true, false), valid: true});
-            setVal(UploadCenterLatColumns, latCol, {validator: getColValidator(searchParams.uploadInfo.columns, true, false), valid: true});
+            const errMsg= 'Upload tables require identifying spatial columns containing equatorial coordinates.  Please provide column names.';
+            setVal(UploadCenterLonColumns, lonCol, {validator: getColValidator(searchParams.uploadInfo.columns, true, false,errMsg), valid: true});
+            setVal(UploadCenterLatColumns, latCol, {validator: getColValidator(searchParams.uploadInfo.columns, true, false,errMsg), valid: true});
             setUploadInfo(searchParams.uploadInfo);
             checkHeaderCtl.setPanelActive(true);
         }
@@ -129,8 +130,9 @@ export function SpatialSearch({cols, serviceUrl, serviceLabel, columnsModel, tab
 
     useEffect(() => {
         const {lon,lat} = formCenterColumns(columnsModel);
-        setVal(CenterLonColumns, lon, {validator: getColValidator(cols, true, false), valid: true});
-        setVal(CenterLatColumns, lat, {validator: getColValidator(cols, true, false), valid: true});
+        const errMsg= 'Spatial searches require identifying table columns containing equatorial coordinates.  Please provide column names.';
+        setVal(CenterLonColumns, lon, {validator: getColValidator(cols, true, false, errMsg), valid: true});
+        setVal(CenterLatColumns, lat, {validator: getColValidator(cols, true, false, errMsg), valid: true});
         const noDefaults= !lon || !lat;
         setVal(posOpenKey, (noDefaults) ? 'open' : 'closed');
         if (noDefaults) checkHeaderCtl.setPanelActive(false);
diff --git a/src/firefly/js/ui/tap/TemportalSearch.jsx b/src/firefly/js/ui/tap/TemportalSearch.jsx
index d456f78af9..854073d866 100644
--- a/src/firefly/js/ui/tap/TemportalSearch.jsx
+++ b/src/firefly/js/ui/tap/TemportalSearch.jsx
@@ -128,9 +128,9 @@ export function TemporalSearch({cols, columnsModel}) {
 
     useEffect(() => {
         const timeCol= findTimeColumn(columnsModel) ?? '';
-        setVal(TemporalColumns, timeCol, {validator: getColValidator(cols, true, false), valid: true});
+        const errMsg= 'Temporal searches require identifying a table column containing a time in MJD.  Please provide a column name.';
+        setVal(TemporalColumns, timeCol, {validator: getColValidator(cols, true, false, errMsg), valid: true});
         checkHeaderCtl.setPanelOpen(Boolean(timeCol));
-        // checkHeaderCtl.setPanelActive(Boolean(timeCol));
     }, [columnsModel]);