Skip to content

Commit

Permalink
improve search of authorized and unauthorized studies
Browse files Browse the repository at this point in the history
fix 9915 improve search of authorized studies
add new search filter based on readPermission flag called Controlled access authorized. Modified the search algorithm to accept booleans instead of just strings esp. false value

fix 9915 improve search of authorized studies

fix 9915 improve search of authorized studies
add new search filter based on readPermission flag called Controlled access authorized. Modified the search algorithm to accept booleans instead of just strings esp. false value

Use _.toString in matchPhraseFull, and specify type

Unnest fieldName check in Phrase.match

Introduce FilterFieldOption type that includes a displayValue

Update QueryParser.ts

Update CheckboxFilterField.tsx

id has to be unique for each filter when there are multiple filters.

Modify the Select all checkbox to select only authorized studies

Update checkbox label text

Update checkbox label text when filter is applied vs not applied

fix all Authorized or Unauthorized studies scenario

fix all Authorized or Unauthorized studies scenario. Hide the option if the studies that are all authorized or all unauthorized.

Updated logic and refactoring

Only CancerStudy objects from treeData are filtered based on whether the readPermission field has a value.  Refactoring: New const created as shownStudiesLengthstring to identify if there is a filter applied or not.

prettier
  • Loading branch information
jagnathan committed May 4, 2023
1 parent 184afdf commit 2f10d41
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 82 deletions.
107 changes: 68 additions & 39 deletions src/shared/components/query/CancerStudySelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ export default class CancerStudySelector extends React.Component<
onCheckAllFiltered: () => {
this.logic.mainView.toggleAllFiltered();
},
onCheckAuthorizedFiltered: () => {
this.logic.mainView.toggleAllAuthorizedAndFiltered();
},
onClearFilter: () => {
this.store.setSearchText('');
},
Expand Down Expand Up @@ -193,9 +196,16 @@ export default class CancerStudySelector extends React.Component<
shownAndSelectedStudies,
} = this.logic.mainView.getSelectionReport();

const shownAndAuthorizedStudies = shownStudies.filter(study => {
return study.readPermission;
});
const quickSetButtons = this.logic.mainView.quickSelectButtons(
getServerConfig().skin_quick_select_buttons
);
const shownStudiesLengthstring =
shownStudies.length < this.store.cancerStudies.result.length
? 'matching filter'
: '';

