Skip to content

Commit

Permalink
Squashed commit of the following:
Browse files Browse the repository at this point in the history
commit d1c5786
Merge: 44eeeb8 059425c
Author: Ryan Ahearn <[email protected]>
Date:   Tue Feb 9 17:10:20 2021 -0500

    Merge pull request #157 from HHS/user-last-login

    Allow filtering users by last login and access permissions

commit 059425c
Author: Ryan Ahearn <[email protected]>
Date:   Tue Feb 9 15:25:09 2021 -0500

    Make eslint happy

commit 09e906f
Author: Ryan Ahearn <[email protected]>
Date:   Tue Feb 9 14:46:50 2021 -0500

    Refactor user filtering to match access control SOP rules directly

commit facfee4
Author: Ryan Ahearn <[email protected]>
Date:   Tue Feb 9 14:03:29 2021 -0500

    Allow filtering by only showing users who do have SITE_ACCESS

commit b5b93f0
Author: Ryan Ahearn <[email protected]>
Date:   Tue Feb 9 13:40:44 2021 -0500

    Allow filtering by last login > 60 or 180 days ago

    Options to match process here: https://github.com/HHS/Head-Start-TTADP/wiki/Access-Control-&-Account-Management-SOP#account-review-frequency-and-process

commit 1d08788
Author: Ryan Ahearn <[email protected]>
Date:   Tue Feb 9 12:18:39 2021 -0500

    Display lastLogin on admin user details

commit 31883dd
Author: Ryan Ahearn <[email protected]>
Date:   Tue Feb 9 11:12:00 2021 -0500

    Add lastLogin to user api response

commit 60bbefd
Author: Ryan Ahearn <[email protected]>
Date:   Tue Feb 9 10:20:03 2021 -0500

    Add lastLogin value to user model & update on login

commit 1a2dd2c
Author: Ryan Ahearn <[email protected]>
Date:   Tue Feb 9 09:36:09 2021 -0500

    Prevent validation issues if HSES email updates

commit 7554928
Merge: 875f5e2 44eeeb8
Author: Ryan Ahearn <[email protected]>
Date:   Tue Feb 9 14:15:51 2021 -0500

    Merge pull request #286 from adhocteam/main

    Fix cucumber test on CI

commit 875f5e2
Merge: 4bd8566 9f5bb95
Author: Ryan Ahearn <[email protected]>
Date:   Fri Feb 5 16:31:48 2021 -0500

    Merge pull request #284 from adhocteam/main

    Filter locked users from admin list

commit 4bd8566
Merge: 17f9d7f f327246
Author: Ryan Ahearn <[email protected]>
Date:   Fri Feb 5 16:05:29 2021 -0500

    Merge pull request #281 from adhocteam/main

    Case insensitive admin search, testing updates and manager setting of notes and report status

commit 17f9d7f
Merge: 16d54e2 8e5d3ef
Author: Ryan Ahearn <[email protected]>
Date:   Wed Feb 3 16:09:21 2021 -0500

    Merge pull request #279 from adhocteam/main

    add frontend for file upload

commit 16d54e2
Merge: 541640a e27e7df
Author: Ryan Ahearn <[email protected]>
Date:   Tue Feb 2 15:33:25 2021 -0500

    Merge pull request #275 from adhocteam/main

    Add goal component to frontend

commit 541640a
Merge: 790ca05 4c3a436
Author: Ryan Ahearn <[email protected]>
Date:   Fri Jan 29 15:47:13 2021 -0500

    Merge pull request #272 from adhocteam/main

    Save collaborators to report

commit 790ca05
Merge: 4948bd3 3cff2f4
Author: Ryan Ahearn <[email protected]>
Date:   Wed Jan 27 12:04:04 2021 -0500

    Merge pull request #267 from adhocteam/main

    Add file upload api
  • Loading branch information
Chuck McAndrew committed Feb 10, 2021
1 parent fb3478a commit d941b03
Show file tree
Hide file tree
Showing 15 changed files with 203 additions and 38 deletions.
5 changes: 3 additions & 2 deletions docs/logical_data_model.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Logical Data Model
==================

