Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions static-site/components/list-resources/individual-resource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { SetModalDataContext } from "./modal";
import TooltipWrapper from "./tooltip-wrapper";

import { Resource } from "./types";
import { displayResourceType } from ".";

import styles from "./individual-resource.module.css";

Expand Down Expand Up @@ -70,17 +71,17 @@ export function IndividualResource({
return null;
}

const restricted = resource.restrictedDataWarning ? (
<TooltipWrapper description={resource.restrictedDataWarning}>
const restricted = resource.maybeRestricted ? (
<TooltipWrapper description={`Warning! This ${displayResourceType(resource.resourceType, 1)} may contain restricted data. Please refer to Restricted Data Terms of Use linked above.`}>
<IconContainer iconName="restricted" text={''}/>
</TooltipWrapper>
) : null;

// If an out of date warning exists then show it. Otherwise show cadence information if it's available
let history: React.JSX.Element | null = null;
if (resource.outOfDateWarning) {
if (resource.maybeOutOfDate) {
history = (
<TooltipWrapper description={resource.outOfDateWarning}>
<TooltipWrapper description={`Warning! This ${displayResourceType(resource.resourceType, 1)} may be over a year old. Last known update on ${resource.lastUpdated}`}>
<IconContainer iconName="out-of-date" text={''}/>
</TooltipWrapper>
);
Expand Down
26 changes: 12 additions & 14 deletions static-site/components/list-resources/listResourcesApi.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ResourceType, Resource, Group, PathVersionsForGroup, FetchGroupHistory } from "./types";
import { ResourceType, Resource, Group, PathVersionsForGroup, FetchGroupHistory, maybeRestrictedData } from "./types";
import { InternalError } from "../error-boundary";
import fetchAndParseJSON from "../../util/fetch-and-parse-json";

Expand Down Expand Up @@ -62,7 +62,7 @@ export async function listResourcesAPI(
}
const groups = await Promise.all(Object.entries(
areDatasets(data) ?
groupDatasetsByPathogen(data.pathVersions, urlBuilder, versioned, groupNameBuilder) :
groupDatasetsByPathogen(data.pathVersions, urlBuilder, versioned, groupNameBuilder, resourceType) :
groupIntermediatesByPathogen(data.latestVersions, groupNameBuilder)
).map(async ([groupName, resources]) => {
const group = resourceGroup(groupName, resources);
Expand Down Expand Up @@ -135,6 +135,9 @@ function groupDatasetsByPathogen(

/** constructs the name (e.g. pathogen) under which to group a dataset */
groupNameBuilder: (name: string) => string,

/** the type of resource */
resourceType: ResourceType,
): Record<string, Resource[]> {
return Object.entries(pathVersions).reduce(
(store: Record<string, Resource[]>, [name, dates]) => {
Expand All @@ -152,6 +155,7 @@ function groupDatasetsByPathogen(
nameParts,
sortingName: _sortableName(nameParts),
url: urlBuilder(name),
resourceType,
};

if (versioned) {
Expand All @@ -160,17 +164,15 @@ function groupDatasetsByPathogen(
throw new InternalError("Resource does not have any dates.");
}
const lastUpdated = sortedDates.at(-1) as string; // eslint-disable-line @typescript-eslint/consistent-type-assertions
const nDaysOld = _timeDelta(lastUpdated);
resourceDetails.lastUpdated = lastUpdated;
resourceDetails.firstUpdated = sortedDates[0];
resourceDetails.dates = sortedDates;
resourceDetails.nVersions = sortedDates.length;
resourceDetails.updateCadence = _updateCadence(
sortedDates.map((date) => new Date(date)),
);
const nDaysOld = _timeDelta(lastUpdated);
if (nDaysOld && nDaysOld>365) {
resourceDetails.outOfDateWarning = `Warning! This dataset may be over a year old. Last known update on ${lastUpdated}`;
}
resourceDetails.maybeOutOfDate = !!nDaysOld && nDaysOld>365;
}
(store[groupName] ??= []).push(resourceDetails);

Expand Down Expand Up @@ -198,22 +200,18 @@ function groupIntermediatesByPathogen(
if (filename==='mostRecentlyIndexed') continue;
const nameParts = [...baseParts, filename]
const [url, lastUpdated] = urlDatePair;
const nDaysOld = _timeDelta(lastUpdated);
const resourceDetails: Resource = {
name: nameParts.join('/'), // includes filename
groupName, // decoupled from nameParts
nameParts,
sortingName: _sortableName(nameParts),
url,
resourceType: 'intermediate',
lastUpdated,
maybeRestricted: maybeRestrictedData(filename),
maybeOutOfDate: !!nDaysOld && nDaysOld>365,
};
if (nameParts.at(-1)?.includes("restricted")) {
// "restricted" in filename
resourceDetails.restrictedDataWarning = "Warning! This file may contain restricted data. Please refer to Restricted Data Terms of Use linked above.";
}
const nDaysOld = _timeDelta(lastUpdated);
if (nDaysOld && nDaysOld>365) {
resourceDetails.outOfDateWarning = `Warning! This file may be over a year old. Last known update on ${lastUpdated}`;
}
(store[groupName] ??= []).push(resourceDetails);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"use client";

import React, { useEffect, useState} from "react";
import { FilterOption, Group, PathVersionsForGroup, GroupFilesChangelog } from "./types";
import { FilterOption, Group, PathVersionsForGroup, GroupFilesChangelog, maybeRestrictedData } from "./types";
import IconContainer from "./icon-container";
import TooltipWrapper from "./tooltip-wrapper";
import styles from "./modal.module.css";
import Spinner from "../spinner";

Expand Down Expand Up @@ -61,8 +63,9 @@ export function GroupHistory({
const filteredHistory = _filterHistory(history, filterWords);

const nDays = filteredHistory.length;
const allFiles = filteredHistory.flatMap((h) => Object.keys(h[1]));
const nFilesUnique = (new Set(allFiles)).size;
const allFiles = filteredHistory.flatMap((h) => h[1]);
const nFilesUnique = (new Set(allFiles.map((file) => file.name))).size;
const hasRestricted = allFiles.some((file) => file.maybeRestricted);

return (
<>
Expand All @@ -76,6 +79,17 @@ export function GroupHistory({
{` and thus there could be more recent versions of a particular file uploaded since ${filteredHistory.at(0)?.[0]};`}
{` please use the link on the background page to guarantee you're getting the latest version of a particular file.`}
{` Finally there may have been files or versions beyond those shown here which are no longer available.`}
{hasRestricted && (<>
<br/>
<br/>
{`Some of these files contain Restricted Data from Pathoplexus.`}
<br/>
{`To use these in your own analysis, please read `}
<a href="https://pathoplexus.org/about/terms-of-use/restricted-data" target="_blank" rel="noreferrer">
Pathoplexus Restricted Data Terms of Use
</a>
{`.`}
</>)}
</div>
{filterWords.length!==0 && (
<div className={styles.newLine}>
Expand All @@ -90,11 +104,16 @@ export function GroupHistory({
{date}
</div>
<ul>
{Object.entries(info).map(([filename, url]) => {
const displayFilename = filename.split('/').join(' / ');
{info.map((file) => {
const displayFilename = file.name.split('/').join(' / ');
return (
<li className={styles.list} key={filename}>
<a href={url}>{displayFilename}</a>
<li className={styles.list} key={file.name}>
<a href={file.url}>{displayFilename}</a>
{file.maybeRestricted && (
<TooltipWrapper description="Warning! This file may contain restricted data. Please refer to Restricted Data Terms of Use linked above.">
<IconContainer iconName="restricted" text={''}/>
</TooltipWrapper>
)}
</li>
)
})}
Expand All @@ -112,24 +131,24 @@ function _filterHistory(history: GroupFilesChangelog, filters: string[]): GroupF
// Use a cache to speed things up as the same filenames are repeated many times
const cache: Map<string, boolean> = new Map(); // true: passes filter, false: exclude
return history.flatMap(([date, files]) => { // flatMap allows empty array returns to disappear
const filePairs = Object.entries(files)
.filter(([filename, ]) => {
if (!cache.has(filename)) {
const filewords = filename.split('/');
const filtered = files
.filter((file) => {
if (!cache.has(file.name)) {
const filewords = file.name.split('/');
const passes = filters.map((w) => filewords.includes(w)).every((el) => el===true);
cache.set(filename, passes);
cache.set(file.name, passes);
}
return cache.get(filename);
return cache.get(file.name);
})
if (filePairs.length===0) return []; // no matches from this day
return [[date, Object.fromEntries(filePairs)]]
if (filtered.length===0) return []; // no matches from this day
return [[date, filtered]]
})
}

function _changelog(pathVersions: PathVersionsForGroup): GroupFilesChangelog {
const dates = _orderedDates(pathVersions);
const indexes = Object.fromEntries(dates.map((d, i) => [d, i]));
const history: GroupFilesChangelog = dates.map((d) => [d, {}]);
const history: GroupFilesChangelog = dates.map((d) => [d, []]);
for (const [id, infoByDay] of Object.entries(pathVersions)) {
for (const info of infoByDay) {
const date = info.date;
Expand All @@ -139,7 +158,11 @@ function _changelog(pathVersions: PathVersionsForGroup): GroupFilesChangelog {
if (dateIdx===undefined) continue;
const historyEl = history[dateIdx];
if (historyEl===undefined) continue;
historyEl[1][`${id}/${filename}`] = url;
historyEl[1].push({
name: `${id}/${filename}`,
url,
maybeRestricted: maybeRestrictedData(filename),
});
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions static-site/components/list-resources/modal.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@
}

.list {
display: flex;
align-items: center;
gap: 5px;
margin-left: 20px;
margin-bottom: 3px;
}
Expand Down
4 changes: 2 additions & 2 deletions static-site/components/list-resources/resource-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -377,10 +377,10 @@ function _getMaxResourceWidth(
(w: number, r: DisplayNamedResource): number => {
/* add the pixels for the display name */
let _w = r.displayName.default.length * namePxPerChar;
if (r.restrictedDataWarning) {
if (r.maybeRestricted) {
_w += 40; // icon + padding
}
if (r.outOfDateWarning) {
if (r.maybeOutOfDate) {
_w += 40; // icon + padding
} else if (r.nVersions && r.updateCadence) {
_w += gapWidth + iconWidth;
Expand Down
1 change: 1 addition & 0 deletions static-site/components/list-resources/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

.resourceTooltip {
font-size: 1.6rem;
z-index: 200; /* higher than .modalContainer's z-index (100) */
}

.sortContainer {
Expand Down
26 changes: 19 additions & 7 deletions static-site/components/list-resources/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,15 @@ export type PathVersionsForGroup = {
}[]
}

export interface ChangelogFile {
name: string;
url: string;
maybeRestricted: boolean;
}

export type GroupFilesChangelog = [
date: string,
{[nameInclFilename: string]: string}
ChangelogFile[]
][]


Expand All @@ -70,19 +76,25 @@ export interface Resource {

sortingName: string;
url: string;
resourceType?: ResourceType;
resourceType: ResourceType;
lastUpdated?: string; // date
firstUpdated?: string; // date
dates?: string[];
nVersions?: number;
updateCadence?: UpdateCadence;

/** Warning is set if the resource potentially uses restricted data. */
restrictedDataWarning?: string;
/** True if the resource potentially uses restricted data. */
maybeRestricted?: boolean;

/** If the resource is (potentially) out of date (according to the `lastUpdated` property)
* the `outOfDateWarning` may be set */
outOfDateWarning?: string;
/** True if the resource is potentially out of date (according to the `lastUpdated` property). */
maybeOutOfDate?: boolean;
}

/**
* Check if the filename indicates restricted data.
*/
export function maybeRestrictedData(filename: string): boolean {
return filename.includes("restricted");
}

export interface DisplayNamedResource extends Resource {
Expand Down