5b8fdca0d6014f4b2f363724ee91f096ef62eae5
chmalee
  Thu Sep 30 12:21:57 2021 -0700
Get hub track selection box and hgTrackUi links working on track search

diff --git src/hg/hgTracks/searchTracks.c src/hg/hgTracks/searchTracks.c
index 8564780..1e5a2b3 100644
--- src/hg/hgTracks/searchTracks.c
+++ src/hg/hgTracks/searchTracks.c
@@ -34,30 +34,35 @@
 
 #define TRACK_SEARCH_FORM        "trackSearch"
 #define SEARCH_RESULTS_FORM      "searchResults"
 #define TRACK_SEARCH_CURRENT_TAB "tsCurTab"
 #define TRACK_SEARCH_SIMPLE      "tsSimple"
 #define TRACK_SEARCH_ON_NAME     "tsName"
 #define TRACK_SEARCH_ON_TYPE     "tsType"
 #define TRACK_SEARCH_ON_GROUP    "tsGroup"
 #define TRACK_SEARCH_ON_DESCR    "tsDescr"
 #define TRACK_SEARCH_SORT        "tsSort"
 #define TRACK_SEARCH_ON_HUBS     "tsIncludePublicHubs"
 
 // the list of found tracks
 struct slRef *tracks = NULL;
 
+// for advanced search only:
+// associates hub id's to hub urls that have search results,
+// used to get huburls onto hgTrackUi links
+struct hash *hubIdsToUrls = NULL;
+
 static int gCmpGroup(const void *va, const void *vb)
 /* Compare groups based on label. */
 {
 const struct group *a = *((struct group **)va);
 const struct group *b = *((struct group **)vb);
 return strcmp(a->label, b->label);
 }
 
 // Would like to do a radio button choice ofsorts
 enum sortBy
     {
     sbRelevance=0,
     sbAbc      =1,
     sbHierarchy=2,
     };
@@ -296,71 +301,75 @@
     pfd->done = TRUE;
     if (measureTiming)
         {
         pfd->searchTime = clock1000() - startTime;;
         measureTime("Finished finding tracks for hub '%s': ", pfd->hubName);
         }
     slRemoveEl(&pfdRunning, pfd);
     slAddHead(&pfdDone, pfd);
     pthread_mutex_unlock(&pfdMutex);
     }
 errCatchEnd(errCatch);
 //always return NULL for pthread_create()
 return NULL;
 }
 
