Skip to content

[ERA-9830] wiring up new back-end permissions for data export functionality #1141

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

Merged
merged 1 commit into from
Jul 29, 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
91 changes: 59 additions & 32 deletions src/GlobalMenuDrawer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ const GlobalMenuDrawer = () => {
const { t } = useTranslation('menu-drawer', { keyPrefix: 'globalMenuDrawer' });

const hasPatrolViewPermissions = usePermissions(PERMISSION_KEYS.PATROLS, PERMISSIONS.READ);
const hasObservationExportPermissions = usePermissions(PERMISSION_KEYS.OBSERVATIONS_EXPORT, PERMISSIONS.READ);
const hasEventExportPermissions = usePermissions(PERMISSION_KEYS.EVENTS_EXPORT, PERMISSIONS.READ);

const isMediumLayoutOrLarger = useMatchMedia(BREAKPOINTS.screenIsMediumLayoutOrLarger);

Expand All @@ -79,37 +81,62 @@ const GlobalMenuDrawer = () => {
const token = useSelector((state) => state.data.token);
const user = useSelector((state) => state.data.user);

const modals = useMemo(() => [
...(dailyReportEnabled ? [{
title: t('dailyReportModal.title'),
content: DailyReportModal,
modalProps: { className: 'daily-report-modal' },
}] : []),
{
title: t('fieldReportsModal.title'),
content: DataExportModal,
url: 'activity/events/export',
paramString: calcEventFilterForRequest(),
children: <div>{t('fieldReportsModal.content')}</div>
},
...(kmlExportEnabled ? [{
title: t('masterKMLModal.title'),
content: KMLExportModal,
url: 'subjects/kml/root',
modalProps: { className: 'kml-export-modal' },
}] : []),
{
title: t('subjectInformationModal.title'),
content: DataExportModal,
url: 'trackingmetadata/export',
},
{
title: t('subjectReportsModal.title'),
content: DataExportModal,
url: 'trackingdata/export',
},
// eslint-disable-next-line react-hooks/exhaustive-deps
], [dailyReportEnabled, eventFilter, eventTypes, kmlExportEnabled, t]);
const modals = useMemo(() => {
const modals = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Funky new line haha

];

if (dailyReportEnabled) {
modals.push(
{
title: t('dailyReportModal.title'),
content: DailyReportModal,
modalProps: { className: 'daily-report-modal' },
}
);
}

if (kmlExportEnabled) {
modals.push(
{
title: t('masterKMLModal.title'),
content: KMLExportModal,
url: 'subjects/kml/root',
modalProps: { className: 'kml-export-modal' },
}
);
}

if (hasObservationExportPermissions) {
modals.push(...[
{
title: t('subjectInformationModal.title'),
content: DataExportModal,
url: 'trackingmetadata/export',
},
{
title: t('subjectReportsModal.title'),
content: DataExportModal,
url: 'trackingdata/export',
}
]);
}

if (hasEventExportPermissions) {
modals.push(
{
title: t('fieldReportsModal.title'),
content: DataExportModal,
url: 'activity/events/export',
paramString: calcEventFilterForRequest(),
children: <div>{t('fieldReportsModal.content')}</div>
}
);
}

return modals;
/* eslint-disable-next-line react-hooks/exhaustive-deps */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for this eslint-disable?

Copy link
Collaborator Author

@JoshuaVulcan JoshuaVulcan Jul 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes -- I'm preserving this from the old code actually. There's a bit of a break in the paradigm of store-driven state changes here, so we pass eventFilter in the dependency array to ensure the value returned by calcEventFilterForRequest always matches the current state of the event filter.

}, [eventFilter, hasEventExportPermissions, hasObservationExportPermissions, dailyReportEnabled, kmlExportEnabled, t]);

const showPatrols = !!patrolFlagEnabled && !!hasPatrolViewPermissions;

const onModalClick = useCallback((modal, analyticsTitle = REPORT_EXPORT_CATEGORY) => {
Expand Down Expand Up @@ -140,7 +167,7 @@ const GlobalMenuDrawer = () => {
if (siteInput) {
siteInput.value = window.location.hostname;
}
const username = (selectedUserProfile?.id ? selectedUserProfile: user)?.username;
const username = (selectedUserProfile?.id ? selectedUserProfile : user)?.username;
const userInput = selectSupportFormFieldByLabelText('ER Requestor Name');

if (userInput) {
Expand Down
127 changes: 92 additions & 35 deletions src/GlobalMenuDrawer/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ jest.mock('../hooks/useNavigate', () => jest.fn());
describe('GlobalMenuDrawer', () => {
let addModalMock, fetchTableauDashboardMock, hideDrawerMock, navigate, store, useMatchMediaMock, useNavigateMock;
beforeEach(() => {
addModalMock = jest.fn(() => () => {});
addModalMock = jest.fn(() => () => { });
addModal.mockImplementation(addModalMock);
fetchTableauDashboardMock = jest.fn(() => () => Promise.resolve({ display_url: 'tableau url ' }));
fetchTableauDashboard.mockImplementation(fetchTableauDashboardMock);
hideDrawerMock = jest.fn(() => () => {});
hideDrawerMock = jest.fn(() => () => { });
hideDrawer.mockImplementation(hideDrawerMock);
useMatchMediaMock = jest.fn(() => true);
useMatchMedia.mockImplementation(useMatchMediaMock);
Expand All @@ -59,6 +59,8 @@ describe('GlobalMenuDrawer', () => {
user: {
permissions: {
[PERMISSION_KEYS.PATROLS]: [PERMISSIONS.READ],
[PERMISSION_KEYS.EVENTS_EXPORT]: [PERMISSIONS.READ],
[PERMISSION_KEYS.OBSERVATIONS_EXPORT]: [PERMISSIONS.READ],
}
},
},
Expand Down Expand Up @@ -320,22 +322,41 @@ describe('GlobalMenuDrawer', () => {
expect(addModal.mock.calls[0][0].title).toBe('Daily Report');
});

test('opens the field reports modal when clicking the Field Reports button', async () => {
render(
<Provider store={mockStore(store)}>
<GlobalMenuDrawer />
</Provider>
);
describe('exporting field reports', () => {
const getFieldEventsButton = () => screen.queryByText('Field Events');

expect(addModal).toHaveBeenCalledTimes(0);
test('does not show the Field Reports button if a user doesn\'t have export event data permissions', () => {
delete store.data.user.permissions[PERMISSION_KEYS.EVENTS_EXPORT];

const fieldReportsButton = await screen.findByText('Field Events');
userEvent.click(fieldReportsButton);
render(
<Provider store={mockStore(store)}>
<GlobalMenuDrawer />
</Provider>
);

expect(addModal).toHaveBeenCalledTimes(1);
expect(addModal.mock.calls[0][0].title).toBe('Field Events');
const fieldEventsButton = getFieldEventsButton();

expect(fieldEventsButton).toBeNull();
});

test('opens the field reports modal when clicking the Field Reports button', () => {
render(
<Provider store={mockStore(store)}>
<GlobalMenuDrawer />
</Provider>
);

expect(addModal).toHaveBeenCalledTimes(0);

const fieldEventsButton = getFieldEventsButton();
userEvent.click(fieldEventsButton);

expect(addModal).toHaveBeenCalledTimes(1);
expect(addModal.mock.calls[0][0].title).toBe('Field Events');
});
});


test('opens the kml export modal when clicking the Master KML button', async () => {
render(
<Provider store={mockStore(store)}>
Expand All @@ -352,38 +373,74 @@ describe('GlobalMenuDrawer', () => {
expect(addModal.mock.calls[0][0].title).toBe('Subject KML');
});

test('opens the subject information modal when clicking the Subject Information button', async () => {
render(
<Provider store={mockStore(store)}>
<GlobalMenuDrawer />
</Provider>
);

expect(addModal).toHaveBeenCalledTimes(0);
describe('exporting subject information', () => {
const getSubjectInfoButton = () => screen.queryByText('Subject Summary');

const subjectInformationButton = await screen.findByText('Subject Summary');
userEvent.click(subjectInformationButton);
test('does not show the subject information link if a user doesn\'t have export observation data permissions', () => {
delete store.data.user.permissions[PERMISSION_KEYS.OBSERVATIONS_EXPORT];

expect(addModal).toHaveBeenCalledTimes(1);
expect(addModal.mock.calls[0][0].title).toBe('Subject Summary');
render(
<Provider store={mockStore(store)}>
<GlobalMenuDrawer />
</Provider>
);

const subjectInfoButton = getSubjectInfoButton();
expect(subjectInfoButton).toBeNull();
});

test('opens the subject information modal when clicking the Subject Information button', () => {
render(
<Provider store={mockStore(store)}>
<GlobalMenuDrawer />
</Provider>
);

expect(addModal).toHaveBeenCalledTimes(0);

const subjectInfoButton = getSubjectInfoButton();
userEvent.click(subjectInfoButton);

expect(addModal).toHaveBeenCalledTimes(1);
expect(addModal.mock.calls[0][0].title).toBe('Subject Summary');
});
});

test('opens the subject reports modal when clicking the Subject Reports button', async () => {
render(
<Provider store={mockStore(store)}>
<GlobalMenuDrawer />
</Provider>
);
describe('exporting subject reports', () => {
const getSubjectReportsButton = () => screen.queryByText('Observations');

expect(addModal).toHaveBeenCalledTimes(0);
test('does not show the subject reports link if a user doesn\'t have export observation data permissions', () => {
delete store.data.user.permissions[PERMISSION_KEYS.OBSERVATIONS_EXPORT];

const subjectReportsButton = await screen.findByText('Observations');
userEvent.click(subjectReportsButton);
render(
<Provider store={mockStore(store)}>
<GlobalMenuDrawer />
</Provider>
);

expect(addModal).toHaveBeenCalledTimes(1);
expect(addModal.mock.calls[0][0].title).toBe('Observations');
const subjectReportsButton = getSubjectReportsButton();
expect(subjectReportsButton).toBeNull();
});

test('opens the subject reports modal when clicking the Subject Reports button', () => {
render(
<Provider store={mockStore(store)}>
<GlobalMenuDrawer />
</Provider>
);

expect(addModal).toHaveBeenCalledTimes(0);

const subjectReportsButton = getSubjectReportsButton();
userEvent.click(subjectReportsButton);

expect(addModal).toHaveBeenCalledTimes(1);
expect(addModal.mock.calls[0][0].title).toBe('Observations');
});
});


test('lists links to various privacy and data policies', async () => {
render(
<Provider store={mockStore(store)}>
Expand Down
2 changes: 2 additions & 0 deletions src/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,8 @@ export const PERMISSION_KEYS = {
PATROLS: 'patrol',
PATROL_TYPES: 'patroltype',
MESSAGING: 'message',
OBSERVATIONS_EXPORT: 'export_observation_data',
EVENTS_EXPORT: 'export_event_data',
};


Expand Down