Skip to content

[DT-4062] Show PI institution and profile links - #3930

Merged
otchet-broad merged 20 commits into
otchet-dt-4062-study-page-framefrom
otchet-dt-4062-study-pi-details
Sep 23, 2026
Merged

otchet-broad merged 20 commits into
otchet-dt-4062-study-page-framefrom
otchet-dt-4062-study-pi-details

Conversation

@otchet-broad

Copy link
Copy Markdown
Contributor

Addresses

https://broadworkbench.atlassian.net/browse/DT-4062

Summary

Adds the PI institution row and ORCID / LinkedIn / website icons to the overview.

The page's primary study comes from the Elasticsearch-backed search index, which does not carry
these fields, so they are read from the relational store through a new Study.getById.
DataSet.getStudyById now delegates to it instead of duplicating the request, while still typing
the payload as the data-submission form's editable Study shape — the two callers of one endpoint
stay honest about wanting different views of it.

That relational response is also the fallback for study metadata when a study has no datasets and
so has no search-index document, which is why the two changes sit together.

The profile links live inside the PI Name row, and StudyInfoTable drops rows whose value is
falsy, so that row's presence cannot hinge on piName alone: the index sometimes has no name for
a study whose profile links are populated. Only plain http(s) urls are linked.

GET api/dataset/study/{studyId} already exists on consent develop, so until #3051 deploys the
request still returns 200 — only the new fields are absent. The institution row is omitted and no
icons render. This is the one branch in the stack that degrades quietly rather than showing an
error state: the endpoint is not new, only the fields on it are.


Have you read Terra's Contributing Guide lately? If not, do that first.

  • Label PR with a Jira ticket number and include a link to the ticket
  • Label PR with a security risk modifier [no, low, medium, high]
  • PR describes scope of changes
  • Get a minimum of one thumbs worth of review, preferably two if enough team members are available
  • Get PO sign-off for all non-trivial UI or workflow changes
  • Verify all tests go green
  • Test this change deployed correctly and works on dev environment after deployment

otchet-broad and others added 7 commits September 9, 2026 20:48
… page

Adds the PI institution row and ORCID / LinkedIn / website icons to the
study overview.

The page's primary `study` object comes from the Elasticsearch-backed
search index, which does not carry these fields, so they are read from
the relational store through a new `Study.getById`. `DataSet.getStudyById`
now delegates to it rather than duplicating the request; it keeps typing
the payload as the data-submission form's editable `Study` shape, so the
two callers of one endpoint stay honest about wanting different views of
it.

The profile links live inside the PI Name row, and StudyInfoTable drops
rows whose value is falsy, so that row's presence cannot hinge on piName
alone - the index sometimes has no name for a study whose profile links
are populated. Links are rendered only for plain http(s) urls.

Third of the DT-4062 stack. **Pairs with consent #3051**
(otchet-dt-4061-study-pi-details), which adds the pi_institution_id,
pi_orcid, pi_linkedin_url and pi_website_url columns and returns them
from GET /api/dataset/study/{studyId}. Until that deploys the request
succeeds but the fields come back undefined, so the institution row is
omitted and no icons render - the page degrades rather than breaking.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…udy-pi-details

# Conflicts:
#	src/components/study_details/StudyDetails.tsx
#	src/hooks/useStudyDetailsData.ts
#	test/components/study_details/StudyDetails.spec.tsx
@github-actions