-static void hubSearchHashToPfdList(struct hash *searchResultsHash, struct hash *hubLookup,
-                                    struct sqlConnection *conn)
+static void hubSearchHashToPfdList(char *descSearch, struct hash *searchResultsHash,
+                                    struct hash *hubLookup, struct sqlConnection *conn)
 /* getHubSearchResults() returned a hash of search hits to various hubs, convert that
  * into something we can work on in parallel */
 {
 struct hubSearchTracks *ret = NULL;
 struct hashCookie cookie = hashFirst(searchResultsHash);
 struct hash *hubUrlsToTrackList = hashNew(0);
 struct hashEl *hel = NULL;
 struct dyString *trackName = dyStringNew(0);
 struct slName *connectedHubs = hubConnectHubsInCart(cart);
 while ((hel = hashNext(&cookie)) != NULL)
     {
     struct hubSearchText *hst = (struct hubSearchText *)hel->val;
     struct hubEntry *hubInfo = (struct hubEntry *) hashFindVal(hubLookup, hst->hubUrl);
     if (isNotEmpty(hubInfo->errorMessage))
         continue;
-    // if we were already checking out this hub, then it's search hits
+    // if we were already connected to this hub, then it's search hits
     // were already taken care of by the regular search code
     char hubId[256];
     safef(hubId, sizeof(hubId), "%d", hubInfo->id);
     if (slNameInList(connectedHubs, hubId))
         continue;
     struct hubConnectStatus *hub = hubConnectStatusForId(conn, hubInfo->id);
     // the hubSearchText contains multiple entries per lookup in order to form
     // the hgHubConnect UI. We only need one type of hit here:
     struct hubSearchTracks *found = hashFindVal(hubUrlsToTrackList, hst->hubUrl);
     for (; hst != NULL; hst = hst->next)
         {
         // don't add results for matches to the hub descriptionUrl, only to track names/descs
         if (isNotEmpty(hst->track) && hst->textLength != hubSearchTextMeta)
             {
+            // hst->textLength=hubSearchTextLong denotes hits to track description
+            if (isNotEmpty(descSearch) && hst->textLength != hubSearchTextLong)
+                continue;
             if (!found)
                 {
                 AllocVar(found);
                 found->hubUrl = hst->hubUrl;
                 found->searchedTracks = NULL;
                 found->hub = hub;
                 found->hubId = hubInfo->id;
                 slAddHead(&ret, found);
                 hashAdd(hubUrlsToTrackList, hst->hubUrl, found);
+                hashAdd(hubIdsToUrls, hubId, hst->hubUrl);
                 }
             dyStringPrintf(trackName, "%s%d_%s", hubTrackPrefix, hubInfo->id, hst->track);
             slNameStore(&found->searchedTracks, cloneString(trackName->string));
             dyStringClear(trackName);
             }
         }
     }
 struct hubSearchTracks *t;
 for (t = ret; t != NULL; t = t->next)
     {
     struct paraFetchData *pfd;
     AllocVar(pfd);
     pfd->hubName = t->hubUrl;
     pfd->hst = t;
     slAddHead(&pfdList, pfd);
@@ -441,60 +450,62 @@
     {
     // Should we warn or something that results are still waiting? As of now
     // just silently return instead, and note that no unconnected hub data
     // will show up (we get connected hub results for free because of
     // trackDbCaching)
     if (measureTiming)
         measureTime("Timed out searching hubs");
     }
 if (lockStatus == 0)
     pthread_mutex_unlock(&pfdMutex);
 }
 
 void addHubSearchResults(struct slName *nameList, char *descSearch)
 /* add public hubs to the track list */
 {
-// set to something large so we always use the udc cache
 struct sqlConnection *conn = hConnectCentral();
 char *hubSearchTableName = hubSearchTextTableName();
 char *publicTable = cfgOptionEnvDefault("HGDB_HUB_PUBLIC_TABLE",
     hubPublicTableConfVariable, defaultHubPublicTableName);
 char *statusTable = cfgOptionEnvDefault("HGDB_HUB_STATUS_TABLE",
     hubStatusTableConfVariable, defaultHubStatusTableName);
 struct dyString *extra = dyStringNew(0);
 if (nameList)
     {
     struct slName *tmp = NULL;
+    char escapedInput[512];
     for (tmp = nameList; tmp != NULL; tmp = tmp->next)
         {
-        dyStringPrintf(extra, "label like '%%%s%%'", tmp->name);
+        // escape user input manually:
+        sqlSafefFrag(escapedInput, sizeof(escapedInput), "%s", tmp->name);
+        dyStringPrintf(extra, "label like '%%%s%%'", escapedInput);
         if (tmp->next)
             dyStringPrintf(extra, " and ");
         }
     }
 
 if (sqlTableExists(conn, hubSearchTableName))
     {
     struct hash *searchResultsHash = hashNew(0);
     struct hash *pHash = hashNew(0);
     struct slName *hubsToPrint = NULL;
     addPublicHubsToHubStatus(cart, conn, publicTable, statusTable);
     struct hash *hubLookup = buildPublicLookupHash(conn, publicTable, statusTable, &pHash);
     char *db = cloneString(trackHubSkipHubName(database));
     tolowers(db);
-    getHubSearchResults(conn, hubSearchTableName, descSearch, descSearch != NULL, db, hubLookup, &searchResultsHash, &hubsToPrint, extra->string);
-    hubSearchHashToPfdList(searchResultsHash, hubLookup, conn);
+    getHubSearchResults(conn, hubSearchTableName, descSearch, isNotEmpty(descSearch), db, hubLookup, &searchResultsHash, &hubsToPrint, dyStringCannibalize(&extra));
+    hubSearchHashToPfdList(descSearch, searchResultsHash, hubLookup, conn);
     if (measureTiming)
         measureTime("after querying hubSearchText table and ready to start threads");
     int ptMax = atoi(cfgOptionDefault("parallelFetch.threads", "20"));
     int pfdListCount = 0, pt;
     if (ptMax > 0)
         {
         pfdListCount = slCount(pfdList);
         pthread_t *threads = NULL;
         ptMax = min(ptMax, pfdListCount);
         if (ptMax > 0)
             {
             AllocArray(threads, ptMax);
             for (pt = 0; pt < ptMax; pt++)
                 {
                 int rc = pthread_create(&threads[pt], NULL, addUnconnectedHubSearchResults, &threads[pt]);
@@ -813,115 +824,164 @@
         cgiMakeOnEventRadioButtonWithClass(TRACK_SEARCH_SORT, "1", (sortBy == sbAbc), 
 	    NULL,"click", "findTracks.sortNow(this);");
         hPrintf("Alphabetically");
         cgiMakeOnEventRadioButtonWithClass(TRACK_SEARCH_SORT, "2", (sortBy == sbHierarchy), 
 	    NULL,"click", "findTracks.sortNow(this);");
         hPrintf("by Hierarchy&nbsp;&nbsp;</span>\n");
         }
     hPrintf("</td></tr>\n");
 
     // Set up json for js functionality
     struct jsonElement *jsonTdbVars = newJsonObject(newHash(8));
 
     int trackCount=0;
     boolean containerTrackCount = 0;
     struct slRef *ptr;
+    struct slName *connectedHubIds = hubConnectHubsInCart(cart);
     while((ptr = slPopHead(&tracks)))
         {
         if (++trackCount > MAX_FOUND_TRACKS)
             break;
 
         struct track *track = (struct track *) ptr->val;
         jsonTdbSettingsBuild(jsonTdbVars, track, FALSE); // FALSE: No config from track search
+        char *hubId = NULL;
+        if (isHubTrack(track->track))
+            {
+            char *trackNameCopy = cloneString(track->track);
+            hubId = strchr(trackNameCopy, '_');
+            hubId += 1;
+            char *ptr2 = strchr(hubId, '_');
+            if (ptr2 == NULL)
+                errAbort("hub track '%s' not in correct format", track->track);
+            *ptr2 = '\0';
+            char *hubUrl = hashFindVal(hubIdsToUrls, hubId);
+            if (hubUrl != NULL)
+                {
+                struct jsonElement *ele = jsonFindNamedField(jsonTdbVars, "", track->track);
+                jsonObjectAdd(ele, "hubUrl", newJsonString(hubUrl));
+                }
+            }
 
         if (tdbIsFolder(track->tdb)) // supertrack
             hPrintf("<tr class='bgLevel4' valign='top' class='found'>\n");
         else if (tdbIsContainer(track->tdb))
             hPrintf("<tr class='bgLevel3' valign='top' class='found'>\n");
         else
             hPrintf("<tr class='bgLevel2' valign='top' class='found'>\n");
 
         hPrintf("<td align='center'>\n");
 
         // Determine visibility and checked state
         track->visibility = tdbVisLimitedByAncestors(cart, track->tdb, TRUE, TRUE);
         boolean checked = ( track->visibility != tvHide );
         if (tdbIsContainerChild(track->tdb))
             {
             // Don't need all 4 states here.  Visible=checked&&enabled
             checked = fourStateVisible(subtrackFourStateChecked(track->tdb,cart));
             // Checked is only if subtrack level vis is also set!
             checked = (checked && ( track->visibility != tvHide ));
             }
+        // if we haven't already connected to this hub, then by default
+        // we need to unselect every checkbox
+        if (isHubTrack(track->track) && !slNameInList(connectedHubIds, hubId))
+            checked = FALSE;
 
         // Setup the check box
         #define CB_HIDDEN_VAR "<INPUT TYPE=HIDDEN disabled=true NAME='%s_sel' VALUE='%s'>"
         // subtracks and folder children get "_sel" var. ("_sel" var is temp on folder children)
         if (tdbIsContainerChild(track->tdb) || tdbIsFolderContent(track->tdb))
             hPrintf(CB_HIDDEN_VAR,track->track,checked?"1":CART_VAR_EMPTY);
         #define CB_SEEN "<INPUT TYPE=CHECKBOX id='%s_sel_id' VALUE='on' class='selCb' %s>"
         hPrintf(CB_SEEN,track->track,(checked ? " CHECKED" : ""));
 	safef(id, sizeof id, "%s_sel_id", track->track); // XSS Filter?
 	jsOnEventById("click", id, "findTracks.clickedOne(this,true);");  
         hPrintf("</td><td>\n");
 
         // Setup the visibility drop down
         #define VIS_HIDDEN_VAR "<INPUT TYPE=HIDDEN disabled=true NAME='%s' VALUE='%s'>"
         hPrintf(VIS_HIDDEN_VAR,track->track,CART_VAR_EMPTY); // All tracks get vis hidden var
 	
 	safef(id, sizeof id, "%s_id", track->track); // XSS Filter?
 	safef(javascript, sizeof javascript, "findTracks.changeVis(this);");
 	struct slPair *event = slPairNew("change", cloneString(javascript));
         if (tdbIsFolder(track->tdb))
             {
             hideShowDropDownWithClassAndExtra(track->track, id, (track->visibility != tvHide),
                                               "normalText visDD", event);
             }
         else
             {
             hTvDropDownClassWithJavascript(NULL, id, track->visibility,track->canPack,
                                            "normalText seenVis",event);
             }
+        char *hubId = NULL;
+        if (isHubTrack(track->track))
+            {
+            char *trackNameCopy = cloneString(track->track);
+            hubId = strchr(trackNameCopy, '_');
+            hubId += 1;
+            char *ptr2 = strchr(hubId, '_');
+            if (ptr2 == NULL)
+                errAbort("hub track '%s' not in correct format", track->track);
+            *ptr2 = '\0';
+            }
 
         // If this is a container track, allow configuring...
         if (tdbIsContainer(track->tdb) || tdbIsFolder(track->tdb))
             {
             containerTrackCount++; // Using onclick ensures return to search tracks on submit
             hPrintf("&nbsp;<IMG SRC='../images/folderWrench.png' style='cursor:pointer;' "
                     "id='%s_confSet' title='Configure this track container...' "
                     ">&nbsp;", track->track);
             safef(id, sizeof id, "%s_confSet", track->track); // XSS Filter?
-	    jsOnEventByIdF("click", id, "findTracks.configSet(\"%s\");", track->track);  
+            char hubConfigUrl[4096];
+            safef(hubConfigUrl, sizeof(hubConfigUrl), "%s", track->track);
+            if (isHubTrack(track->track))
+                {
+                char *hubUrl = hashFindVal(hubIdsToUrls, hubId);
+                if (hubUrl != NULL)
+                    safefcat(hubConfigUrl, sizeof(hubConfigUrl), "&hubUrl=%s", hubUrl);
+                }
+            jsOnEventByIdF("click", id, "findTracks.configSet(\"%s\");", hubConfigUrl);
             }
 //#define SHOW_PARENT_FOLDER
 #ifdef SHOW_PARENT_FOLDER
         else if (tdbIsContainerChild(track->tdb) || tdbIsFolderContent(track->tdb))
             {
             struct trackDb *parentTdb =
                             tdbIsContainerChild(track->tdb) ? tdbGetContainer(track->tdb)
                                                             : tdbGetImmediateFolder(track->tdb);
             if (parentTdb != NULL) // Using href will not return to search tracks on submit
                 hPrintf("&nbsp;<A HREF='../cgi-bin/hgTrackUi?g=%s'><IMG SRC='../images/folderC.png'"
                         " title='Navigate to parent container...'></A>&nbsp;", parentTdb->track);
             }
 #endif///def SHOW_PARENT_FOLDER
         hPrintf("</td>\n");
 
         // shortLabel has description popup and longLabel has "..." metadata
+        char hgTrackUiUrl[4096];
+        safef(hgTrackUiUrl, sizeof(hgTrackUiUrl), "%s", trackUrl(track->track, NULL));
+        if (isHubTrack(track->track))
+            {
+            char *hubUrl = hashFindVal(hubIdsToUrls, hubId);
+            if (hubUrl != NULL)
+                safefcat(hgTrackUiUrl, sizeof(hgTrackUiUrl), "&hubUrl=%s", hubUrl);
+            }
         hPrintf("<td><a target='_top' id='%s_dispFndTrk' "
                 "href='%s' title='Display track details'>%s</a></td>\n",
-                track->track, trackUrl(track->track, NULL), track->shortLabel);
+                track->track, hgTrackUiUrl, track->shortLabel);
         safef(id, sizeof id, "%s_dispFndTrk", track->track);
         jsOnEventByIdF("click", id, "popUp.hgTrackUi('%s',true); return false;", track->track);
         hPrintf("<td>%s", track->longLabel);
         compositeMetadataToggle(database, track->tdb, NULL, TRUE, FALSE);
         hPrintf("</td></tr>\n");
         }
     //hPrintf("</table>\n");
 
     // Closing view in browser button and foundTracks count
     hPrintf("<tr><td nowrap colspan=3>");
     hPrintf("<INPUT TYPE=SUBMIT NAME='submit' VALUE='Return to Browser' class='viewBtn' "
             "style='font-size:.8em;'>");
     hPrintf("&nbsp;&nbsp;&nbsp;&nbsp;<span class='selCbCount'></span>");
     if (tracksFound >= ENOUGH_FOUND_TRACKS)
         {
@@ -1136,30 +1196,32 @@
 hPrintf("</div>\n");
 
 hPrintf("</div>\n");
 
 hPrintf("</form>\n");
 hPrintf("</div>"); // Restricts to max-width:1000px;
 cgiDown(0.8);
 
 if (measureTiming)
     measureTime("Rendered tabs");
 
 if (doSearch)
     {
     // Now search
     long startTime = clock1000();
+    if (hubIdsToUrls == NULL)
+        hubIdsToUrls = hashNew(0);
     if (selectedTab==simpleTab && !isEmpty(simpleEntry))
         simpleSearchForTracks(simpleEntry);
     else if (selectedTab==advancedTab)
         advancedSearchForTracks(conn,groupList,nameSearch,typeSearch,descSearch,
                                          groupSearch,mdbSelects, includeHubResults);
 
     if (measureTiming)
         fprintf(stdout, "<span class='timing'>Searched for tracks: %lu<br></span>", clock1000()-startTime);
 
     // Sort and Print results
     if (selectedTab!=filesTab)
         {
         enum sortBy sortBy = cartUsualInt(cart,TRACK_SEARCH_SORT,sbRelevance);
         tracksFound = slCount(tracks);
         if (tracksFound > 1)