Skip to content

Commit

Permalink
Merge pull request #1259 from VEuPathDB/orthogroup-tree-table__1254-f…
Browse files Browse the repository at this point in the history
…ixes

Orthogroup tree table  1254 fixes
  • Loading branch information
bobular authored Nov 6, 2024
2 parents 07a8363 + 7ef7b0e commit d24c342
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 36 deletions.
34 changes: 27 additions & 7 deletions packages/libs/coreui/src/components/inputs/SelectList.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { ReactNode, useCallback, useEffect, useState } from 'react';
import PopoverButton from '../buttons/PopoverButton/PopoverButton';
import CheckboxList, { CheckboxListProps } from './checkboxes/CheckboxList';
import CheckboxList, {
CheckboxListProps,
Item,
} from './checkboxes/CheckboxList';

export interface SelectListProps<T> extends CheckboxListProps<T> {
export interface SelectListProps<T extends string>
extends CheckboxListProps<T> {
children?: ReactNode;
/** A button's content if/when no values are currently selected */
defaultButtonDisplayContent: ReactNode;
Expand All @@ -15,7 +19,7 @@ export interface SelectListProps<T> extends CheckboxListProps<T> {
instantUpdate?: boolean;
}

export default function SelectList<T>({
export default function SelectList<T extends string>({
name,
items,
value,
Expand All @@ -30,13 +34,13 @@ export default function SelectList<T>({
}: SelectListProps<T>) {
const [selected, setSelected] = useState<SelectListProps<T>['value']>(value);
const [buttonDisplayContent, setButtonDisplayContent] = useState<ReactNode>(
value.length ? value.join(', ') : defaultButtonDisplayContent
getDisplayContent(value, items, defaultButtonDisplayContent)
);

const onClose = () => {
onChange(selected);
setButtonDisplayContent(
selected.length ? selected.join(', ') : defaultButtonDisplayContent
getDisplayContent(selected, items, defaultButtonDisplayContent)
);
};

Expand All @@ -61,9 +65,9 @@ export default function SelectList<T>({
setSelected(value);
if (instantUpdate) return; // we don't want the button text changing on every click
setButtonDisplayContent(
value.length ? value.join(', ') : defaultButtonDisplayContent
getDisplayContent(value, items, defaultButtonDisplayContent)
);
}, [value, defaultButtonDisplayContent]);
}, [value, items, defaultButtonDisplayContent]);

const buttonLabel = (
<span
Expand Down Expand Up @@ -103,3 +107,19 @@ export default function SelectList<T>({
</PopoverButton>
);
}

// Returns button display content based on `value` array, mapping to display names from `items` when available.
// If no matching display name is found, uses the value itself. Returns `defaultContent` if `value` is empty.
function getDisplayContent<T extends string>(
value: T[],
items: Item<T>[],
defaultContent: ReactNode
): ReactNode {
return value.length
? value
.map<ReactNode>(
(v) => items.find((item) => item.value === v)?.display ?? v
)
.reduce((accum, elem) => (accum ? [accum, ',', elem] : elem), null)
: defaultContent;
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export type Item<T> = {
disabled?: boolean;
};

export type CheckboxListProps<T> = {
export type CheckboxListProps<T extends string> = {
/** Optional name attribute for the native input element */
name?: string;

Expand All @@ -99,7 +99,7 @@ export type CheckboxListProps<T> = {
disabledCheckboxTooltipContent?: ReactNode;
};

export default function CheckboxList<T>({
export default function CheckboxList<T extends string>({
name,
items,
value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import React, {
import TreeTable from '@veupathdb/components/lib/components/tidytree/TreeTable';
import { RecordTableProps, WrappedComponentProps } from './Types';
import { useOrthoService } from 'ortho-client/hooks/orthoService';
import { Loading } from '@veupathdb/wdk-client/lib/Components';
import { Loading, Link } from '@veupathdb/wdk-client/lib/Components';
import { Branch, parseNewick } from 'patristic';
import {
AttributeValue,
Expand Down Expand Up @@ -39,6 +39,10 @@ import {
import { RecordTable_TaxonCounts_Filter } from './RecordTable_TaxonCounts_Filter';
import { formatAttributeValue } from '@veupathdb/wdk-client/lib/Utils/ComponentUtils';
import { RecordFilter } from '@veupathdb/wdk-client/lib/Views/Records/RecordTable/RecordFilter';
import {
areTermsInStringRegexString,
parseSearchQueryString,
} from '@veupathdb/wdk-client/lib/Utils/SearchUtils';

type RowType = Record<string, AttributeValue>;

Expand Down Expand Up @@ -94,7 +98,11 @@ export function RecordTable_Sequences(
const numSequences = mesaRows.length;

const treeResponse = useOrthoService(
(orthoService) => orthoService.getGroupTree(groupName),
(orthoService) => {
if (numSequences < MIN_SEQUENCES_FOR_TREE)
return Promise.resolve(undefined);
return orthoService.getGroupTree(groupName);
},
[groupName, numSequences]
);

Expand Down Expand Up @@ -191,7 +199,7 @@ export function RecordTable_Sequences(
const { tree, leaves, sortedRows } = useMemo(() => {
const tree = treeResponse == null ? undefined : parseNewick(treeResponse);
const leaves = tree && getLeaves(tree);
const sortedRows = leaves && sortRows(leaves, mesaRows);
const sortedRows = leaves ? sortRows(leaves, mesaRows) : mesaRows;
return { tree, leaves, sortedRows };
}, [treeResponse, mesaRows]);

Expand Down Expand Up @@ -391,20 +399,16 @@ export function RecordTable_Sequences(

// None shall pass! (hooks, at least)

if (
!mesaState ||
!sortedRows ||
(numSequences >= MIN_SEQUENCES_FOR_TREE &&
numSequences <= MAX_SEQUENCES_FOR_TREE &&
(tree == null || treeResponse == null))
) {
if (!mesaState || !sortedRows || !tree || !treeResponse) {
return <Loading />;
}

if (
numSequences >= MIN_SEQUENCES_FOR_TREE &&
mesaRows != null &&
sortedRows != null &&
mesaRows.length !== sortedRows.length
(mesaRows.length !== sortedRows.length ||
mesaRows.length !== leaves?.length)
) {
console.log(
'Tree and protein list mismatch. A=Tree, B=Table. Summary below:'
Expand All @@ -419,8 +423,16 @@ export function RecordTable_Sequences(
<Banner
banner={{
type: 'warning',
message:
'Tree and protein list mismatch. Please contact the helpdesk',
message: (
<span>
A data processing error has occurred on our end. We apologize for
the inconvenience. If this problem persists, please{' '}
<Link target="_blank" to="/contact-us">
contact us
</Link>
.
</span>
),
}}
/>
);
Expand Down Expand Up @@ -514,6 +526,7 @@ export function RecordTable_Sequences(
onPress={() => {
proteinFilterButtonRef.current?.close();
setProteinFilterIds([]);
setTablePageNumber(1);
}}
/>
);
Expand All @@ -522,6 +535,7 @@ export function RecordTable_Sequences(
proteinFilterButtonRef.current?.close();
setProteinFilterIds(highlightedNodes);
setHighlightedNodes([]);
setTablePageNumber(1);
};

const proteinFilter = (
Expand Down Expand Up @@ -604,12 +618,13 @@ export function RecordTable_Sequences(
corePeripheralFilterValue.length +
selectedSpecies.length +
proteinFilterIds.length ===
0
0 && searchQuery === ''
}
icon={Undo}
size={'medium'}
themeRole={'primary'}
onPress={() => {
setSearchQuery('');
setProteinFilterIds([]);
setPfamFilterIds([]);
setCorePeripheralFilterValue([]);
Expand All @@ -622,6 +637,23 @@ export function RecordTable_Sequences(

if (filteredRows == null) return null;

const warningText =
numSequences >= MIN_SEQUENCES_FOR_TREE &&
(filteredRows.length > MAX_SEQUENCES_FOR_TREE ||
filteredRows.length < MIN_SEQUENCES_FOR_TREE) ? (
<span>
To see a phylogenetic tree please use a filter to display between{' '}
{MIN_SEQUENCES_FOR_TREE.toLocaleString()} and{' '}
{MAX_SEQUENCES_FOR_TREE.toLocaleString()} sequences
</span>
) : filteredRows.length < sortedRows.length ? (
<span>
Note: The ortholog group's phylogeny has been pruned to display only the
currently filtered proteins. This may differ from a tree constructed{' '}
<i>de novo</i> using only these sequences.
</span>
) : undefined;

return (
<div
style={
Expand All @@ -630,8 +662,7 @@ export function RecordTable_Sequences(
} as CSSProperties
}
>
{(filteredRows.length > MAX_SEQUENCES_FOR_TREE ||
filteredRows.length < MIN_SEQUENCES_FOR_TREE) && (
{warningText && (
<div
style={{
display: 'flex',
Expand All @@ -645,9 +676,7 @@ export function RecordTable_Sequences(
fontWeight: 500,
}}
>
To see a phylogenetic tree please use a filter to display between{' '}
{MIN_SEQUENCES_FOR_TREE.toLocaleString()} and{' '}
{MAX_SEQUENCES_FOR_TREE.toLocaleString()} sequences
{warningText}
</div>
)}
<div
Expand All @@ -662,6 +691,7 @@ export function RecordTable_Sequences(
}}
>
<RecordFilter
key={`text-search-${resetCounter}`}
searchTerm={searchQuery}
onSearchTermChange={setSearchQuery}
recordDisplayName="Proteins"
Expand Down Expand Up @@ -781,14 +811,9 @@ function rowMatch(row: RowType, query: RegExp, keys?: string[]): boolean {

function createSafeSearchRegExp(input: string): RegExp | undefined {
if (input === '') return undefined;
try {
// Attempt to create a RegExp from the user input directly
return new RegExp(input, 'i');
} catch (error) {
// If an error occurs (e.g., invalid RegExp), escape the input and create a literal search RegExp
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return new RegExp(escapedInput, 'i');
}
const queryTerms = parseSearchQueryString(input);
const searchTermRegex = areTermsInStringRegexString(queryTerms);
return new RegExp(searchTermRegex, 'i');
}

function logIdMismatches(A: string[], B: string[]) {
Expand Down

0 comments on commit d24c342

Please sign in to comment.