diff --git a/src/main/cliapp/src/components/Editors/NotEditor.js b/src/main/cliapp/src/components/Editors/NotEditor.js new file mode 100644 index 000000000..75e8a6264 --- /dev/null +++ b/src/main/cliapp/src/components/Editors/NotEditor.js @@ -0,0 +1,41 @@ +import React, { useState } from 'react'; +import { Dropdown } from "primereact/dropdown"; + +export function NotEditor({ props, value, editorChange }) { + const [selectedValue, setSelectedValue] = useState(value); + const textString = selectedValue ? "NOT" : ""; + const options = [ + { label: "NOT", value: true }, + ]; + + const onChange = (e) => { + let event; + if(e.value === undefined){ + event = { + target: { + value: false, + name: e.target.name + } + } + } else { + event = e; + } + setSelectedValue(event.target.value); + editorChange(event, props); + } + + return ( + <> + onChange(e)} + showClear={true} + placeholder={textString} + style={{ width: '100%' }} + /> + + ); +} diff --git a/src/main/cliapp/src/components/Editors/__tests__/NotEditor.test.js b/src/main/cliapp/src/components/Editors/__tests__/NotEditor.test.js new file mode 100644 index 000000000..bb1c66886 --- /dev/null +++ b/src/main/cliapp/src/components/Editors/__tests__/NotEditor.test.js @@ -0,0 +1,64 @@ +import { render, fireEvent, within } from '@testing-library/react'; +import { NotEditor } from '../NotEditor'; +import '../../../tools/jest/setupTests'; + +describe('NotEditor', () => { + it('should display "NOT" as the placeholder text when the initial value is true', () => { + const props = {}; + const value = true; + const editorChange = jest.fn(); + + const result = render(); + + expect(result.getAllByText("NOT")).toHaveLength(2); + }); + + it('should render a Dropdown component with no options when value prop is undefined', () => { + const props = {}; + const value = undefined; + const editorChange = jest.fn(); + + const result = render(); + + + expect(result.getByText("empty")).toBeInTheDocument(); + }); + + it('should update the selected value when an option is selected', () => { + const props = {}; + const value = false; + const editorChange = jest.fn(); + + + const result = render(); + const span = result.container.getElementsByTagName('span')[0]; + + expect(within(span).getByText('empty')).toBeInTheDocument(); + + fireEvent.click(span); + + const option = result.getAllByText('NOT'); + fireEvent.click(option[0]); + const updatedSpan = result.container.getElementsByTagName('span')[0]; + + + expect(within(updatedSpan).getByText('NOT')).toBeInTheDocument(); + }); + + it('should call editorChange when an option is selected', () => { + const props = {}; + const value = false; + const editorChange = jest.fn(); + const result = render(); + const span = result.container.getElementsByTagName('span')[0]; + + fireEvent.click(span); + + + const option = result.getAllByText('NOT'); + fireEvent.click(option[0]); + + expect(editorChange).toHaveBeenCalledTimes(1); + }); + +}); \ No newline at end of file diff --git a/src/main/cliapp/src/components/Filters/FilterComponentBinaryDropDown.js b/src/main/cliapp/src/components/Filters/FilterComponentBinaryDropDown.js index e15175c72..fb80666fd 100644 --- a/src/main/cliapp/src/components/Filters/FilterComponentBinaryDropDown.js +++ b/src/main/cliapp/src/components/Filters/FilterComponentBinaryDropDown.js @@ -2,7 +2,7 @@ import React, { useRef } from "react"; import { Dropdown } from "primereact/dropdown"; export function FilterComponentBinaryDropDown({ isInEditMode, filterConfig, currentFilters, onFilter }) { - const options = useRef(["true", "false"]); + const options = useRef(filterConfig.options || ["true", "false"]); const fieldSet = filterConfig.fieldSets[0]; diff --git a/src/main/cliapp/src/components/Templates/NotTemplate.js b/src/main/cliapp/src/components/Templates/NotTemplate.js new file mode 100644 index 000000000..5538a06fa --- /dev/null +++ b/src/main/cliapp/src/components/Templates/NotTemplate.js @@ -0,0 +1,8 @@ +import React from 'react' +import { EllipsisTableCell } from '../EllipsisTableCell'; + +export const NotTemplate = ({ value }) => { + if (value === null || value === undefined || typeof value !== 'boolean') return null; + const textString = value ? "NOT" : ""; + return {textString}; +} diff --git a/src/main/cliapp/src/components/Templates/__tests__/NotTemplate.test.js b/src/main/cliapp/src/components/Templates/__tests__/NotTemplate.test.js new file mode 100644 index 000000000..0118a4c0e --- /dev/null +++ b/src/main/cliapp/src/components/Templates/__tests__/NotTemplate.test.js @@ -0,0 +1,25 @@ +import { render } from '@testing-library/react'; +import { NotTemplate } from '../NotTemplate'; +import '../../../tools/jest/setupTests'; + +describe('NotTemplate', () => { + + it('should return null when value is null', () => { + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it('should return a component with the text "NOT" when the value is true', () => { + const { getByText } = render(); + expect(getByText("NOT")).toBeInTheDocument(); + }); + it('should return a component with an empty string when the value is false', () => { + const { container } = render(); + expect(container.textContent).toBe(''); + }); + + it('should return null when value is not a boolean', () => { + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); +}); \ No newline at end of file diff --git a/src/main/cliapp/src/constants/FilterFields.js b/src/main/cliapp/src/constants/FilterFields.js index 56032443e..e1b604f48 100644 --- a/src/main/cliapp/src/constants/FilterFields.js +++ b/src/main/cliapp/src/constants/FilterFields.js @@ -594,7 +594,7 @@ export const FILTER_CONFIGS = Object.freeze({ isExtinctFilterConfig: { filterComponentType: "dropdown", fieldSets: [FIELD_SETS.isExtinctFieldSet] }, obsoleteFilterConfig: { filterComponentType: "dropdown", fieldSets: [FIELD_SETS.obsoleteFieldSet] }, internalFilterConfig: { filterComponentType: "dropdown", fieldSets: [FIELD_SETS.internalFieldSet] }, - negatedFilterConfig: { filterComponentType: "dropdown", fieldSets: [FIELD_SETS.negatedFieldSet] }, + negatedFilterConfig: { filterComponentType: "dropdown", fieldSets: [FIELD_SETS.negatedFieldSet], options: [ { label: "NOT", value: "true" }, { label: "null", value: "false" } ] }, annotationTypeFilterConfig: { filterComponentType: "multiselect", fieldSets: [FIELD_SETS.annotationTypeFieldSet], aggregationFieldSet: FIELD_SETS.daAggregationFieldSet, useKeywordFields: true }, diseaseDataProviderFilterConfig: { filterComponentType: "multiselect", fieldSets: [FIELD_SETS.dataProviderFieldSet], aggregationFieldSet: FIELD_SETS.daAggregationFieldSet, useKeywordFields: true }, @@ -605,12 +605,12 @@ export const FILTER_CONFIGS = Object.freeze({ agmDataProviderFilterConfig: { filterComponentType: "multiselect", fieldSets: [FIELD_SETS.dataProviderFieldSet], aggregationFieldSet: FIELD_SETS.agmAggregationFieldSet, useKeywordFields: true }, diseaseQualifiersFilterConfig: { filterComponentType: "multiselect", fieldSets: [FIELD_SETS.diseaseQualifiersFieldSet], aggregationFieldSet: FIELD_SETS.daAggregationFieldSet, useKeywordFields: true }, relationFilterConfig: { filterComponentType: "multiselect", fieldSets: [FIELD_SETS.relationFieldSet], aggregationFieldSet: FIELD_SETS.daAggregationFieldSet, useKeywordFields: true }, - paRelationFilterConfig: { filterComponentType: "multiselect", fieldSets: [FIELD_SETS.relationFieldSet], aggregationFieldSet: FIELD_SETS.paAggregationFieldSet, useKeywordFields: true }, + paRelationFilterConfig: { filterComponentType: "multiselect", fieldSets: [FIELD_SETS.relationFieldSet], aggregationFieldSet: FIELD_SETS.paAggregationFieldSet, useKeywordFields: true }, geneticModifierRelationFilterConfig: { filterComponentType: "multiselect", fieldSets: [FIELD_SETS.geneticModifierRelationFieldSet], aggregationFieldSet: FIELD_SETS.daAggregationFieldSet, useKeywordFields: true }, geneticSexFilterConfig: { filterComponentType: "multiselect", fieldSets: [FIELD_SETS.geneticSexFieldSet], aggregationFieldSet: FIELD_SETS.daAggregationFieldSet, useKeywordFields: true }, secondaryDataProviderFilterConfig: { filterComponentType: "multiselect", fieldSets: [FIELD_SETS.secondaryDataProviderFieldSet], aggregationFieldSet: FIELD_SETS.daAggregationFieldSet, useKeywordFields: true }, - speciesDataProviderFilterConfig: { filterComponentType: "multiselect", fieldSets: [FIELD_SETS.dataProviderFieldSet], aggregationFieldSet: FIELD_SETS.speciesAggregationFieldSet, useKeywordFields: true }, - evidenceCodesFilterConfig: { filterComponentType: "multiselect", fieldSets: [FIELD_SETS.evidenceCodesFieldSet], aggregationFieldSet: FIELD_SETS.daAggregationFieldSet, useKeywordFields: true }, + speciesDataProviderFilterConfig: { filterComponentType: "multiselect", fieldSets: [FIELD_SETS.dataProviderFieldSet], aggregationFieldSet: FIELD_SETS.speciesAggregationFieldSet, useKeywordFields: true }, + evidenceCodesFilterConfig: { filterComponentType: "multiselect", fieldSets: [FIELD_SETS.evidenceCodesFieldSet], aggregationFieldSet: FIELD_SETS.daAggregationFieldSet, useKeywordFields: true }, variantDataProviderFilterConfig: { filterComponentType: "multiselect", fieldSets: [FIELD_SETS.dataProviderFieldSet], aggregationFieldSet: FIELD_SETS.variantAggregationFieldSet, useKeywordFields: true }, variantStatusFilterConfig: { filterComponentType: "multiselect", fieldSets: [FIELD_SETS.variantStatusFieldSet], aggregationFieldSet: FIELD_SETS.variantAggregationFieldSet,useKeywordFields: true }, diff --git a/src/main/cliapp/src/containers/diseaseAnnotationsPage/DiseaseAnnotationsTable.js b/src/main/cliapp/src/containers/diseaseAnnotationsPage/DiseaseAnnotationsTable.js index 7a49b220b..d2151ead2 100644 --- a/src/main/cliapp/src/containers/diseaseAnnotationsPage/DiseaseAnnotationsTable.js +++ b/src/main/cliapp/src/containers/diseaseAnnotationsPage/DiseaseAnnotationsTable.js @@ -22,6 +22,9 @@ import { DiseaseTemplate } from '../../components/Templates/DiseaseTemplate'; import { GenomicEntityTemplate } from '../../components/Templates/genomicEntity/GenomicEntityTemplate'; import { GenomicEntityListTemplate } from '../../components/Templates/genomicEntity/GenomicEntityListTemplate'; import { BooleanTemplate } from '../../components/Templates/BooleanTemplate'; +import { NotTemplate } from '../../components/Templates/NotTemplate'; + +import { NotEditor } from '../../components/Editors/NotEditor'; import { ControlledVocabularyDropdown } from '../../components/ControlledVocabularySelector'; import { ConditionRelationHandleDropdown } from '../../components/ConditionRelationHandleSelector'; @@ -428,26 +431,12 @@ export const DiseaseAnnotationsTable = () => { ); }; - const onNegatedEditorValueChange = (props, event) => { - let updatedAnnotations = [...props.props.value]; - if (event.value || event.value === '') { - updatedAnnotations[props.rowIndex].negated = JSON.parse(event.value.name); - } - }; + const onNegatedEditorValueChange = (event, props) => { + if(event.target.value === undefined || event.target.value === null) return; - const negatedEditor = (props) => { - return ( - <> - - - - ); - }; + let updatedAnnotations = [...props.props.value]; + updatedAnnotations[props.rowIndex].negated = event.target.value; + } const onInternalEditorValueChange = (props, event) => { let updatedAnnotations = [...props.props.value]; @@ -934,11 +923,11 @@ export const DiseaseAnnotationsTable = () => { }, { field: "negated", - header: "Negated", - body: (rowData) => , + header: "NOT", + body: (rowData) => , sortable: true, filterConfig: FILTER_CONFIGS.negatedFilterConfig, - editor: (props) => negatedEditor(props) + editor: (props) => }, { field: "diseaseAnnotationObject.name", diff --git a/src/main/cliapp/src/containers/diseaseAnnotationsPage/NewAnnotationForm.js b/src/main/cliapp/src/containers/diseaseAnnotationsPage/NewAnnotationForm.js index 6c0982ce5..b7457df74 100644 --- a/src/main/cliapp/src/containers/diseaseAnnotationsPage/NewAnnotationForm.js +++ b/src/main/cliapp/src/containers/diseaseAnnotationsPage/NewAnnotationForm.js @@ -6,6 +6,7 @@ import { Toast } from "primereact/toast"; import { MultiSelect } from 'primereact/multiselect'; import { useMutation, useQueryClient } from "react-query"; import { FormErrorMessageComponent } from "../../components/Error/FormErrorMessageComponent"; +import { NotEditor } from "../../components/Editors/NotEditor"; import { classNames } from "primereact/utils"; import { DiseaseAnnotationService } from "../../service/DiseaseAnnotationService"; import { Splitter, SplitterPanel } from "primereact/splitter"; @@ -75,7 +76,7 @@ export const NewAnnotationForm = ({ const geneticModifierRelationTerms = useControlledVocabularyService('disease_genetic_modifier_relation'); const [uiErrorMessages, setUiErrorMessages] = useState({}); const areUiErrors = useRef(false); - const newAnnotationOptionalFields = ["Asserted Genes", "Asserted Allele", "Negated", "With", "Related Notes", "Experimental Conditions", "Experiments", "Genetic Sex", + const newAnnotationOptionalFields = ["Asserted Genes", "Asserted Allele", "NOT", "With", "Related Notes", "Experimental Conditions", "Experiments", "Genetic Sex", "Disease Qualifiers", "SGD Strain Background", "Annotation Type", "Genetic Modifier Relation", "Genetic Modifiers","Internal"]; let defaultUserSettings = getDefaultFormState("DiseaseAnnotations", newAnnotationOptionalFields, undefined); const { settings: settingsKey , mutate: setSettingsKey } = useGetUserSettings('DiseaseAnnotationsFormSettings', defaultUserSettings, false); @@ -566,22 +567,14 @@ export const NewAnnotationForm = ({ - {selectedFormFields?.includes("Negated") && ( + {selectedFormFields?.includes("NOT") && ( <>
- +
- +
diff --git a/src/main/cliapp/src/containers/diseaseAnnotationsPage/__tests__/DiseaseAnnotationsTable.test.js b/src/main/cliapp/src/containers/diseaseAnnotationsPage/__tests__/DiseaseAnnotationsTable.test.js index ffcaed57f..2318a7d09 100644 --- a/src/main/cliapp/src/containers/diseaseAnnotationsPage/__tests__/DiseaseAnnotationsTable.test.js +++ b/src/main/cliapp/src/containers/diseaseAnnotationsPage/__tests__/DiseaseAnnotationsTable.test.js @@ -37,7 +37,8 @@ describe("", () => { const modInternalIdTd = await result.findByText("mockModInternalId"); const subjectTd = await result.findByText(/C57BL\/6J-Rfx3/i); const relationTd = await result.findByText("is_model_of"); - const negatedInternalObsoleteArray = await result.findAllByText("false"); + const internalObsoleteArray = await result.findAllByText("false"); + const NOTArray = await result.findAllByText("NOT"); const diseaseTd = await result.findByText(/visceral heterotaxy/i); const referenceTd = await result.findByText(/MGI:5284969/i); const evidenceCodeTd = await result.findByText(/TAS/i); @@ -63,7 +64,8 @@ describe("", () => { expect(modInternalIdTd).toBeInTheDocument(); expect(subjectTd).toBeInTheDocument(); expect(relationTd).toBeInTheDocument(); - expect(negatedInternalObsoleteArray.length).toEqual(3); + expect(internalObsoleteArray.length).toEqual(2); + expect(NOTArray.length).toEqual(2); expect(diseaseTd).toBeInTheDocument(); expect(referenceTd).toBeInTheDocument(); expect(evidenceCodeTd).toBeInTheDocument(); diff --git a/src/main/cliapp/src/containers/diseaseAnnotationsPage/mockData/mockData.js b/src/main/cliapp/src/containers/diseaseAnnotationsPage/mockData/mockData.js index 4442d524d..5e423d5f2 100644 --- a/src/main/cliapp/src/containers/diseaseAnnotationsPage/mockData/mockData.js +++ b/src/main/cliapp/src/containers/diseaseAnnotationsPage/mockData/mockData.js @@ -96,7 +96,7 @@ export const data = { } ] }, - "negated": false, + "negated": true, "relation": { "dateCreated": "2022-01-26T09:40:54.020724Z", "dateUpdated": "2022-01-26T09:40:54.020726Z",