return (
<FlexCol
Expand Down Expand Up @@ -307,45 +317,64 @@ export default class CancerStudySelector extends React.Component<
</div>
</Then>
<Else>
<label>
<input
type="checkbox"
data-test="selectAllStudies"
style={{ top: -2 }}
onClick={
this.handlers
.onCheckAllFiltered
}
checked={
shownAndSelectedStudies.length ===
shownStudies.length
}
/>
<strong>
{shownAndSelectedStudies.length ===
shownStudies.length
? `Deselect all listed studies ${
shownStudies.length <
this.store
.cancerStudies
.result.length
? 'matching filter'
: ''
} (${
shownStudies.length
})`
: `Select all listed studies ${
shownStudies.length <
this.store
.cancerStudies
.result.length
? 'matching filter'
: ''
} (${
shownStudies.length
})`}
</strong>
</label>
<If
condition={
getServerConfig()
.skin_home_page_show_unauthorized_studies ===
false
}
>
<Then>
<label>
<input
type="checkbox"
data-test="selectAllStudies"
style={{ top: -2 }}
onClick={
this.handlers
.onCheckAllFiltered
}
checked={
shownAndSelectedStudies.length ===
shownStudies.length
}
/>
<strong>
{shownAndSelectedStudies.length ===
shownStudies.length
? `Deselect all listed studies ${shownStudiesLengthstring} (${shownStudies.length})`
: `Select all listed studies ${shownStudiesLengthstring} (${shownStudies.length})`}
</strong>
</label>
</Then>
<Else>
<label>
<input
type="checkbox"
data-test="selectAuthorizedStudies"
style={{ top: -2 }}
onClick={
this.handlers
.onCheckAuthorizedFiltered
}
checked={
shownAndSelectedStudies.length ===
shownAndAuthorizedStudies.length &&
shownAndAuthorizedStudies.length >
0
}
/>
<strong>
{shownAndSelectedStudies.length ===
shownAndAuthorizedStudies.length &&
shownAndAuthorizedStudies.length >
0
? `Deselect all authorized studies ${shownStudiesLengthstring} (${shownAndAuthorizedStudies.length})`
: `Select all authorized studies ${shownStudiesLengthstring} (${shownAndAuthorizedStudies.length})`}
</strong>
</label>
</Else>
</If>
</Else>
</If>
</If>
Expand Down
12 changes: 11 additions & 1 deletion src/shared/components/query/QueryStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export class QueryStore {

@computed
get queryParser() {
return new QueryParser(this.referenceGenomes);
return new QueryParser(this.referenceGenomes, this.readPermissions);
}

initialize(urlWithInitialParams?: string) {
Expand Down Expand Up @@ -1479,6 +1479,16 @@ export class QueryStore {
return new Set(referenceGenomes);
}

@computed get readPermissions(): Set<string> {
const studies = Array.from(this.treeData.map_node_meta.keys()).filter(
s => typeof (s as CancerStudy).readPermission !== 'undefined'
);
const readPermissions = studies.map(n =>
(n as CancerStudy).readPermission.toString()
);
return new Set(readPermissions);
}

@computed get selectableSelectedStudies() {
return this.selectableSelectedStudyIds
.map(
Expand Down
36 changes: 36 additions & 0 deletions src/shared/components/query/StudyListLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,42 @@ export class FilteredCancerTreeView {
);
}

@action toggleAllAuthorizedAndFiltered() {
const {
selectableSelectedStudyIds,
selectableSelectedStudies,
shownStudies,
shownAndSelectedStudies,
} = this.getSelectionReport();

let updatedSelectableSelectedStudyIds: string[] = [];
const shownAndAuthorizedStudies = shownStudies.filter(study => {
return study.readPermission;
});
if (
shownAndAuthorizedStudies.length === shownAndSelectedStudies.length
) {
// deselect
updatedSelectableSelectedStudyIds = _.without(
this.store.selectableSelectedStudyIds,
...shownAndAuthorizedStudies.map(
(study: CancerStudy) => study.studyId
)
);
} else {
updatedSelectableSelectedStudyIds = _.union(
this.store.selectableSelectedStudyIds,
shownAndAuthorizedStudies.map(
(study: CancerStudy) => study.studyId
)
);
}

this.store.selectableSelectedStudyIds = updatedSelectableSelectedStudyIds.filter(
id => !_.includes(this.store.deletedVirtualStudies, id)
);
}

@action selectAllMatchingStudies(match: string | string[]) {
const {
selectableSelectedStudyIds,
Expand Down
10 changes: 7 additions & 3 deletions src/shared/components/query/filteredSearch/Phrase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,12 @@ export class ListPhrase implements Phrase {
public match(study: FullTextSearchNode): boolean {
let anyFieldMatch = false;
for (const fieldName of this.fields) {
if (!_.has(study, fieldName)) {
continue;
}
let anyPhraseMatch = false;
const fieldValue = study[fieldName];
if (fieldValue) {
if (typeof fieldValue !== 'undefined') {
for (const phrase of this._phraseList) {
anyPhraseMatch =
anyPhraseMatch ||
Expand Down Expand Up @@ -167,7 +170,8 @@ function matchPhrase(phrase: string, fullText: string) {

/**
* Full match using lowercase
* Need to convert boolean to string before applying lowercase
*/
function matchPhraseFull(phrase: string, fullText: string) {
return fullText.toLowerCase() === phrase.toLowerCase();
function matchPhraseFull(phrase: string, toMatch: boolean | string | number) {
return _.toString(toMatch).toLowerCase() === phrase.toLowerCase();
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import {
} from 'shared/components/query/filteredSearch/field/CheckboxFilterField';
import { CancerTreeSearchFilter } from 'shared/lib/query/textQueryUtils';
import { ListPhrase } from 'shared/components/query/filteredSearch/Phrase';
import {
toFilterFieldOption,
toFilterFieldValue,
} from 'shared/components/query/filteredSearch/field/FilterFieldOption';

describe('CheckboxFilterField', () => {
describe('createQueryUpdate', () => {
Expand All @@ -12,7 +16,7 @@ describe('CheckboxFilterField', () => {
nodeFields: ['studyId'],
form: {
input: FilterCheckbox,
options: ['a', 'b', 'c', 'd', 'e'],
options: ['a', 'b', 'c', 'd', 'e'].map(toFilterFieldOption),
label: 'Test label',
},
} as CancerTreeSearchFilter;
Expand Down Expand Up @@ -48,7 +52,11 @@ describe('CheckboxFilterField', () => {
it('removes all update when only And', () => {
const checked = dummyFilter.form.options;
const toRemove: ListPhrase[] = [];
const result = createQueryUpdate(toRemove, checked, dummyFilter);
const result = createQueryUpdate(
toRemove,
checked.map(toFilterFieldValue),
dummyFilter
);
expect(result.toAdd?.length).toEqual(0);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@ import {
} from 'shared/lib/query/textQueryUtils';
import { FieldProps } from 'shared/components/query/filteredSearch/field/FilterFormField';
import { ListPhrase } from 'shared/components/query/filteredSearch/Phrase';
import {
FilterFieldOption,
toFilterFieldValue,
} from 'shared/components/query/filteredSearch/field/FilterFieldOption';

export type CheckboxFilterField = {
input: typeof FilterCheckbox;
label: string;
options: string[];
options: FilterFieldOption[];
};

export const FilterCheckbox: FunctionComponent<FieldProps> = props => {
Expand All @@ -43,53 +47,53 @@ export const FilterCheckbox: FunctionComponent<FieldProps> = props => {
});

for (const option of options) {
const isChecked = isOptionChecked(option, relevantClauses);
const isChecked = isOptionChecked(option.value, relevantClauses);
if (isChecked) {
checkedOptions.push(option);
checkedOptions.push(option.value);
}
}

return (
<div className="filter-checkbox">
<h5>{props.filter.form.label}</h5>
<div>
{options.map((option: string) => {
const id = `input-${option}`;
let isChecked = checkedOptions.includes(option);
{options.map((option: FilterFieldOption) => {
const id = `input-${option.displayValue}-${option.value}`;
let isChecked = checkedOptions.includes(option.value);
return (
<div
style={{
display: 'inline-block',
padding: '0 1em 0 0',
}}
>
<input
type="checkbox"
id={id}
value={option}
checked={isChecked}
onClick={() => {
isChecked = !isChecked;
updatePhrases(option, isChecked);
const update = createQueryUpdate(
toRemove,
checkedOptions,
props.filter
);
props.onChange(update);
}}
style={{
display: 'inline-block',
}}
/>
<label
htmlFor={id}
style={{
display: 'inline-block',
padding: '0 0 0 0.2em',
}}
>
{option}
<input
type="checkbox"
id={id}
value={option.displayValue}
checked={isChecked}
onClick={() => {
isChecked = !isChecked;
updatePhrases(option.value, isChecked);
const update = createQueryUpdate(
toRemove,
checkedOptions,
props.filter
);
props.onChange(update);
}}
style={{
display: 'inline-block',
}}
/>{' '}
{option.displayValue}
</label>
</div>
);
Expand Down Expand Up @@ -159,7 +163,8 @@ export function createQueryUpdate(
toAdd = [];
} else if (onlyNot || moreAnd) {
const phrase = options
.filter(o => !optionsToAdd.includes(o))
.filter(o => !optionsToAdd.includes(o.value))
.map(toFilterFieldValue)
.join(FILTER_VALUE_SEPARATOR);
toAdd = [new NotSearchClause(createListPhrase(prefix, phrase, fields))];
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export type FilterFieldOption = {
value: string;
displayValue: string;
};

export function toFilterFieldOption(option: string) {
return { value: option, displayValue: option };
}

export function toFilterFieldValue(option: FilterFieldOption) {
return option.value;
}
Loading

0 comments on commit 2f10d41

Please sign in to comment.