<img src="http://www.plantuml.com/plantuml/png/nLVRRkCs47tNL_2jRO0OHOkYm29Ox6xgBV9GDqYo7p2M9Z9RYiCEHzaQSVwzefaAeYnd6QkNbmGwSpyylBXuvmLOsiOKlWI3wjl5ZpyKnLSA7aXNoZ6j3JPUVTQoKM1hWvKgcJvxTOyeXVzTMmSCZJBQecmTAcBPK56K1hnNdpooUYoKEbDTjZTAMy6wWoukQhJ885YzVTkPDnvzyBqAJbvOsxf3BJGu_7ORihXgcpNo43vJs80sGqYq67oZqBPDo4uDtc2joLuDclZw----KJVBZykRvUfoUQjSkpQwN4H7lz3LFdN96BfyBrsWw0QzGEFsbjPL4vQdlqUFUNA6FniDsgUuBSdXAHcKgj2Nh9reiZqwQvnhv4PxdprIwv4ps0wBtdFD9wwH3pnbXEtv_2TMze-smvRCn3w73NBXGvCThb3BUlZPe6meKJ_7CX-esgQmTsGz7iClgS8UdpaecIdvlIJPEtAwp8_foyIuB6KncPp53bdxjPQG_ghwKio-Y_a2-Pd0v76Wb1UGrW-HaF2UHsxQVsHgRXjWyHj4kHEuDX04jIOo27PMnt7PNcgxxcmPf8SK_nVf35OmKw_pAnsjCxwUJZxLwZXG3klHCmQE11KHbfz0S3GeskeGdKrCa43EHZvXm9IQtBWOTPA6Pt2n8XkxFkK2drVwVIdwGSls1Xsnv9HdzDHokN-39NSGL9KMJHRCYWGJLKMX_tf_3Iowr2umdUo52er-GDx-Sd2pLMr_7LPOwWPCeiOmN2BR7e9-igwWHYjtMuSJT-RuVNYu9nnJpT24-803CsmleZKG8i0roZMvrkmxdV9X14zsmk0IHiRcU2nE6WJYF0UMagpWdv8C03NUIcJdDq-sWngm7SZfIfty63FWvLCKseVjl_oIUmwRB_c16FEs7IHx9QFXT7F5nbzr7CKGsnyF6N6htdasKMdxbsGCh8b1YFyhlbuho10otNx-LJEtxHDkaEUsmmHgjpkdHnMb7f4dEZ_VFGv-L1NnjzuX7gEc36Hw__qJgeXlwAVT7Wez554rlLj_FnVXpm4QKouMAlmVf9o6Oj4-smY9XSFaVOddKL8ivJaaEQxGIDsBBb_Cb_Hxp1Gf79cJjHoPqi_49pcJ8Y_1FcqUF1gE-hn3MxMD-Gi0">
<img src="http://www.plantuml.com/plantuml/png/nLTjR-Gq4FwUN-5lm4bT8H12EbMdkojxg18iLTlx0RF9D6jmF6OywR5g-z-9unB5cspFjn7mfLMUUN_yE9DzwmLOsiOKlWI3wjlLZpyKnLSAxabNoZ5j3JPUVTAoKs1hWvKgcJvvTOSeXVz3MmSCZJBQekmTAcBPKL6K1hnN7psoUYYKUgMwRA-Lje9r1vsVrsYHGR1wywOpxppwu7iPdBomjdM7McXm_EnsP77JDblaCVnAOGFQZ27HOl2rGjis8JirU8sr9divQkBhxx_xHbslFwolrvkBzOroxTReSXKT_KfNVMe3NdwXMjikK7I3Ng1nlQLa3Fs_aucbTTMCvUaVKcA575fU7L0mfBWfoU4f6PIgq9UidMYo0xfed2laHdkVFLBhaJFE8SXXE0K-yVHyu2aZhESpH2qzsJ_R3bip4_iSDiY51qrsV48jw_5ZWxAZv5vmBFEUgdqAUqVMup7yWYeSy8M3afcJtqkIlIMdo_oOeKoCor8Mayavx91rE6i9wI-hpyNqNImNoCy49ey3fRo0j7wCX8HtVE2c_GUcvgO15hz3b5k1AmD1K5iYWs2NTTmF_bBRTMVB83sa-Bz8Px22cNfTNkbedSdYElMjLiU1SbYFdZ5mAgX-z_y4WWU3egscw69YWWGoBl8f0wRKvCQ5g1Crl82B3TdOzOaBV47fTwNeUotxQtJ4abEUqLFBPVzrbkmWg2eT5XUO3Gacgef2_-tk2bXqgLrWEhaB5HfzZxp_TRBPgdPOXnMMkW6Jg34CDyWsWM2Oh2keqShjtk7CpZpySfpU2STKCpGXFk303FlpQ0s4272DSaMkDNsdSpwCuCdE65pAo84vlXPd3G9ndecb92luboG3G8qt4jdvtPFje0Pi1t9wKYT_Z1dmyZ4AxVt-N_wIUmvRBlcH6FEk7IHt9QFXz7B5nh_eTHH3xF1wo8ZR3CxBYKhQlo1ZO4iCGlmVylL5WWmXy-tdhvgbRP-mGPxR3XEenySpUb1HwX5vehEpnuVHZweY_jQRuZ5gpa1cyEy_eOh-htfwxA7GGn5LCxWD-xa8VoPeJBbQgV1_d78MYaM7R0T8B1mc7qeyZv9YASyXoN65HkfUTlbSlgJVOwP8uO4vESi3K_gO-2JlfCXBiC-RXukEnttUegtQnlmD" alt="logical data model diagram">

