diff --git a/end-to-end-test/remote/screenshots/reference/filter_study_from_url_element_chrome_1600x1000.png b/end-to-end-test/remote/screenshots/reference/filter_study_from_url_element_chrome_1600x1000.png index 3a31239fa85..d986e8de93d 100644 Binary files a/end-to-end-test/remote/screenshots/reference/filter_study_from_url_element_chrome_1600x1000.png and b/end-to-end-test/remote/screenshots/reference/filter_study_from_url_element_chrome_1600x1000.png differ diff --git a/end-to-end-test/remote/screenshots/reference/fusion_filter_filter_study_from_url_element_chrome_1600x1000.png b/end-to-end-test/remote/screenshots/reference/fusion_filter_filter_study_from_url_element_chrome_1600x1000.png index 1777f24f270..6bbdfa4bb9f 100644 Binary files a/end-to-end-test/remote/screenshots/reference/fusion_filter_filter_study_from_url_element_chrome_1600x1000.png and b/end-to-end-test/remote/screenshots/reference/fusion_filter_filter_study_from_url_element_chrome_1600x1000.png differ diff --git a/src/pages/studyView/StudyViewPageStore.ts b/src/pages/studyView/StudyViewPageStore.ts index 2601dae5343..1f662ecb219 100644 --- a/src/pages/studyView/StudyViewPageStore.ts +++ b/src/pages/studyView/StudyViewPageStore.ts @@ -1412,7 +1412,7 @@ export class StudyViewPageStore { } public getGeneFiltersByUniqueKey(uniqueKey: string) { - return _.flatMapDeep(toJS(this._geneFilterSet.get(uniqueKey)) || []); + return toJS(this._geneFilterSet.get(uniqueKey)) || []; } public getClinicalDataFiltersByUniqueKey(uniqueKey: string) { diff --git a/src/pages/studyView/TableUtils.tsx b/src/pages/studyView/TableUtils.tsx index 0acc99dd8c5..cb4f6e29d8b 100644 --- a/src/pages/studyView/TableUtils.tsx +++ b/src/pages/studyView/TableUtils.tsx @@ -8,7 +8,6 @@ import {getFrequencyStr, getCNAByAlteration} from "pages/studyView/StudyViewUtil import {GenePanelList} from "pages/studyView/table/StudyViewGenePanelModal"; import {CSSProperties} from "react"; import * as _ from "lodash"; -import {GeneTableUserSelectionWithIndex} from "pages/studyView/table/GeneTable"; export type AlteredGenesTableUserSelectionWithIndex = { entrezGeneId: number; @@ -88,17 +87,3 @@ export function getFreqColumnRender(type: FreqColumnType, numberOfProfiledCases: ); } - -export function rowIsChecked(uniqueKey: string, preSelectedRows: GeneTableUserSelectionWithIndex[], selectedRows: GeneTableUserSelectionWithIndex[]) { - return _.some( - preSelectedRows.concat(selectedRows), - (row: GeneTableUserSelectionWithIndex) => row.uniqueKey === uniqueKey - ); -}; - -export function rowIsDisabled(uniqueKey: string, selectedRows: GeneTableUserSelectionWithIndex[]) { - return _.some( - selectedRows, - (row: GeneTableUserSelectionWithIndex) => row.uniqueKey === uniqueKey - ); -}; diff --git a/src/pages/studyView/table/FixedHeaderTable.tsx b/src/pages/studyView/table/FixedHeaderTable.tsx index 62ac3d5248d..73558a9df53 100644 --- a/src/pages/studyView/table/FixedHeaderTable.tsx +++ b/src/pages/studyView/table/FixedHeaderTable.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import {Column, LazyMobXTableStore, SortDirection} from "../../../shared/components/lazyMobXTable/LazyMobXTable"; +import {Column, LazyMobXTableStore, SortDirection, lazyMobXTableSort} from "../../../shared/components/lazyMobXTable/LazyMobXTable"; import { Column as RVColumn, SortDirection as RVSortDirection, @@ -17,9 +17,11 @@ import {If} from 'react-if'; import autobind from 'autobind-decorator'; import {inputBoxChangeTimeoutEvent} from "../../../shared/lib/EventUtils"; import {DefaultTooltip} from "cbioportal-frontend-commons"; +import { SimpleGetterLazyMobXTableApplicationDataStore } from "shared/lib/ILazyMobXTableApplicationDataStore"; export type IFixedHeaderTableProps = { columns: Column[], + fixedTopRowsData?: T[]; data: T[]; sortBy?: string; sortDirection?: SortDirection; @@ -38,6 +40,7 @@ export type IFixedHeaderTableProps = { removeAllDisabled?:boolean; showSelectableNumber?: boolean; isSelectedRow?: (data: T) => boolean; + highlightedRowClassName?: (data: T) => string; autoFocusSearchAfterRendering?:boolean; afterSorting?: (sortBy: string, sortDirection: SortDirection) => void; }; @@ -47,6 +50,24 @@ const RVSDTtoStrType = { ['asc' as SortDirection]: RVSortDirection.ASC }; +export class FixedHeaderTableDataStore extends SimpleGetterLazyMobXTableApplicationDataStore { + + constructor(getData: () => any[], fixedTopRowsData: any[]) { + super(getData); + this.fixedTopRowsData = fixedTopRowsData; + } + + private fixedTopRowsData: any[]; + + @computed get sortedData() { + // if not defined, use default values for sortMetric and sortAscending + const sortMetric = this.sortMetric || (() => 0); + const sortAscending = this.sortAscending !== undefined ? this.sortAscending : true; + + return [...this.fixedTopRowsData, ...lazyMobXTableSort(this.allData, sortMetric, sortAscending)]; + } +} + @observer export default class FixedHeaderTable extends React.Component, {}> { private _store: LazyMobXTableStore; @@ -78,32 +99,51 @@ export default class FixedHeaderTable extends React.Component) { this.updateDataStore(nextProps); } - updateDataStore(nextProps: any) { + updateDataStore(nextProps: IFixedHeaderTableProps) { + const tableDataStore = new FixedHeaderTableDataStore( + () => { + return this.props.data; + }, + this.props.fixedTopRowsData || [] + ); this._store.setProps({ columns: nextProps.columns, - data: nextProps.data, + dataStore: tableDataStore, initialSortColumn: this._sortBy, initialSortDirection: this._sortDirection }); } initDataStore() { + const tableDataStore = new FixedHeaderTableDataStore( + () => { + return this.props.data; + }, + this.props.fixedTopRowsData || [] + ); this._store = new LazyMobXTableStore({ columns: this.props.columns, - data: this.props.data, + dataStore: tableDataStore, initialSortColumn: this._sortBy, initialSortDirection: this._sortDirection }); } @autobind - rowClassName({index}: any) { + rowClassName({ index }: any) { if (index > -1 && this.isSelectedRow(this._store.dataStore.sortedFilteredData[index])) { - return classnames(styles.row, styles.highlightedRow); + const classNames: string[] = [styles.row] + if (this.props.highlightedRowClassName) { + const className = this.props.highlightedRowClassName(this._store.dataStore.sortedFilteredData[index]); + classNames.push(className); + } else { + classNames.push(styles.highlightedRow); + } + return classnames(classNames); } else if (index < 0) { return styles.headerRow; } else { diff --git a/src/pages/studyView/table/GeneTable.tsx b/src/pages/studyView/table/GeneTable.tsx index ad6a7fa6582..87e56223943 100644 --- a/src/pages/studyView/table/GeneTable.tsx +++ b/src/pages/studyView/table/GeneTable.tsx @@ -17,16 +17,13 @@ import { import { OncokbCancerGene } from "pages/studyView/StudyViewPageStore"; -import {getFreqColumnRender, getGeneColumnHeaderRender, rowIsChecked, rowIsDisabled} from "pages/studyView/TableUtils"; +import {getFreqColumnRender, getGeneColumnHeaderRender} from "pages/studyView/TableUtils"; import {GeneCell} from "pages/studyView/table/GeneCell"; import LabeledCheckbox from "shared/components/labeledCheckbox/LabeledCheckbox"; import styles from "pages/studyView/table/tables.module.scss"; import MobxPromise from "mobxpromise"; - -export type GeneTableUserSelectionWithIndex = { - uniqueKey: string; - rowIndex: number; -}; +import { stringListToIndexSet, stringListToSet } from "cbioportal-frontend-commons"; +import ifNotDefined from "shared/lib/ifNotDefined"; export type GeneTableRow = OncokbCancerGene & { entrezGeneId: number @@ -61,7 +58,7 @@ export type GeneTableProps = & { promise: MobxPromise; width: number; height: number; - filters: string[]; + filters: string[][]; onUserSelection: (value: string[]) => void; numOfSelectedSamples: number; onGeneSelect: (hugoGeneSymbol: string) => void; @@ -89,7 +86,7 @@ class GeneTableComponent extends FixedHeaderTable { @observer export class GeneTable extends React.Component { - @observable protected preSelectedRows: GeneTableUserSelectionWithIndex[] = []; + @observable protected selectedRowsKeys: string[] = []; @observable protected sortBy: GeneTableColumnKey; @observable private sortDirection: SortDirection; @observable private modalSettings: { @@ -367,12 +364,39 @@ export class GeneTable extends React.Component { return this.isFilteredByCancerGeneList ? _.filter(this.props.promise.result, data => data.isCancerGene) : (this.props.promise.result || []); } + @computed get flattenedFilters() { + return _.flatMap(this.props.filters); + } + + @computed get selectableTableData() { + if (this.flattenedFilters.length === 0) { + return this.tableData; + } + return _.filter(this.tableData, data => !this.flattenedFilters.includes(data.uniqueKey)); + } + + @computed + get preSelectedRows() { + if (this.flattenedFilters.length === 0) { + return []; + } + const order = stringListToIndexSet(this.flattenedFilters); + return _.chain(this.tableData) + .filter(data => this.flattenedFilters.includes(data.uniqueKey)) + .sortBy(data => ifNotDefined(order[data.uniqueKey], Number.POSITIVE_INFINITY)) + .value(); + } + + @computed + get preSelectedRowsKeys() { + return this.preSelectedRows.map(row => row.uniqueKey); + } + @computed get tableColumns() { return this.props.columns.map(column => this.getDefaultColumnDefinition(column.columnKey, this.columnsWidth[column.columnKey], this.cellMargin[column.columnKey])); } - @autobind @action toggleModal(panelName: string) { @@ -399,88 +423,58 @@ export class GeneTable extends React.Component { return !!this.props.cancerGeneFilterEnabled && this.props.filterByCancerGenes; } + @computed get allSelectedRowsKeysSet() { + return stringListToSet([...this.selectedRowsKeys, ...this.preSelectedRowsKeys]); + } + @autobind isChecked(uniqueKey: string) { - return rowIsChecked(uniqueKey, this.preSelectedRows, this.selectedRows); + return !!this.allSelectedRowsKeysSet[uniqueKey]; } @autobind isDisabled(uniqueKey: string) { - return rowIsDisabled(uniqueKey, this.selectedRows); + return _.some(this.preSelectedRowsKeys, (key) => key === uniqueKey); } @autobind togglePreSelectRow(uniqueKey: string) { - const record: GeneTableUserSelectionWithIndex | undefined = _.find( - this.preSelectedRows, - (row: GeneTableUserSelectionWithIndex) => row.uniqueKey === uniqueKey - ); + const record = _.find(this.selectedRowsKeys,(key) => key === uniqueKey); if (_.isUndefined(record)) { - let dataIndex = -1; - // definitely there is a match - const datum = _.find( - this.tableData, - (row, index: number) => { - const exist = row.uniqueKey! === uniqueKey; - if (exist) { - dataIndex = index; - } - return exist; - } - ); - - if (!_.isUndefined(datum)) { - this.preSelectedRows.push({ - rowIndex: dataIndex, - uniqueKey: datum.uniqueKey!, - }); - } + this.selectedRowsKeys.push(uniqueKey); } else { - this.preSelectedRows = _.xorBy(this.preSelectedRows, [record], "rowIndex"); + this.selectedRowsKeys = _.xorBy(this.selectedRowsKeys, [record]); } } @autobind @action afterSelectingRows() { - this.props.onUserSelection( - this.preSelectedRows.map(row => row.uniqueKey) - ); - this.preSelectedRows = []; + this.props.onUserSelection(this.selectedRowsKeys); + this.selectedRowsKeys = []; } - @computed - get selectedRows() { - if (this.props.filters.length === 0) { - return []; - } else { - return _.reduce( - this.tableData, - ( - acc: GeneTableUserSelectionWithIndex[], - row, - index: number - ) => { - if (_.includes(this.props.filters, row.uniqueKey)) { - acc.push({ - rowIndex: index, - uniqueKey: row.uniqueKey - }); - } - return acc; - }, - [] - ); - } + @autobind + isSelectedRow(data: GeneTableRow) { + return this.isChecked(data.uniqueKey); + } + + @computed get filterKeyToIndexSet() { + return _.reduce(this.props.filters, (acc, next, index) => { + next.forEach(key => { + acc[key] = index; + }); + return acc; + }, {} as { [id: string]: number }); } @autobind - isSelectedRow(data: GeneTableRow) { - return !_.isUndefined( - _.find(_.union(this.selectedRows, this.preSelectedRows), function (row) { - return row.uniqueKey === data.uniqueKey; - }) - ); + selectedRowClassName(data: GeneTableRow) { + const index = this.filterKeyToIndexSet[data.uniqueKey]; + if (index === undefined) { + return this.props.filters.length % 2 === 0 ? styles.highlightedEvenRow : styles.highlightedOddRow; + } + return index % 2 === 0 ? styles.highlightedEvenRow : styles.highlightedOddRow; } @autobind @@ -498,14 +492,16 @@ export class GeneTable extends React.Component { 0} + showSelectSamples={true && this.selectedRowsKeys.length > 0} isSelectedRow={this.isSelectedRow} afterSelectingRows={this.afterSelectingRows} sortBy={this.sortBy} sortDirection={this.sortDirection} afterSorting={this.afterSorting} + fixedTopRowsData={this.preSelectedRows} + highlightedRowClassName={this.selectedRowClassName} /> )} {this.props.genePanelCache ? ( diff --git a/src/pages/studyView/table/tables.module.scss b/src/pages/studyView/table/tables.module.scss index f72df1d0b6b..caea251ffa9 100644 --- a/src/pages/studyView/table/tables.module.scss +++ b/src/pages/studyView/table/tables.module.scss @@ -67,10 +67,14 @@ $study-view-table-icon-size: 10px; background-color: #ffffff; } -.highlightedRow { +.highlightedEvenRow, .highlightedRow { background-color: #d3d3d3; } +.highlightedOddRow { + background-color: #e0e0e0; +} + .cancerGeneIcon { margin-right: 5px; }