Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

psp-7658 display is retired in property list view. #3835

Merged
merged 3 commits into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions source/backend/dal/Helpers/Extensions/PropertyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Text.RegularExpressions;
using LinqKit;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Pims.Core.Extensions;
using Pims.Dal.Entities;
using Pims.Dal.Security;
Expand Down Expand Up @@ -94,9 +95,13 @@ private static ExpressionStarter<PimsProperty> GenerateCommonPropertyQuery(Claim
predicateBuilder = predicateBuilder.And(p => p != null && p.SurveyPlanNumber.Equals(filter.PlanNumber));
}

var isRetired = filter.Ownership.Contains("isRetired");

ExpressionStarter<PimsProperty> ownershipBuilder;

if (filter.Ownership.Count > 0)
{
var ownershipBuilder = PredicateBuilder.New<PimsProperty>(p => false);
ownershipBuilder = isRetired ? PredicateBuilder.New<PimsProperty>(p => p.IsRetired == true) : PredicateBuilder.New<PimsProperty>(p => false);
if (filter.Ownership.Contains("isCoreInventory"))
{
ownershipBuilder = ownershipBuilder.Or(p => p.IsOwned);
Expand All @@ -113,9 +118,13 @@ private static ExpressionStarter<PimsProperty> GenerateCommonPropertyQuery(Claim
{
ownershipBuilder = ownershipBuilder.Or(p => p.IsDisposed);
}

predicateBuilder = predicateBuilder.And(ownershipBuilder);
}
else
{
// psp-7658 is retired properties should be omitted by default.
ownershipBuilder = PredicateBuilder.New<PimsProperty>(p => p.IsRetired != true);
}
predicateBuilder = predicateBuilder.And(ownershipBuilder);

return predicateBuilder;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,14 @@ public class PropertyRepositoryTest
{
new object[] { new PropertyFilter() { PinOrPid = "111-111-111" , Ownership = new List<string>()}, 1 },
new object[] { new PropertyFilter() { PinOrPid = "111" , Ownership = new List<string>()}, 2 },
new object[] { new PropertyFilter() { Address = "12342 Test Street" , Ownership = new List<string>()}, 6 },
new object[] { new PropertyFilter() { Page = 1, Quantity = 10 , Ownership = new List<string>() }, 6 },
new object[] { new PropertyFilter(), 6 },
new object[] { new PropertyFilter() { Address = "12342 Test Street" , Ownership = new List<string>()}, 7 },
new object[] { new PropertyFilter() { PlanNumber = "SP-89TTXY", Ownership = new List<string>()}, 1 },
new object[] { new PropertyFilter() { Page = 1, Quantity = 10 , Ownership = new List<string>() }, 7 },
new object[] { new PropertyFilter(), 7 },
new object[] { new PropertyFilter(){ Ownership = new List<string>(){"isCoreInventory", "isPropertyOfInterest"}}, 4 },
new object[] { new PropertyFilter(){ Ownership = new List<string>(){"isDisposed"}}, 1 },
new object[] { new PropertyFilter(){ Ownership = new List<string>(){"isRetired"}}, 1 },
new object[] { new PropertyFilter(){ Ownership = new List<string>(){"isOtherInterest"}}, 1 },
new object[] { new PropertyFilter(){ Ownership = new List<string>(){"isCoreInventory"}}, 3 },
};
#endregion
Expand Down Expand Up @@ -110,6 +114,10 @@ public void GetPage_Properties(PropertyFilter filter, int expectedCount)
testProperty.IsOwned = true;
testProperty = init.CreateProperty(111111111);
testProperty.IsOwned = true;
testProperty = init.CreateProperty(22222);
testProperty.IsRetired = true;
testProperty = init.CreateProperty(33333);
testProperty.SurveyPlanNumber = "SP-89TTXY";

init.SaveChanges();

Expand Down
30 changes: 30 additions & 0 deletions source/frontend/src/components/common/TooltipIcon.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
margin: 0rem 0.2rem;
}

.tooltip-icon.warning {
min-width: 1.4rem;
min-height: 1.4rem;
color: $font-warning-color;
margin: 0rem 0.2rem;
}

.tooltip {
white-space: pre-wrap;
.tooltip-inner {
Expand All @@ -29,6 +36,29 @@
}
}

.tooltip.warning {
white-space: pre-wrap;
.tooltip-inner {
text-align: left;
max-width: 40rem;
background-color: $summary-color;
color: $font-warning-color;
font-weight: 700;
}
&.bs-tooltip-top .arrow::before {
border-top-color: $secondary-variant-color;
}
&.bs-tooltip-right .arrow::before {
border-right-color: $secondary-variant-color;
}
&.bs-tooltip-left .arrow::before {
border-left-color: $secondary-variant-color;
}
&.bs-tooltip-bottom .arrow::before {
border-bottom-color: $secondary-variant-color;
}
}

.tooltip.tooltip-light {
.tooltip-inner {
background-color: $filter-box-color;
Expand Down
25 changes: 14 additions & 11 deletions source/frontend/src/components/common/TooltipIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface TooltipIconProps extends Partial<React.ComponentPropsWithRef<typeof Ov
toolTipId: string;
className?: string;
innerClassName?: string;
variant?: 'info' | 'warning';
customOverlay?: OverlayChildren;
customToolTipIcon?: React.ReactNode;
}
Expand All @@ -20,29 +21,31 @@ const TooltipIcon: React.FunctionComponent<React.PropsWithChildren<TooltipIconPr
const overlay =
props.customOverlay === undefined
? ((
<Tooltip id={props.toolTipId} className={props.className}>
<Tooltip id={props.toolTipId} className={classNames(props.className, props.variant)}>
{props.toolTip}
</Tooltip>
) as OverlayChildren)
: props.customOverlay;

const icon =
props.customToolTipIcon === undefined ? (
<FaInfoCircle className={classNames('tooltip-icon', props.innerClassName)} />
<FaInfoCircle className={classNames('tooltip-icon', props.innerClassName, props.variant)} />
) : (
props.customToolTipIcon
);

return (
<OverlayTrigger placement={props.placement} overlay={overlay}>
<span
data-testid={`tooltip-icon-${props.toolTipId}`}
className="tooltip-icon"
id={props.toolTipId}
>
{icon}
</span>
</OverlayTrigger>
<>
<OverlayTrigger placement={props.placement} overlay={overlay}>
<span
data-testid={`tooltip-icon-${props.toolTipId}`}
className="tooltip-icon"
id={props.toolTipId}
>
{icon}
</span>
</OverlayTrigger>
</>
);
};

Expand Down
29 changes: 11 additions & 18 deletions source/frontend/src/components/common/form/Multiselect.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import { act } from '@testing-library/react';
import { Formik } from 'formik';
import noop from 'lodash/noop';

import { render, RenderOptions, userEvent, waitFor } from '@/utils/test-utils';
import {
focusOptionMultiselect,
render,
RenderOptions,
userEvent,
waitFor,
} from '@/utils/test-utils';

import { IMultiselectProps, Multiselect } from './Multiselect';

Expand Down Expand Up @@ -84,11 +90,12 @@ describe('Multiselect component', () => {
});

// click on the multi-select to show drop-down list
act(() => userEvent.click(getInput()));
await waitFor(async () => {
act(() => userEvent.click(getInput()));
});

// select an option from the drop-down
focusOption(container, optionSelected, fakeOptions);
await act(async () => await waitFor(() => userEvent.click(findDropdownOption(optionSelected))));
focusOptionMultiselect(container, optionSelected, fakeOptions);

// assert
expect(onSelectSpy).toHaveBeenCalledWith([optionSelected]);
Expand All @@ -115,17 +122,3 @@ describe('Multiselect component', () => {
expect(onRemoveSpy).toHaveBeenCalledWith(remainingOptions);
});
});