UML Source
----------
Expand All @@ -21,6 +21,7 @@ class User {
* email : string
title: enum
homeRegionId : integer(32) REFERENCES public.Regions.id
* lastLogin : timestamp
* createdAt : timestamp
* updatedAt : timestamp
}
Expand Down Expand Up @@ -209,7 +210,7 @@ NonGrantee ||-{ ActivityParticipant
Instructions
------------

1. [Edit this diagram with plantuml.com](http://www.plantuml.com/plantuml/png/nLVRRkCs47tNL_2jRO0OHOkYm29Ox6xgBV9GDqYo7p2M9Z9RYiCEHzaQSVwzefaAeYnd6QkNbmGwSpyylBXuvmLOsiOKlWI3wjl5ZpyKnLSA7aXNoZ6j3JPUVTQoKM1hWvKgcJvxTOyeXVzTMmSCZJBQecmTAcBPK56K1hnNdpooUYoKEbDTjZTAMy6wWoukQhJ885YzVTkPDnvzyBqAJbvOsxf3BJGu_7ORihXgcpNo43vJs80sGqYq67oZqBPDo4uDtc2joLuDclZw----KJVBZykRvUfoUQjSkpQwN4H7lz3LFdN96BfyBrsWw0QzGEFsbjPL4vQdlqUFUNA6FniDsgUuBSdXAHcKgj2Nh9reiZqwQvnhv4PxdprIwv4ps0wBtdFD9wwH3pnbXEtv_2TMze-smvRCn3w73NBXGvCThb3BUlZPe6meKJ_7CX-esgQmTsGz7iClgS8UdpaecIdvlIJPEtAwp8_foyIuB6KncPp53bdxjPQG_ghwKio-Y_a2-Pd0v76Wb1UGrW-HaF2UHsxQVsHgRXjWyHj4kHEuDX04jIOo27PMnt7PNcgxxcmPf8SK_nVf35OmKw_pAnsjCxwUJZxLwZXG3klHCmQE11KHbfz0S3GeskeGdKrCa43EHZvXm9IQtBWOTPA6Pt2n8XkxFkK2drVwVIdwGSls1Xsnv9HdzDHokN-39NSGL9KMJHRCYWGJLKMX_tf_3Iowr2umdUo52er-GDx-Sd2pLMr_7LPOwWPCeiOmN2BR7e9-igwWHYjtMuSJT-RuVNYu9nnJpT24-803CsmleZKG8i0roZMvrkmxdV9X14zsmk0IHiRcU2nE6WJYF0UMagpWdv8C03NUIcJdDq-sWngm7SZfIfty63FWvLCKseVjl_oIUmwRB_c16FEs7IHx9QFXT7F5nbzr7CKGsnyF6N6htdasKMdxbsGCh8b1YFyhlbuho10otNx-LJEtxHDkaEUsmmHgjpkdHnMb7f4dEZ_VFGv-L1NnjzuX7gEc36Hw__qJgeXlwAVT7Wez554rlLj_FnVXpm4QKouMAlmVf9o6Oj4-smY9XSFaVOddKL8ivJaaEQxGIDsBBb_Cb_Hxp1Gf79cJjHoPqi_49pcJ8Y_1FcqUF1gE-hn3MxMD-Gi0)
1. [Edit this diagram with plantuml.com](http://www.plantuml.com/plantuml/uml/nLTjR-Gq4FwUN-5lm4bT8H12EbMdkojxg18iLTlx0RF9D6jmF6OywR5g-z-9unB5cspFjn7mfLMUUN_yE9DzwmLOsiOKlWI3wjlLZpyKnLSAxabNoZ5j3JPUVTAoKs1hWvKgcJvvTOSeXVz3MmSCZJBQekmTAcBPKL6K1hnN7psoUYYKUgMwRA-Lje9r1vsVrsYHGR1wywOpxppwu7iPdBomjdM7McXm_EnsP77JDblaCVnAOGFQZ27HOl2rGjis8JirU8sr9divQkBhxx_xHbslFwolrvkBzOroxTReSXKT_KfNVMe3NdwXMjikK7I3Ng1nlQLa3Fs_aucbTTMCvUaVKcA575fU7L0mfBWfoU4f6PIgq9UidMYo0xfed2laHdkVFLBhaJFE8SXXE0K-yVHyu2aZhESpH2qzsJ_R3bip4_iSDiY51qrsV48jw_5ZWxAZv5vmBFEUgdqAUqVMup7yWYeSy8M3afcJtqkIlIMdo_oOeKoCor8Mayavx91rE6i9wI-hpyNqNImNoCy49ey3fRo0j7wCX8HtVE2c_GUcvgO15hz3b5k1AmD1K5iYWs2NTTmF_bBRTMVB83sa-Bz8Px22cNfTNkbedSdYElMjLiU1SbYFdZ5mAgX-z_y4WWU3egscw69YWWGoBl8f0wRKvCQ5g1Crl82B3TdOzOaBV47fTwNeUotxQtJ4abEUqLFBPVzrbkmWg2eT5XUO3Gacgef2_-tk2bXqgLrWEhaB5HfzZxp_TRBPgdPOXnMMkW6Jg34CDyWsWM2Oh2keqShjtk7CpZpySfpU2STKCpGXFk303FlpQ0s4272DSaMkDNsdSpwCuCdE65pAo84vlXPd3G9ndecb92luboG3G8qt4jdvtPFje0Pi1t9wKYT_Z1dmyZ4AxVt-N_wIUmvRBlcH6FEk7IHt9QFXz7B5nh_eTHH3xF1wo8ZR3CxBYKhQlo1ZO4iCGlmVylL5WWmXy-tdhvgbRP-mGPxR3XEenySpUb1HwX5vehEpnuVHZweY_jQRuZ5gpa1cyEy_eOh-htfwxA7GGn5LCxWD-xa8VoPeJBbQgV1_d78MYaM7R0T8B1mc7qeyZv9YASyXoN65HkfUTlbSlgJVOwP8uO4vESi3K_gO-2JlfCXBiC-RXukEnttUegtQnlmD)
2. Copy and paste the final UML into the UML Source section
3. Update the img src and edit link target to the current values

Expand Down
4 changes: 3 additions & 1 deletion docs/openapi/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ components:
attachmentType:
type: string
description: "Type of attachment. Acceptable values are ATTACHMENT or RESOURCE"
reportId:
reportId:
type: number
description: "id of the Activity report the file is associated with"
file:
Expand Down Expand Up @@ -176,6 +176,8 @@ components:
type: number
title:
type: string
lastLogin:
type: string
permissions:
type: array
items:
Expand Down
8 changes: 1 addition & 7 deletions docs/openapi/paths/user.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,4 @@ get:
application/json:
schema:
type: object
properties:
userId:
type: number
name:
type: string
role:
type: string
$ref: '../index.yaml#/components/schemas/user'
14 changes: 13 additions & 1 deletion frontend/src/pages/Admin/UserInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import {
Label, TextInput, Grid, Fieldset,
} from '@trussworks/react-uswds';
import moment from 'moment';

import RegionDropdown from '../../components/RegionDropdown';
import JobTitleDropdown from '../../components/JobTitleDropdown';
Expand All @@ -12,12 +13,16 @@ import JobTitleDropdown from '../../components/JobTitleDropdown';
* editing of basic user information.
*/
function UserInfo({ user, onUserChange }) {
let { lastLogin } = user;
if (lastLogin && lastLogin !== '') {
lastLogin = moment(lastLogin).format('lll Z');
}
return (
<Fieldset className="margin-bottom-2" legend="User Info">
<Grid row gap>
<Grid col={12}>
<Label htmlFor="input-email-name">Email</Label>
<TextInput disabled id="input-email-name" type="text" name="email" value={user.email || ''} onChange={onUserChange} />
<TextInput readOnly disabled id="input-email-name" type="text" name="email" value={user.email || ''} />
</Grid>
<Grid col={12}>
<Label htmlFor="input-full-name">Full Name</Label>
Expand All @@ -32,6 +37,12 @@ function UserInfo({ user, onUserChange }) {
<JobTitleDropdown id="role" name="role" value={user.role || undefined} onChange={onUserChange} />
</Grid>
</Grid>
<Grid row gap>
<Grid col={12}>
<Label htmlFor="input-last-login">Last Login</Label>
<TextInput readOnly disabled id="input-last-login" type="text" name="lastLogin" value={lastLogin} />
</Grid>
</Grid>
</Fieldset>
);
}
Expand All @@ -44,6 +55,7 @@ UserInfo.propTypes = {
role: PropTypes.string,
hsesUserId: PropTypes.string,
phoneNumber: PropTypes.string,
lastLogin: PropTypes.string,
}).isRequired,
onUserChange: PropTypes.func.isRequired,
};
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/pages/Admin/__tests__/UserInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ describe('UserInfo', () => {
test('has the default jobTitle', () => {
expect(screen.getByLabelText('Role')).toHaveValue('default');
});

test('has a blank last login', async () => {
expect(screen.getByLabelText('Last Login')).toHaveValue('');
});
});

describe('with a full user object', () => {
Expand All @@ -34,6 +38,7 @@ describe('UserInfo', () => {
name: 'first last',
homeRegionId: 1,
role: 'Grantee Specialist',
lastLogin: '2021-02-09T11:15:00Z',
};

render(<UserInfo user={user} onUserChange={() => {}} />);
Expand All @@ -54,5 +59,9 @@ describe('UserInfo', () => {
test('has correct jobTitle', () => {
expect(screen.getByLabelText('Role')).toHaveValue('Grantee Specialist');
});

test('has correct lastLogin', () => {
expect(screen.getByLabelText('Last Login')).toHaveValue('Feb 9, 2021 11:15 AM +00:00');
});
});
});
39 changes: 34 additions & 5 deletions frontend/src/pages/Admin/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import userEvent from '@testing-library/user-event';
import { createMemoryHistory } from 'history';
import fetchMock from 'fetch-mock';
import join from 'url-join';
import moment from 'moment';

import Admin from '../index';
import { SCOPE_IDS } from '../../../Constants';
Expand All @@ -34,20 +35,39 @@ describe('Admin Page', () => {
name: undefined,
homeRegionId: 1,
role: 'Grantee Specialist',
permissions: [],
lastLogin: moment().subtract(65, 'days').toISOString(),
permissions: [{
userId: 2,
scopeId: SCOPE_IDS.SITE_ACCESS,
regionId: 14,
}],
},
{
id: 3,
email: '[email protected]',
name: 'Harry Potter',
homeRegionId: 1,
role: 'Grantee Specialist',
lastLogin: moment().toISOString(),
permissions: [{
userId: 3,
scopeId: SCOPE_IDS.SITE_ACCESS,
regionId: 14,
}],
},
{
id: 4,
email: '[email protected]',
name: 'Hermione Granger',
homeRegionId: 1,
role: 'Early Childhood Specialist',
lastLogin: moment().subtract(190, 'days').toISOString(),
permissions: [{
userId: 4,
scopeId: SCOPE_IDS.READ_ACTIVITY_REPORTS,
regionId: 1,
}],
},
];

beforeEach(() => {
Expand All @@ -73,7 +93,7 @@ describe('Admin Page', () => {
userEvent.type(filter, '@hogwarts.com');
const sideNav = screen.getByTestId('sidenav');
const links = within(sideNav).getAllByRole('link');
expect(links.length).toBe(2);
expect(links.length).toBe(3);
});

it('user filtering is case-insentive', async () => {
Expand All @@ -85,15 +105,24 @@ describe('Admin Page', () => {
expect(links[0]).toHaveTextContent('Harry Potter');
});

it('user list is filterable by SITE_ACCESS permission', async () => {
const checkbox = await screen.findByRole('checkbox', { name: 'Show Only Locked Users' });
userEvent.click(checkbox);
it('user list is filterable by users to lock', async () => {
const radio = await screen.findByRole('radio', { name: 'Show users to lock' });
userEvent.click(radio);
const sideNav = screen.getByTestId('sidenav');
const links = within(sideNav).getAllByRole('link');
expect(links.length).toBe(1);
expect(links[0]).toHaveTextContent('[email protected]');
});

it('user list is filterable by users to disable', async () => {
const radio = await screen.findByRole('radio', { name: 'Show users to disable' });
userEvent.click(radio);
const sideNav = screen.getByTestId('sidenav');
const links = within(sideNav).getAllByRole('link');
expect(links.length).toBe(1);
expect(links[0]).toHaveTextContent('Hermione Granger');
});

it('allows a user to be selected', async () => {
const button = await screen.findByText('Harry Potter');
userEvent.click(button);
Expand Down
60 changes: 50 additions & 10 deletions frontend/src/pages/Admin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import ReactRouterPropTypes from 'react-router-prop-types';
import _ from 'lodash';
import { Helmet } from 'react-helmet';
import {
Label, TextInput, Grid, SideNav, Alert, Checkbox,
Label, TextInput, Grid, SideNav, Alert, Radio, Fieldset,
} from '@trussworks/react-uswds';
import moment from 'moment';
import UserSection from './UserSection';
import NavLink from '../../components/NavLink';
import Container from '../../components/Container';
Expand Down Expand Up @@ -73,14 +74,29 @@ function Admin(props) {
}
}, [userId, users]);

const permissionsIncludesAccess = (permissions) => (
_.some(permissions, (perm) => (perm.scopeId === SCOPE_IDS.SITE_ACCESS))
);

// rules for to-lock and to-disable filters are laid out in Access Control SOP:
// https://github.com/HHS/Head-Start-TTADP/wiki/Access-Control-&-Account-Management-SOP#account-review-frequency-and-process
const lockThreshold = moment().subtract(60, 'days');
const disableThreshold = moment().subtract(180, 'days');

const filteredUsers = useMemo(() => (
_.filter(users, (u) => {
const { email, name, permissions } = u;
const lastLogin = moment(u.lastLogin);
const userMatchesFilter = `${email}${name}`.toLowerCase().includes(userSearch.toLowerCase());
let userMatchesLockFilter = true;
if (lockedFilter) {
userMatchesLockFilter = !_.some(permissions,
(perm) => (perm.scopeId === SCOPE_IDS.SITE_ACCESS));
if (lockedFilter === 'recent') {
userMatchesLockFilter = lastLogin.isAfter(lockThreshold)
&& !permissionsIncludesAccess(permissions);
} else if (lockedFilter === 'to-lock') {
userMatchesLockFilter = lastLogin.isBefore(lockThreshold)
&& permissionsIncludesAccess(permissions);
} else if (lockedFilter === 'to-disable') {
userMatchesLockFilter = lastLogin.isBefore(disableThreshold) && permissions.length > 0;
}
return userMatchesFilter && userMatchesLockFilter;
})
Expand Down Expand Up @@ -125,12 +141,36 @@ function Admin(props) {
<h1 className="text-center">User Administration</h1>
<Grid row gap>
<Grid col={4}>
<Checkbox
label="Show Only Locked Users"
id="show-locked-users"
checked={lockedFilter}
onChange={() => updateLockedFilter(!lockedFilter)}
/>
<Fieldset className="smart-hub--report-legend" legend="Access Control Filtering">
<Radio
label="Show all users"
id="access-control-filter-all"
name="lock-filter"
checked={lockedFilter === false}
onChange={() => updateLockedFilter(false)}
/>
<Radio
label="Show recent locked logins"
id="access-control-filter-recent"
name="lock-filter"
checked={lockedFilter === 'recent'}
onChange={() => updateLockedFilter('recent')}
/>
<Radio
label="Show users to lock"
id="access-control-filter-lock"
name="lock-filter"
checked={lockedFilter === 'to-lock'}
onChange={() => updateLockedFilter('to-lock')}
/>
<Radio
label="Show users to disable"
id="access-control-filter-disable"
name="lock-filter"
checked={lockedFilter === 'to-disable'}
onChange={() => updateLockedFilter('to-disable')}
/>
</Fieldset>
<Label htmlFor="input-filter-users">Filter Users</Label>
<TextInput value={userSearch} onChange={onUserSearchChange} id="input-filter-users" name="input-filter-users" type="text" />
<div className="overflow-y-scroll maxh-tablet-lg margin-top-3">
Expand Down
17 changes: 17 additions & 0 deletions src/migrations/20210208212205-user-last-login.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn(
'Users',
'lastLogin',
{
defaultValue: Sequelize.fn('NOW'),
allowNull: false,
type: Sequelize.DATE,
},
);
},

down: async (queryInterface) => {
await queryInterface.removeColumn('Users', 'lastLogin');
},
};
1 change: 1 addition & 0 deletions src/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export default (sequelize, DataTypes) => {
},
},
role: DataTypes.ENUM(roles),
lastLogin: DataTypes.DATE,
}, {
sequelize,
modelName: 'User',
Expand Down
15 changes: 6 additions & 9 deletions src/routes/admin/user.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
User, Permission, sequelize,
} from '../../models';
import { userById } from '../../services/users';
import { userById, userAttributes } from '../../services/users';
import handleErrors from '../../lib/apiErrorHandler';
import { auditLogger } from '../../logger';

Expand Down Expand Up @@ -36,7 +36,7 @@ export async function getUser(req, res) {
export default async function getUsers(req, res) {
try {
const users = await User.findAll({
attributes: ['id', 'name', 'role', 'hsesUserId', 'email', 'phoneNumber', 'homeRegionId'],
attributes: userAttributes,
include: [
{ model: Permission, as: 'permissions', attributes: ['userId', 'scopeId', 'regionId'] },
],
Expand Down Expand Up @@ -81,17 +81,14 @@ export async function updateUser(req, res) {
const { userId } = req.params;

try {
await sequelize.transaction(async (t) => {
await sequelize.transaction(async (transaction) => {
await User.update(requestUser,
{
include: [{ model: Permission, as: 'permissions', attributes: ['userId', 'scopeId', 'regionId'] }],
where: { id: userId },
}, { transaction: t });

await Permission.destroy({ where: { userId } },
{ transaction: t });
await Permission.bulkCreate(requestUser.permissions,
{ transaction: t });
}, { transaction });
await Permission.destroy({ where: { userId } }, { transaction });
await Permission.bulkCreate(requestUser.permissions, { transaction });
});
auditLogger.warn(`User ${req.session.userId} updated User: ${userId} and set permissions: ${JSON.stringify(requestUser.permissions)}`);
const user = await userById(userId);
Expand Down
1 change: 1 addition & 0 deletions src/routes/admin/user.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const mockUser = {
hsesUserId: '49',
email: '[email protected]',
homeRegionId: 1,
lastLogin: new Date('2021-02-09T15:13:00.000Z'),
permissions: [
{
userId: 49,
Expand Down
Loading

0 comments on commit d941b03

Please sign in to comment.