github-actions Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Coverage Report for DUOS Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 90.49% 12312 / 13606
🔵 Statements 89.97% 13164 / 14631
🔵 Functions 87.57% 3671 / 4192
🔵 Branches 82.94% 7690 / 9272
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/components/ScrollToTopOnNavigate.tsx 100% 100% 100% 100%
src/components/study_details/FrequentlyRequestedWithSection.tsx 100% 100% 100% 100%
src/components/study_details/PiExternalProfileIcons.tsx 100% 100% 100% 100%
src/components/study_details/SimilarStudiesSection.tsx 100% 100% 100% 100%
src/components/study_details/StudyAssetCountBadges.tsx 100% 100% 100% 100%
src/components/study_details/StudyAssetTable.tsx 100% 100% 100% 100%
src/components/study_details/StudyCommentsSection.tsx 100% 86.79% 100% 100%
src/components/study_details/StudyDarHistory.tsx 100% 80% 100% 100%
src/components/study_details/StudyDetails.tsx 98.46% 100% 88.88% 98.41% 351
src/components/study_details/StudyPublicationCards.tsx 100% 93.33% 100% 100%
src/components/study_details/StudyQueryResult.tsx 100% 100% 100% 100%
src/components/study_details/StudyRecommendationCarousel.tsx 100% 100% 100% 100%
src/components/study_details/StudySecondaryResearchOutputs.tsx 100% 100% 100% 100%
src/components/study_details/piProfileLinks.ts 95.23% 100% 100% 95% 55
src/hooks/useStudyDetailsData.ts 100% 100% 100% 100%
src/libs/ajax/DataSet.ts 100% 100% 100% 100%
src/libs/ajax/DatasetMetrics.ts 100% 100% 100% 100%
src/libs/ajax/Study.ts 70% 100% 50% 66.66% 45-49, 62-68
src/libs/ajax/StudyComments.ts 100% 100% 100% 100%
src/libs/ajax/StudyRecommendations.ts 100% 100% 100% 100%
src/pages/DatasetStatistics.tsx 82.97% 81.81% 93.33% 87.5% 76-86, 113, 117, 163, 167, 171-173, 177, 191-192
src/types/model.ts 100% 100% 100% 100%
src/utils/ErrorUtils.ts 92.3% 94.44% 100% 92.3% 9
Generated in workflow #7317 for commit 9cc7ac2 by the Vitest Coverage Report Action

@otchet-broad
otchet-broad marked this pull request as ready for review September 15, 2026 01:31
@otchet-broad
otchet-broad requested a review from a team as a code owner September 15, 2026 01:31
@otchet-broad
otchet-broad requested review from fboulnois and kevinmarete and a lite review from Copilot and removed request for a team September 15, 2026 01:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Normalize ORCID values before URL detection to handle whitespace and blank values correctly.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds PI institution details and ORCID, LinkedIn, and website links to study overview pages, with relational metadata fallback.

Changes:

  • Adds shared relational study retrieval and metadata fallback.
  • Displays PI institution and validated external profile links.
  • Expands study overview tests.
File summaries
File Summary
test/components/study_details/StudyDetails.spec.tsx Tests PI metadata and profile-link rendering.
src/types/model.ts Adds PI metadata fields.
src/libs/ajax/Study.ts Adds study lookup API support.
src/libs/ajax/DataSet.ts Delegates study retrieval to the shared API.
src/hooks/useStudyDetailsData.ts Fetches relational study details.
src/components/study_details/StudyDetails.tsx Displays institution and profile icons.
src/components/study_details/piProfileLinks.ts Builds and validates profile URLs. Moderate finding: normalize ORCID values before URL detection (3 votes).
src/components/study_details/PiExternalProfileIcons.tsx Renders accessible external links.
Review details
  • Files reviewed: 8/8 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/components/study_details/piProfileLinks.ts Outdated
otchet-broad and others added 6 commits September 14, 2026 22:18
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
orcidHref decided whether the submitted value was an identifier or a
complete URL by testing the untrimmed string. A padded URL therefore
failed startsWith and was pasted onto the orcid.org base - a link to a
nonsense path - and a whitespace-only value became a link to the ORCID
home page once validateHttpUrl trimmed the base it had been appended to.

Trimming first settles both: a blank value is omitted, and a complete URL
is recognized whatever surrounds it.

