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

Acquisition File Header | psp-6936 #3507

Merged
merged 5 commits into from
Oct 7, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,21 @@ public IActionResult GetAcquisitionFile(long id)
return new JsonResult(_mapper.Map<AcquisitionFileModel>(acqFile));
}

/// <summary>
/// Gets the specified acquisition file last updated-by information.
/// </summary>
/// <returns></returns>
[HttpGet("{id:long}/updateInfo")]
[HasPermission(Permissions.AcquisitionFileView)]
[Produces("application/json")]
[ProducesResponseType(typeof(Dal.Entities.Models.LastUpdatedByModel), 200)]
[SwaggerOperation(Tags = new[] { "acquisitionfile" })]
public IActionResult GetLastUpdatedBy(long id)
{
var lastUpdated = _acquisitionService.GetLastUpdateInformation(id);
return new JsonResult(lastUpdated);
}

/// <summary>
/// Adds the specified acquisition file.
/// </summary>
Expand Down
8 changes: 8 additions & 0 deletions source/backend/api/Services/AcquisitionFileService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,14 @@ public PimsAcquisitionFile GetById(long id)
return acqFile;
}

public LastUpdatedByModel GetLastUpdateInformation(long acquisitionFileId)
{
_logger.LogInformation("Retrieving last updated-by information...");
_user.ThrowIfNotAuthorized(Permissions.AcquisitionFileView);

return _acqFileRepository.GetLastUpdateBy(acquisitionFileId);
}

public IEnumerable<PimsPropertyAcquisitionFile> GetProperties(long id)
{
_logger.LogInformation("Getting acquisition file with id {id}", id);
Expand Down
2 changes: 2 additions & 0 deletions source/backend/api/Services/IAcquisitionFileService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public interface IAcquisitionFileService

PimsAcquisitionFile GetById(long id);

LastUpdatedByModel GetLastUpdateInformation(long acquisitionFileId);

PimsAcquisitionFile Add(PimsAcquisitionFile acquisitionFile, IEnumerable<UserOverrideCode> userOverrides);

PimsAcquisitionFile Update(PimsAcquisitionFile acquisitionFile, IEnumerable<UserOverrideCode> userOverrides);
Expand Down
432 changes: 432 additions & 0 deletions source/backend/dal/Repositories/AcquisitionFileRepository.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public interface IAcquisitionFileRepository : IRepository

PimsAcquisitionFile GetById(long id);

LastUpdatedByModel GetLastUpdateBy(long id);

List<PimsAcquisitionOwner> GetOwnersByAcquisitionFileId(long acquisitionFileId);

List<PimsAcquisitionFilePerson> GetTeamMembers(HashSet<short> regions, long? contractorPersonId = null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
mockAcquisitionFileOwnersResponse,
mockAcquisitionFileResponse,
} from '@/mocks/acquisitionFiles.mock';
import { mockLastUpdatedBy } from '@/mocks/lastUpdatedBy.mock';
import { mockLookups } from '@/mocks/lookups.mock';
import { mapMachineBaseMock } from '@/mocks/mapFSM.mock';
import { mockNotesResponse } from '@/mocks/noteResponses.mock';
Expand Down Expand Up @@ -39,7 +40,6 @@ jest.mock('@/features/documents/hooks/useDocumentGenerationRepository');
}));

jest.mock('@/components/common/mapFSM/MapStateMachineContext');
(useMapStateMachine as jest.Mock).mockImplementation(() => mapMachineBaseMock);

const onClose = jest.fn();

