Skip to content

Commit

Permalink
Parse comma seperated alterations and add info icon to add mutation m…
Browse files Browse the repository at this point in the history
…odal input (#422)
  • Loading branch information
calvinlu3 authored Aug 16, 2024
1 parent 5fdf940 commit 9c3068e
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 14 deletions.
11 changes: 11 additions & 0 deletions src/main/webapp/app/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -429,3 +429,14 @@ a {
.toastify-toast {
display: inline-flex;
}

.scrollbar-wrapper {
overflow: auto;
visibility: hidden;
}

.scrollbar-content,
.scrollbar-wrapper:hover,
.scrollbar-wrapper:focus {
visibility: visible;
}
4 changes: 2 additions & 2 deletions src/main/webapp/app/components/sidebar/NavigationSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,8 @@ export const NavigationSidebar: React.FunctionComponent<StoreProps> = ({ isNavSi
</div>
<MenuDivider />

<div style={{ flex: '1 1 0%', overflowY: 'auto', minHeight: '200px' }}>
<Menu>
<div style={{ flex: '1 1 0%', minHeight: '200px' }} className={'scrollbar-wrapper'}>
<Menu className="scrollbar-content">
{props.isCurator && (
<MenuItemCollapsible
isCollapsed={isNavSidebarCollapsed}
Expand Down
45 changes: 38 additions & 7 deletions src/main/webapp/app/shared/modal/AddMutationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Tabs from 'app/components/tabs/tabs';
import { notifyError } from 'app/oncokb-commons/components/util/NotificationUtils';
import { IRootStore } from 'app/stores';
import { onValue, ref } from 'firebase/database';
import _, { isNil, result } from 'lodash';
import _ from 'lodash';
import { flow, flowResult } from 'mobx';
import React, { KeyboardEventHandler, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { FaChevronDown, FaChevronUp, FaExclamationTriangle, FaPlus } from 'react-icons/fa';
Expand All @@ -18,7 +18,7 @@ import {
Alteration as ApiAlteration,
} from '../api/generated/curation';
import { IGene } from '../model/gene.model';
import { getDuplicateMutations, getFirebaseGenePath, getFirebaseVusPath } from '../util/firebase/firebase-utils';
import { getDuplicateMutations, getFirebaseVusPath } from '../util/firebase/firebase-utils';
import { componentInject } from '../util/typed-inject';
import { hasValue, isEqualIgnoreCase, parseAlterationName } from '../util/utils';
import { DefaultAddMutationModal } from './DefaultAddMutationModal';
Expand All @@ -27,6 +27,8 @@ import classNames from 'classnames';
import { READABLE_ALTERATION, REFERENCE_GENOME } from 'app/config/constants/constants';
import { Unsubscribe } from 'firebase/database';
import Select from 'react-select/dist/declarations/src/Select';
import DefaultTooltip from '../tooltip/DefaultTooltip';
import InfoIcon from '../icons/InfoIcon';

type AlterationData = {
type: AlterationTypeEnum;
Expand Down Expand Up @@ -1010,11 +1012,16 @@ function AddMutationModal({
/>
</Col>
{!convertOptions?.isConverting ? (
<Col className="col-auto ps-2">
<Button color="primary" disabled={!inputValue} onClick={handleAlterationAdded}>
Add
</Button>
</Col>
<>
<Col className="col-auto ps-2">
<div>
<Button color="primary" disabled={!inputValue} onClick={handleAlterationAdded}>
Add
</Button>
<InfoIcon className="ms-2" overlay={<AddMutationInputOverlay />}></InfoIcon>
</div>
</Col>
</>
) : undefined}
</Row>
{tabStates.length > 0 && (
Expand Down Expand Up @@ -1159,6 +1166,30 @@ function AddMutationModalDropdown({ label, value, options, menuPlacement, onChan
);
}

const AddMutationInputOverlay = () => {
return (
<div>
<div>
Enter alteration(s) in input area, then press <span style={{ fontStyle: 'italic', fontWeight: 'bold' }}>Enter key</span> or click on{' '}
<span style={{ fontStyle: 'italic', fontWeight: 'bold' }}>Add button</span> to annotate alteration(s).
</div>
<div className="mt-2">
<div>Examples:</div>
<div>
<ul style={{ marginBottom: 0 }}>
<li>
Variant alleles seperated by slash - <span className="text-primary">R132C/H/G/S/L</span>
</li>
<li>
Comma seperated list of alterations - <span className="text-primary">V600E, V600K</span>
</li>
</ul>
</div>
</div>
</div>
);
};

const mapStoreToProps = ({
alterationStore,
consequenceStore,
Expand Down
4 changes: 4 additions & 0 deletions src/main/webapp/app/shared/util/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ describe('Utils', () => {
expect(JSON.stringify(results)).toEqual(JSON.stringify(expectedOutput));
});

it('should expand alterations that are comma seperated', () => {
expect(expandAlterationName('V600E, V600G, V600F', true)).toEqual(['V600E', 'V600G', 'V600F']);
});

it('should return original alteration when incorrectly formatted', () => {
const incorrectlyFormattedNames = ['VE600 G', 'V600GF', 'V600E/G/FL'];
const expectedOutput = [['VE600 G'], ['V600GF'], ['V600E/G/FL']];
Expand Down
17 changes: 12 additions & 5 deletions src/main/webapp/app/shared/util/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -214,12 +214,16 @@ export async function isPromiseOk(promise: Promise<any>) {
}
}

// splits alteration name separated by / into multiple alterations
export function expandAlterationName(name: string) {
// splits alteration name separated by '/' or ',' into multiple alterations

export function expandAlterationName(name: string, splitStringMutations = false) {
const regex = new RegExp('^([A-Z])\\s*([0-9]+)\\s*([A-Z])\\s*((?:/\\s*[A-Z]\\s*)*)$', 'i');
const parts = regex.exec(name);

if (!parts) {
if (splitStringMutations && name.includes(',')) {
return name.split(',').map(part => part.trim());
}
return [name];
}

Expand All @@ -236,7 +240,10 @@ export function expandAlterationName(name: string) {

// splits alteration name into alteration, excluding and comment
// if alteration is separated by /, applies the same excluding and comment to separated alterations
export function parseAlterationName(alterationName: string): { alteration: string; excluding: string[]; comment: string; name: string }[] {
export function parseAlterationName(
alterationName: string,
splitStringMutation = false,
): { alteration: string; excluding: string[]; comment: string; name: string }[] {
let regex = new RegExp('\\[(.*)\\]', 'i');
const nameSection = regex.exec(alterationName);
let name = '';
Expand All @@ -257,7 +264,7 @@ export function parseAlterationName(alterationName: string): { alteration: strin
excludingSection[1] = excludingSection[1].replace(/excluding/i, '');
const excludedNames = excludingSection[1].split(';');
for (const ex of excludedNames) {
excluding.push(...expandAlterationName(ex.trim()));
excluding.push(...expandAlterationName(ex.trim(), splitStringMutation));
}
}

Expand Down Expand Up @@ -285,7 +292,7 @@ export function parseAlterationName(alterationName: string): { alteration: strin

const parsedAlteration = alterationNameWithoutVariantNameAndExcluding.replace('(' + comment + ')', '');

const alterationNames = expandAlterationName(parsedAlteration.trim());
const alterationNames = expandAlterationName(parsedAlteration.trim(), splitStringMutation);

return alterationNames.map(alteration => ({
alteration,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 9c3068e

Please sign in to comment.