3e968a25a2e9635f916fb3eeac0b3e2147cfba7c
angie
  Mon Jun 28 08:43:35 2021 -0700
Add links to outbreak.info for lineages in results table & details.

diff --git src/hg/hgPhyloPlace/phyloPlace.c src/hg/hgPhyloPlace/phyloPlace.c
index 17fd160..101496c 100644
--- src/hg/hgPhyloPlace/phyloPlace.c
+++ src/hg/hgPhyloPlace/phyloPlace.c
@@ -831,39 +831,52 @@
  * and fill in those two fields of placementInfo. */
 {
 if (!bigTree)
     return;
 struct hashCookie cookie = hashFirst(samplePlacements);
 struct hashEl *hel;
 while ((hel = hashNext(&cookie)) != NULL)
     {
     struct placementInfo *info = hel->val;
     info->nearestNeighbor = findNearestNeighbor(bigTree, info->sampleId, info->variantPath);
     if (isNotEmpty(info->nearestNeighbor))
         info->neighborLineage = lineageForSample(sampleMetadata, info->nearestNeighbor);
     }
 }
 
+static void printLineageUrl(char *lineage)
+/* If lineage is not empty/NULL, print ": lineage <lineage>" and link to outbreak.info
+ * (unless lineage is "None") */
+{
+if (isNotEmpty(lineage))
+    {
+    if (differentString(lineage, "None"))
+        printf(": lineage <a href='"OUTBREAK_INFO_URLBASE"%s' target=_blank>%s</a>",
+               lineage, lineage);
+    else
+        printf(": lineage %s", lineage);
+    }
+}
+
 static void displayNearestNeighbors(struct placementInfo *info, char *source)
 /* Use info->variantPaths to find sample's nearest neighbor(s) in tree. */
 {
 if (isNotEmpty(info->nearestNeighbor))
     {
     printf("<p>Nearest neighboring %s sequence already in phylogenetic tree: %s",
            source, info->nearestNeighbor);
-    if (isNotEmpty(info->neighborLineage))
-        printf(": lineage %s", info->neighborLineage);
+    printLineageUrl(info->neighborLineage);
     puts("</p>");
     }
 }
 
 static void displayBestNodes(struct placementInfo *info, struct hash *sampleMetadata)
 /* Show the node(s) most closely related to sample. */
 {
 if (info->bestNodeCount == 1)
     printf("<p>The placement in the tree is unambiguous; "
            "there are no other parsimony-optimal placements in the phylogenetic tree.</p>\n");
 else if (info->bestNodeCount > 1)
     printf("<p>This placement is not the only parsimony-optimal placement in the tree; "
            "%d other placements exist.</p>\n", info->bestNodeCount - 1);
 if (showBestNodePaths && info->bestNodes)
     {
@@ -871,32 +884,31 @@
         errAbort("Inconsistent bestNodeCount (%d) and number of bestNodes (%d)",
                  info->bestNodeCount, slCount(info->bestNodes));
     if (info->bestNodeCount > 1)
         printf("<ul><li><b>used for placement</b>: ");
     if (differentString(info->bestNodes->name, "?") && !isAllDigits(info->bestNodes->name))
         printf("%s ", info->bestNodes->name);
     printVariantPathNoNodeNames(stdout, info->bestNodes->variantPath);
     struct bestNodeInfo *bn;
     for (bn = info->bestNodes->next;  bn != NULL;  bn = bn->next)
         {
         printf("\n<li>");
         if (differentString(bn->name, "?") && !isAllDigits(bn->name))
             printf("%s ", bn->name);
         printVariantPathNoNodeNames(stdout, bn->variantPath);
         char *lineage = lineageForSample(sampleMetadata, bn->name);
-        if (isNotEmpty(lineage))
-            printf(": lineage %s", lineage);
+        printLineageUrl(lineage);
         }
     if (info->bestNodeCount > 1)
         puts("</ul>");
     puts("</p>");
     }
 }
 
 static int placementInfoRefCmpSampleMuts(const void *va, const void *vb)
 /* Compare slRef->placementInfo->sampleMuts lists.  Shorter lists first.  Using alpha sort
  * to distinguish between different sampleMuts contents arbitrarily; the purpose is to
  * clump samples with identical lists. */
 {
 struct slRef * const *rra = va;
 struct slRef * const *rrb = vb;
 struct placementInfo *pa = (*rra)->val;
@@ -1472,30 +1484,41 @@
 printf(TOOLTIP("%s"), text);
 }
 
 static void appendExcludingNs(struct dyString *dy, struct seqInfo *si)
 /* Append a note to dy about how many N bases and start and/or end are excluded from statistic. */
 {
 dyStringAppend(dy, "excluding ");
 if (si->nCountStart)
     dyStringPrintf(dy, "%d N bases at start", si->nCountStart);
 if (si->nCountStart && si->nCountEnd)
     dyStringAppend(dy, " and ");
 if (si->nCountEnd)
     dyStringPrintf(dy, "%d N bases at end", si->nCountEnd);
 }
 