Expand Down Expand Up @@ -96,10 +96,13 @@ describe('AcquisitionContainer component', () => {
};

beforeEach(() => {
(useMapStateMachine as jest.Mock).mockImplementation(() => mapMachineBaseMock);

mockAxios.onGet(new RegExp('users/info/*')).reply(200, {});
mockAxios
.onGet(new RegExp('acquisitionfiles/1/properties'))
.reply(200, mockAcquisitionFileResponse().fileProperties);
mockAxios.onGet(new RegExp('acquisitionfiles/1/updateInfo')).reply(200, mockLastUpdatedBy(1));
mockAxios
.onGet(new RegExp('acquisitionfiles/1/owners'))
.reply(200, mockAcquisitionFileOwnersResponse());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,21 @@ const initialState: AcquisitionContainerState = {
export const AcquisitionContainer: React.FunctionComponent<IAcquisitionContainerProps> = props => {
// Load state from props and side-bar context
const { acquisitionFileId, onClose, View } = props;
const { setFile, setFileLoading, staleFile, setStaleFile, file } = useContext(SideBarContext);
const {
setFile,
setFileLoading,
staleFile,
setStaleFile,
file,
setLastUpdatedBy,
lastUpdatedBy,
staleLastUpdatedBy,
} = useContext(SideBarContext);
const [isValid, setIsValid] = useState<boolean>(true);
const withUserOverride = useApiUserOverride<
(userOverrideCodes: UserOverrideCode[]) => Promise<any | void>
>('Failed to update Acquisition File');

const {
getAcquisitionFile: { execute: retrieveAcquisitionFile, loading: loadingAcquisitionFile },
updateAcquisitionProperties,
Expand All @@ -64,6 +74,7 @@ export const AcquisitionContainer: React.FunctionComponent<IAcquisitionContainer
loading: loadingAcquisitionFileProperties,
},
getAcquisitionFileChecklist: { execute: retrieveAcquisitionFileChecklist },
getLastUpdatedBy: { execute: getLastUpdatedBy, loading: loadingGetLastUpdatedBy },
} = useAcquisitionProvider();

const mapMachine = useMapStateMachine();
Expand Down Expand Up @@ -131,15 +142,42 @@ export const AcquisitionContainer: React.FunctionComponent<IAcquisitionContainer
setStaleFile,
]);

const fetchLastUpdatedBy = React.useCallback(async () => {
var retrieved = await getLastUpdatedBy(acquisitionFileId);
if (retrieved !== undefined) {
setLastUpdatedBy(retrieved);
} else {
setLastUpdatedBy(null);
}
}, [acquisitionFileId, getLastUpdatedBy, setLastUpdatedBy]);

React.useEffect(() => {
if (
lastUpdatedBy === undefined ||
acquisitionFileId !== lastUpdatedBy?.parentId ||
staleLastUpdatedBy
) {
fetchLastUpdatedBy();
}
}, [fetchLastUpdatedBy, lastUpdatedBy, acquisitionFileId, staleLastUpdatedBy]);

useEffect(() => {
if (acquisitionFile === undefined || acquisitionFileId !== acquisitionFile.id || staleFile) {
fetchAcquisitionFile();
}
}, [acquisitionFile, fetchAcquisitionFile, acquisitionFileId, staleFile]);

useEffect(
() => setFileLoading(loadingAcquisitionFile || loadingAcquisitionFileProperties),
[loadingAcquisitionFile, setFileLoading, loadingAcquisitionFileProperties],
() =>
setFileLoading(
loadingAcquisitionFile || loadingAcquisitionFileProperties || loadingGetLastUpdatedBy,
),
[
loadingAcquisitionFile,
setFileLoading,
loadingAcquisitionFileProperties,
loadingGetLastUpdatedBy,
],
);

const close = useCallback(() => onClose && onClose(), [onClose]);
Expand Down Expand Up @@ -207,6 +245,7 @@ export const AcquisitionContainer: React.FunctionComponent<IAcquisitionContainer

const onSuccess = () => {
fetchAcquisitionFile();
fetchLastUpdatedBy();
mapMachine.refreshMapProperties();
setIsEditing(false);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
mockAcquisitionFileResponse,
} from '@/mocks/acquisitionFiles.mock';
import { getMockApiInterestHolders } from '@/mocks/interestHolders.mock';
import { mockLastUpdatedBy } from '@/mocks/lastUpdatedBy.mock';
import { mockLookups } from '@/mocks/lookups.mock';
import { mapMachineBaseMock } from '@/mocks/mapFSM.mock';
import { rest, server } from '@/mocks/msw/server';
Expand Down Expand Up @@ -89,6 +90,9 @@ describe('AcquisitionView component', () => {
...mockAcquisitionFileResponse(),
fileType: FileTypes.Acquisition,
}}
lastUpdatedBy={{
...mockLastUpdatedBy(1),
}}
>
<Route path="/mapview/sidebar/acquisition/:id">
<AcquisitionView {...props} />
Expand Down Expand Up @@ -149,7 +153,7 @@ describe('AcquisitionView component', () => {
expect(getByText('1-12345-01 - Test ACQ File')).toBeVisible();
expect(getByText(prettyFormatUTCDate(testAcquisitionFile.appCreateTimestamp))).toBeVisible();
expect(
getByText(prettyFormatUTCDate(testAcquisitionFile.appLastUpdateTimestamp)),
getByText(prettyFormatUTCDate(mockLastUpdatedBy(1).appLastUpdateTimestamp)),
).toBeVisible();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const AcquisitionView: React.FunctionComponent<IAcquisitionViewProps> = (
const location = useLocation();
const history = useHistory();
const match = useRouteMatch();
const { file } = useContext(SideBarContext);
const { file, lastUpdatedBy } = useContext(SideBarContext);
if (!!file && file?.fileType !== FileTypes.Acquisition) {
throw Error('Context file is not an acquisition file');
}
Expand Down Expand Up @@ -130,7 +130,9 @@ export const AcquisitionView: React.FunctionComponent<IAcquisitionViewProps> = (
className="mr-2"
/>
}
header={<AcquisitionHeader acquisitionFile={acquisitionFile} />}
header={
<AcquisitionHeader acquisitionFile={acquisitionFile} lastUpdatedBy={lastUpdatedBy} />
}
footer={
isEditing && (
<SidebarFooter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ exports[`AcquisitionView component renders as expected 1`] = `
>
Last updated:
<strong>
Jul 27, 2022
Oct 6, 2023
</strong>
by
<span
Expand All @@ -230,7 +230,7 @@ exports[`AcquisitionView component renders as expected 1`] = `
id="userNameTooltip"
>
<strong>
admin
MARODRIG
</strong>
</span>
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@ const mockAxios = new MockAdapter(axios);
describe('AcquisitionHeader component', () => {
// render component under test
const setup = (props: IAcquisitionHeaderProps, renderOptions: RenderOptions = {}) => {
const utils = render(<AcquisitionHeader acquisitionFile={props.acquisitionFile} />, {
...renderOptions,
});
const utils = render(
<AcquisitionHeader
acquisitionFile={props.acquisitionFile}
lastUpdatedBy={props.lastUpdatedBy}
/>,
{
...renderOptions,
},
);

return { ...utils };
};
Expand All @@ -29,13 +35,21 @@ describe('AcquisitionHeader component', () => {
});

it('renders as expected when no data is provided', () => {
const { asFragment } = setup({});
const { asFragment } = setup({ lastUpdatedBy: null });
expect(asFragment()).toMatchSnapshot();
});

it('renders as expected when an acquisition file is provided', async () => {
const testAcquisitionFile = mockAcquisitionFileResponse();
const { getByText } = setup({ acquisitionFile: testAcquisitionFile });
const { getByText } = setup({
acquisitionFile: testAcquisitionFile,
lastUpdatedBy: {
parentId: testAcquisitionFile.id || 0,
appLastUpdateUserid: testAcquisitionFile.appLastUpdateUserid || '',
appLastUpdateUserGuid: testAcquisitionFile.appLastUpdateUserGuid || '',
appLastUpdateTimestamp: testAcquisitionFile.appLastUpdateTimestamp || '',
},
});

expect(getByText('1-12345-01 - Test ACQ File')).toBeVisible();
expect(getByText(prettyFormatUTCDate(testAcquisitionFile.appCreateTimestamp))).toBeVisible();
Expand All @@ -46,15 +60,15 @@ describe('AcquisitionHeader component', () => {

it('renders the file number and name concatenated', async () => {
const testAcquisitionFile = mockAcquisitionFileResponse();
const { getByText } = setup({ acquisitionFile: testAcquisitionFile });
const { getByText } = setup({ acquisitionFile: testAcquisitionFile, lastUpdatedBy: null });

expect(getByText('File:')).toBeVisible();
expect(getByText('1-12345-01 - Test ACQ File')).toBeVisible();
});

it('renders the file Project Number and name concatenated', async () => {
const testAcquisitionFile = mockAcquisitionFileResponse();
const { getByText } = setup({ acquisitionFile: testAcquisitionFile });
const { getByText } = setup({ acquisitionFile: testAcquisitionFile, lastUpdatedBy: null });

expect(getByText('Ministry project:')).toBeVisible();
expect(
Expand All @@ -66,7 +80,7 @@ describe('AcquisitionHeader component', () => {

it('renders the file Product code and description concatenated', async () => {
const testAcquisitionFile = mockAcquisitionFileResponse();
const { getByText } = setup({ acquisitionFile: testAcquisitionFile });
const { getByText } = setup({ acquisitionFile: testAcquisitionFile, lastUpdatedBy: null });

expect(getByText('Ministry product:')).toBeVisible();
expect(getByText('00048 - MISCELLANEOUS CLAIMS')).toBeVisible();
Expand All @@ -81,9 +95,28 @@ describe('AcquisitionHeader component', () => {
productId: null,
projectId: null,
},
lastUpdatedBy: null,
});

expect(getByText('Ministry product:')).toBeVisible();
expect(getByTestId('acq-header-product-val')).toHaveTextContent('');
});

it('renders the last-update-time when provided', async () => {
const testDate = new Date().toISOString();
const testAcquisitionFile = mockAcquisitionFileResponse();
const { getByText } = setup({
acquisitionFile: testAcquisitionFile,
lastUpdatedBy: {
parentId: testAcquisitionFile.id || 0,
appLastUpdateUserid: 'Test User Id',
appLastUpdateUserGuid: 'TEST GUID',
appLastUpdateTimestamp: testDate,
},
});

expect(getByText('1-12345-01 - Test ACQ File')).toBeVisible();
expect(getByText(prettyFormatUTCDate(testAcquisitionFile.appCreateTimestamp))).toBeVisible();
expect(getByText(prettyFormatUTCDate(testDate))).toBeVisible();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ import styled from 'styled-components';
import { HeaderField } from '@/components/common/HeaderField/HeaderField';
import { UserNameTooltip } from '@/components/common/UserNameTooltip';
import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile';
import { Api_LastUpdatedBy } from '@/models/api/File';
import { prettyFormatUTCDate } from '@/utils';

export interface IAcquisitionHeaderProps {
acquisitionFile?: Api_AcquisitionFile;
lastUpdatedBy: Api_LastUpdatedBy | null;
}

export const AcquisitionHeader: React.FunctionComponent<
React.PropsWithChildren<IAcquisitionHeaderProps>
> = ({ acquisitionFile }) => {
> = ({ acquisitionFile, lastUpdatedBy }) => {
const leftColumnWidth = '7';
const leftColumnLabel = '3';

Expand Down Expand Up @@ -71,10 +73,10 @@ export const AcquisitionHeader: React.FunctionComponent<
<Col className="text-right">
<StyleSmallText>
Last updated:{' '}
<strong>{prettyFormatUTCDate(acquisitionFile?.appLastUpdateTimestamp)}</strong> by{' '}
<strong>{prettyFormatUTCDate(lastUpdatedBy?.appLastUpdateTimestamp)}</strong> by{' '}
<UserNameTooltip
userName={acquisitionFile?.appLastUpdateUserid}
userGuid={acquisitionFile?.appLastUpdateUserGuid}
userName={lastUpdatedBy?.appLastUpdateUserid}
userGuid={lastUpdatedBy?.appLastUpdateUserGuid}
/>
</StyleSmallText>
</Col>
Expand Down
Loading