// simulate scrolling down using the keyboard arrows
function focusOption(container: HTMLElement, option: Option, options: readonly Option[]) {
const indexOfSelectedOption = options.findIndex(o => o.id === option.id);
for (let i = 0; i < indexOfSelectedOption; i++) {
act(() => {
userEvent.keyboard('{ArrowDown}');
});
}
expect(
container.querySelector('.multiselect-container .optionContainer li.option.highlight')!
.textContent,
).toEqual(option.text);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ import UpdateAcquisitionAgreementForm, {
IUpdateAcquisitionAgreementViewProps,
} from './UpdateAcquisitionAgreementForm';
import { AcquisitionAgreementFormModel } from '../models/AcquisitionAgreementFormModel';
import { act, RenderOptions, render, fillInput } from '@/utils/test-utils';
import {
act,
RenderOptions,
render,
fillInput,
waitForEffects,
selectOptions,
} from '@/utils/test-utils';

export const organizerMock = {
canEditOrDeleteAgreement: jest.fn(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,6 @@ const DispositionForm = React.forwardRef<FormikProps<DispositionFormModel>, IDis
<SectionField label="Assigned date">
<FastDatePicker field="assignedDate" formikProps={formikProps} />
</SectionField>
<SectionField label="Disposition completed date">
<FastDatePicker field="completionDate" formikProps={formikProps} />
</SectionField>
</Section>

<Section header="Properties to include in this file:">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,18 @@ import { ApiGen_Base_Page } from '@/models/api/generated/ApiGen_Base_Page';
import { ApiGen_Concepts_Property } from '@/models/api/generated/ApiGen_Concepts_Property';
import { ILookupCode } from '@/store/slices/lookupCodes';
import { lookupCodesSlice } from '@/store/slices/lookupCodes';
import { cleanup, render, RenderOptions, waitFor } from '@/utils/test-utils';

import PropertyListView from './PropertyListView';
import {
act,
cleanup,
focusOptionMultiselect,
render,
RenderOptions,
userEvent,
waitFor,
} from '@/utils/test-utils';

import PropertyListView, { ownershipFilterOptions } from './PropertyListView';
import { MultiSelectOption } from '@/features/acquisition/list/interfaces';

// Set all module functions to jest.fn
jest.mock('@react-keycloak/web');
Expand Down Expand Up @@ -145,4 +154,68 @@ describe('Property list view', () => {
expect(getByTestId('view-prop-tab')).toBeInTheDocument();
expect(getByTestId('view-prop-ext')).toBeInTheDocument();
});

it('preselects default property ownership state', async () => {
setupMockApi([mockApiProperty]);
const {
component: { getByText },
findSpinner,
} = setup({});

// wait for table to finish loading
await waitFor(async () => expect(findSpinner()).not.toBeInTheDocument());

expect(getByText('Core Inventory')).toBeInTheDocument();
expect(getByText('Property Of Interest')).toBeInTheDocument();
expect(getByText('Disposed')).toBeInTheDocument();
});

it('allows property ownership to be selected', async () => {
setupMockApi([mockApiProperty]);
const {
component: { container },
findSpinner,
} = setup({});

// wait for table to finish loading
await waitFor(async () => expect(findSpinner()).not.toBeInTheDocument());

const optionSelected = ownershipFilterOptions.find(
o => o.id === 'isDisposed',
) as MultiSelectOption;

// click on the multi-select to show drop-down list
act(() => userEvent.click(container.querySelector(`#properties-selector`) as HTMLInputElement));

// select an option from the drop-down
focusOptionMultiselect(container, optionSelected, ownershipFilterOptions);

await waitFor(() => {
expect(mockApiGetPropertiesPagedApi).toHaveBeenCalledWith({
address: '',
latitude: '',
longitude: '',
ownership: 'isCoreInventory,isPropertyOfInterest,isOtherInterest,isDisposed',
page: 1,
pinOrPid: '',
planNumber: '',
quantity: 10,
searchBy: 'pinOrPid',
sort: undefined,
});
});
});

it('displays a tooltip beside properties that are retired', async () => {
setupMockApi([{ ...mockApiProperty, isRetired: true }]);
const {
component: { getByTestId },
findSpinner,
} = setup({});

// wait for table to finish loading
await waitFor(async () => expect(findSpinner()).not.toBeInTheDocument());

expect(getByTestId('tooltip-icon-retired-tooltip')).toBeVisible();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ import { defaultPropertyFilter, IPropertyFilter } from '../filter/IPropertyFilte
import { SearchToggleOption } from '../filter/PropertySearchToggle';
import { columns as columnDefinitions } from './columns';

const ownershipFilterOptions: MultiSelectOption[] = [
export const ownershipFilterOptions: MultiSelectOption[] = [
{ id: 'isCoreInventory', text: 'Core Inventory' },
{ id: 'isPropertyOfInterest', text: 'Property Of Interest' },
{ id: 'isOtherInterest', text: 'Other Interest' },
{ id: 'isDisposed', text: 'Disposed' },
{ id: 'isRetired', text: 'Retired' },
];

const PropertyListView: React.FC<React.PropsWithChildren<unknown>> = () => {
Expand Down Expand Up @@ -156,6 +157,7 @@ const PropertyListView: React.FC<React.PropsWithChildren<unknown>> = () => {
const multiselectOwnershipRef = React.createRef<Multiselect>();

const onSelectedOwnershipChange = (selectedList: MultiSelectOption[]) => {
setPageIndex(0);
const selectedIds = selectedList.map(o => o.id);
setFilter({ ...filter, ownership: selectedIds.join(',') });
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,11 @@ exports[`Property list view matches snapshot 1`] = `
>
Disposed
</li>
<li
class="option "
>
Retired
</li>
</ul>
</div>
</div>
Expand Down
18 changes: 18 additions & 0 deletions source/frontend/src/features/properties/list/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { StyledIconButton } from '@/components/common/buttons';
import { Input } from '@/components/common/form';
import { TypeaheadField } from '@/components/common/form/Typeahead';
import { InlineFlexDiv } from '@/components/common/styles';
import TooltipIcon from '@/components/common/TooltipIcon';
import { ColumnWithProps } from '@/components/Table';
import { AreaUnitTypes, Claims } from '@/constants/index';
import useKeycloakWrapper from '@/hooks/useKeycloakWrapper';
Expand All @@ -30,6 +31,23 @@ export const columns = ({ municipalities }: Props): ColumnWithProps<ApiGen_Conce
accessor: 'pid',
align: 'right',
width: 40,
Cell: (props: CellProps<ApiGen_Concepts_Property>) => {
return (
<>
{props.row.original.pid}
<span style={{ width: '2rem' }}>
{props.row.original.isRetired ? (
<TooltipIcon
variant="warning"
toolTipId="retired-tooltip"
toolTip="RETIRED"
placement="right"
/>
) : null}
</span>
</>
);
},
},
{
Header: 'PIN',
Expand Down
Loading
Loading