+static void printLineageTd(char *lineage, char *alt)
+/* Print a table cell with lineage (& link to outbreak.info if not 'None') or alt if no lineage. */
+{
+if (lineage && differentString(lineage, "None"))
+    printf("<td><a href='"OUTBREAK_INFO_URLBASE"%s' target=_blank>%s</a></td>", lineage, lineage);
+else if (lineage)
+    printf("<td>%s</td>", lineage);
+else
+    printf("<td>%s</td>", alt);
+}
+
 static void summarizeSequences(struct seqInfo *seqInfoList, boolean isFasta,
                                struct usherResults *ur, struct tempName *jsonTns[],
                                struct hash *sampleMetadata, struct dnaSeq *refGenome)
 /* Show a table with composition & alignment stats for each sequence that passed basic QC. */
 {
 if (seqInfoList)
     {
     puts("<table class='seqSummary'>");
     boolean gotClades = FALSE, gotLineages = FALSE;
     lookForCladesAndLineages(seqInfoList, ur->samplePlacements, &gotClades, &gotLineages);
     printSummaryHeader(isFasta, gotClades, gotLineages);
     puts("<tbody>");
     struct dyString *dy = dyStringNew(0);
     struct seqInfo *si;
     for (si = seqInfoList;  si != NULL;  si = si->next)
@@ -1614,34 +1637,34 @@
                     {
                     replaceChar(reason->name, '_', ' ');
                     dyStringPrintf(dy, ", %s", reason->name);
                     }
                 dyStringAppendC(dy, ')');
                 }
             printTooltip(dy->string);
             }
         printf("</td>");
         struct placementInfo *pi = hashFindVal(ur->samplePlacements, si->seq->name);
         if (pi)
             {
             if (gotClades)
                 printf("<td>%s</td>", pi->nextClade ? pi->nextClade : "n/a");
             if (gotLineages)
-                printf("<td>%s</td>", pi->pangoLineage ? pi->pangoLineage : "n/a");
-            printf("<td>%s</td><td>%s</td>",
-                   pi->nearestNeighbor ? replaceChars(pi->nearestNeighbor, "|", " | ") : "?",
-                   pi->neighborLineage ? pi->neighborLineage : "?");
+                printLineageTd(pi->pangoLineage, "n/a");
+            printf("<td>%s</td>",
+                   pi->nearestNeighbor ? replaceChars(pi->nearestNeighbor, "|", " | ") : "?");
+            printLineageTd(pi->neighborLineage, "?");
             int imputedCount = slCount(pi->imputedBases);
             printf("<td class='%s'>%d",
                    qcClassForImputedBases(imputedCount), imputedCount);
             if (imputedCount > 0)
                 {
                 dyStringClear(dy);
                 struct baseVal *bv;
                 for (bv = pi->imputedBases;  bv != NULL;  bv = bv->next)
                     {
                     dyStringAppendSep(dy, ", ");
                     dyStringPrintf(dy, "%d: %s", bv->chromStart+1, bv->val);
                     }
                 printTooltip(dy->string);
                 }
             printf("</td><td class='%s'>%d",