Adds a spec for the module. Three of its cases fail against the previous
behaviour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both the Autofix commit and the local change rewrote orcidHref to
normalize before classifying the value. Kept the Autofix logic: it also
strips leading slashes, so "/0000-0002-..." does not yield a doubled path
under the orcid.org base, which the local version missed. Carried the
local comment over, and added the leading-slash case to the spec.

@kevinmarete kevinmarete left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏾

@fboulnois fboulnois left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor findings identified with Claude and Codex:

1. Profile link validation checks the scheme but never the host (security):

src/components/study_details/piProfileLinks.ts:39: validateHttpUrl only verifies the URL scheme. Any submitter-supplied https:// URL in piOrcid or piLinkedinUrl is rendered on the public study page under the "ORCID profile" / "LinkedIn profile" label and the iD icon.

This is a link-spoofing surface on a page visible to unauthenticated visitors. Suggested fix: constrain the host to orcid.org / linkedin.com and demote anything else to the generic website kind.

2. ORCID scheme check is case-sensitive

src/components/study_details/piProfileLinks.ts:28: normalized.startsWith('http') misclassifies HTTPS://orcid.org/0000-... as a bare identifier, producing https://orcid.org/HTTPS://orcid.org/0000-....

3. Relational fallbacks use ??, so empty index values win

src/components/study_details/StudyDetails.tsx:64: ?? does not fire on '' or [], so a stale or empty index document suppresses exactly the relational piName / dataTypes the fallback exists to supply.

otchet-broad and others added 6 commits September 15, 2026 19:25
Three findings from review, all on a page an unauthenticated visitor
can read:

validateHttpUrl checks only the scheme, so any submitter-supplied https
URL in piOrcid or piLinkedinUrl rendered under the "ORCID profile" or
"LinkedIn profile" label and the iD icon - a link-spoofing surface where
the label is the thing being trusted. The host now has to back the claim;
anything else is shown as a plain website rather than dropped, so the
submitter's link is not silently lost. Subdomains count, since LinkedIn
runs country sites, but the boundary dot is required or notlinkedin.com
would pass a bare suffix test. The allowlist lives here rather than in
validateHttpUrl, which is shared with unrelated callers.

The ORCID scheme test was case-sensitive, so HTTPS://orcid.org/0000-...
read as a bare identifier and became orcid.org/HTTPS://orcid.org/0000-...

The relational fallbacks used ??, which does not fire on '' or [], so an
index document present but empty for a field suppressed exactly the
relational value the fallback exists to supply. dataTypes spells out the
length check, since an empty array is truthy and || alone would not help.

Icons key by href now: a demoted link takes the generic website label, so
two of them can share one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codex review caught that keying the icons by href only moved the
collision: a submitter who puts one URL in two fields, or two links both
demoted to the generic website label, still produce duplicate React keys.
Each field yields at most one link, so the field it came from is the one
value that is unique in every case. It is kept separate from `kind`,
which says how the link is presented and repeats after a demotion.

The index fallbacks go through a `populated` helper rather than `||`.
Same semantics, one length check covering '' and [] instead of a bare ||
plus a special-cased ternary, and it keeps `??` - which `||` on a
nullable string would have put in front of Sonar's S6606.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review reported that dataTypes can arrive null and blank the page. The
`populated` helper added in 39510ce made that worse rather than better:
`??` at least falls through on null, while reading .length off it throws,
so a single null field crashed the render instead of needing both.

`== null` covers null and undefined together and has to be tested first.
The helper now wraps both sides of the fallback as well, so the result is
undefined rather than null when neither is populated - StudyTitleBadges'
`dataTypes = []` default only fires on undefined, which is the original
blank-page path the reviewer described.

The test casts through never deliberately: the declared types forbid
null, and that is the point - they assert a shape for external data
rather than guarantee it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@kevinmarete kevinmarete left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏾

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@otchet-broad
otchet-broad merged commit a6e20b2 into otchet-dt-4062-study-page-frame Sep 23, 2026
12 checks passed
@otchet-broad
otchet-broad deleted the otchet-dt-4062-study-pi-details branch September 23, 2026 15:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants