From 5f9e9fa99026c2af1a393f380016da8868f9fea0 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Thu, 22 Aug 2024 17:23:16 -0700 Subject: [PATCH 1/4] update access request table & view actioned requests --- api/src/paths/administrative-activities.ts | 10 + .../administrative-activity-repository.ts | 16 +- app/src/constants/colours.ts | 16 + app/src/features/admin/AdminUsersRouter.tsx | 2 +- .../admin/users/AccessRequestList.test.tsx | 291 ------------------ .../features/admin/users/ManageUsersPage.tsx | 18 +- .../AccessRequestContainer.tsx | 125 ++++++++ .../ReviewAccessRequestForm.test.tsx | 12 +- .../components}/ReviewAccessRequestForm.tsx | 7 +- .../components/ViewAccessRequestForm.tsx | 93 ++++++ .../actioned/AccessRequestActionedList.tsx | 122 ++++++++ .../pending/AccessRequestPendingList.tsx} | 204 ++++++------ .../rejected/AccessRequestRejectedList.tsx | 122 ++++++++ .../{ => active}/ActiveUsersList.test.tsx | 0 .../users/{ => active}/ActiveUsersList.tsx | 45 ++- .../users/{ => add}/AddSystemUsersForm.tsx | 0 .../{ => projects}/UsersDetailHeader.test.tsx | 2 +- .../{ => projects}/UsersDetailHeader.tsx | 18 +- .../{ => projects}/UsersDetailPage.test.tsx | 6 +- .../users/{ => projects}/UsersDetailPage.tsx | 4 +- .../UsersDetailProjects.test.tsx | 4 +- .../{ => projects}/UsersDetailProjects.tsx | 20 +- app/src/interfaces/useAdminApi.interface.ts | 2 + app/src/themes/appTheme.ts | 3 +- .../seeds/03_basic_project_survey_setup.ts | 41 +++ 25 files changed, 727 insertions(+), 456 deletions(-) delete mode 100644 app/src/features/admin/users/AccessRequestList.test.tsx create mode 100644 app/src/features/admin/users/access-requests/AccessRequestContainer.tsx rename app/src/features/admin/users/{ => access-requests/components}/ReviewAccessRequestForm.test.tsx (95%) rename app/src/features/admin/users/{ => access-requests/components}/ReviewAccessRequestForm.tsx (95%) create mode 100644 app/src/features/admin/users/access-requests/components/ViewAccessRequestForm.tsx create mode 100644 app/src/features/admin/users/access-requests/list/actioned/AccessRequestActionedList.tsx rename app/src/features/admin/users/{AccessRequestList.tsx => access-requests/list/pending/AccessRequestPendingList.tsx} (70%) create mode 100644 app/src/features/admin/users/access-requests/list/rejected/AccessRequestRejectedList.tsx rename app/src/features/admin/users/{ => active}/ActiveUsersList.test.tsx (100%) rename app/src/features/admin/users/{ => active}/ActiveUsersList.tsx (92%) rename app/src/features/admin/users/{ => add}/AddSystemUsersForm.tsx (100%) rename app/src/features/admin/users/{ => projects}/UsersDetailHeader.test.tsx (98%) rename app/src/features/admin/users/{ => projects}/UsersDetailHeader.tsx (88%) rename app/src/features/admin/users/{ => projects}/UsersDetailPage.test.tsx (91%) rename app/src/features/admin/users/{ => projects}/UsersDetailPage.tsx (90%) rename app/src/features/admin/users/{ => projects}/UsersDetailProjects.test.tsx (98%) rename app/src/features/admin/users/{ => projects}/UsersDetailProjects.tsx (93%) diff --git a/api/src/paths/administrative-activities.ts b/api/src/paths/administrative-activities.ts index 81d8d9b58d..31b7c2fbd1 100644 --- a/api/src/paths/administrative-activities.ts +++ b/api/src/paths/administrative-activities.ts @@ -108,6 +108,16 @@ GET.apiDoc = { create_date: { type: 'string', description: 'ISO 8601 date string' + }, + updated_by: { + type: 'string', + description: 'Display name of the user who last updated the record', + nullable: true + }, + update_date: { + type: 'string', + description: 'Date when the record was last updated', + nullable: true } } } diff --git a/api/src/repositories/administrative-activity-repository.ts b/api/src/repositories/administrative-activity-repository.ts index 44f02a3a62..69a09b41b0 100644 --- a/api/src/repositories/administrative-activity-repository.ts +++ b/api/src/repositories/administrative-activity-repository.ts @@ -24,7 +24,9 @@ export const IAdministrativeActivity = z.object({ description: z.string().nullable(), data: shallowJsonSchema, notes: z.string().nullable(), - create_date: z.string() + create_date: z.string(), + updated_by: z.string().nullable(), + update_date: z.string().nullable() }); export type IAdministrativeActivity = z.infer; @@ -66,7 +68,9 @@ export class AdministrativeActivityRepository extends BaseRepository { aa.description, aa.data, aa.notes, - aa.create_date + aa.create_date, + su.display_name as updated_by, + aa.update_date FROM administrative_activity aa LEFT OUTER JOIN @@ -77,6 +81,10 @@ export class AdministrativeActivityRepository extends BaseRepository { administrative_activity_type aat ON aa.administrative_activity_type_id = aat.administrative_activity_type_id + LEFT OUTER JOIN + system_user su + ON + su.system_user_id = aa.update_user WHERE 1 = 1 `; @@ -115,7 +123,9 @@ export class AdministrativeActivityRepository extends BaseRepository { sqlStatement.append(SQL`)`); } - sqlStatement.append(`;`); + sqlStatement.append(` + ORDER BY create_date DESC; + `); const response = await this.connection.sql(sqlStatement, IAdministrativeActivity); return response.rows; diff --git a/app/src/constants/colours.ts b/app/src/constants/colours.ts index f74fb8b408..caf3ddb47a 100644 --- a/app/src/constants/colours.ts +++ b/app/src/constants/colours.ts @@ -65,6 +65,16 @@ const NRM_REGION_COLOUR_MAP = { 'Skeena Natural Resource Region': { colour: red } }; +/** + * Colour map for access request chips. + * + */ +const ACCESS_REQUEST_COLOUR_MAP = { + Pending: { colour: purple }, + Actioned: { colour: green }, + Rejected: { colour: red } +}; + /** * ColourMap key types * @@ -85,6 +95,12 @@ const generateColourMapGetter = (colourMap: T, fallbackColo return (lookup: keyof T) => colourMap[lookup]?.colour ?? fallbackColour; }; +/** + * Get survey progress colour mapping. + * + */ +export const getAccessRequestColour = generateColourMapGetter(ACCESS_REQUEST_COLOUR_MAP); + /** * Get survey progress colour mapping. * diff --git a/app/src/features/admin/AdminUsersRouter.tsx b/app/src/features/admin/AdminUsersRouter.tsx index a9ae56b0d5..29daa60b0d 100644 --- a/app/src/features/admin/AdminUsersRouter.tsx +++ b/app/src/features/admin/AdminUsersRouter.tsx @@ -3,7 +3,7 @@ import { Redirect, Route, Switch } from 'react-router'; import RouteWithTitle from 'utils/RouteWithTitle'; import { getTitle } from 'utils/Utils'; import ManageUsersPage from './users/ManageUsersPage'; -import UsersDetailPage from './users/UsersDetailPage'; +import UsersDetailPage from './users/projects/UsersDetailPage'; /** * Router for all `/admin/users/*` pages. diff --git a/app/src/features/admin/users/AccessRequestList.test.tsx b/app/src/features/admin/users/AccessRequestList.test.tsx deleted file mode 100644 index 98af5b1f3e..0000000000 --- a/app/src/features/admin/users/AccessRequestList.test.tsx +++ /dev/null @@ -1,291 +0,0 @@ -import { SYSTEM_IDENTITY_SOURCE } from 'constants/auth'; -import { AdministrativeActivityStatusType } from 'constants/misc'; -import AccessRequestList from 'features/admin/users/AccessRequestList'; -import { useBiohubApi } from 'hooks/useBioHubApi'; -import { IAccessRequestDataObject, IGetAccessRequestsListResponse } from 'interfaces/useAdminApi.interface'; -import { IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface'; -import { codes } from 'test-helpers/code-helpers'; -import { cleanup, fireEvent, render, waitFor } from 'test-helpers/test-utils'; - -jest.mock('../../../hooks/useBioHubApi'); -const mockBiohubApi = useBiohubApi as jest.Mock; - -const mockUseApi = { - admin: { - approveAccessRequest: jest.fn(), - denyAccessRequest: jest.fn() - } -}; - -// Mock the DataGrid component to disable virtualization -jest.mock('@mui/x-data-grid', () => { - const OriginalModule = jest.requireActual('@mui/x-data-grid'); - - // Wrap the DataGrid component to add disableVirtualization prop - const MockedDataGrid = (props: any) => ; - - // Return the original module with the mocked DataGrid - return { - ...OriginalModule, - DataGrid: MockedDataGrid - }; -}); - -const renderContainer = ( - accessRequests: IGetAccessRequestsListResponse[], - codes: IGetAllCodeSetsResponse, - refresh: () => void -) => { - return render(); -}; - -describe('AccessRequestList', () => { - beforeEach(() => { - mockBiohubApi.mockImplementation(() => mockUseApi); - mockUseApi.admin.approveAccessRequest.mockClear(); - mockUseApi.admin.denyAccessRequest.mockClear(); - }); - - afterEach(() => { - cleanup(); - }); - - it('shows `No Access Requests` when there are no access requests', async () => { - const { getByText } = renderContainer([], codes, () => {}); - - await waitFor(() => { - expect(getByText('No Access Requests')).toBeVisible(); - }); - }); - - it('shows a table row for a pending access request', async () => { - const { getByText } = await renderContainer( - [ - { - id: 1, - type: 1, - type_name: 'test type', - status: 1, - status_name: AdministrativeActivityStatusType.PENDING, - description: 'test description', - notes: 'test notes', - data: { - name: 'test user', - username: 'testusername', - userGuid: 'aaaa', - email: 'email@email.com', - identitySource: SYSTEM_IDENTITY_SOURCE.IDIR, - displayName: 'test user', - company: 'test company', - reason: 'my reason' - }, - create_date: '2020-04-20' - } - ], - codes, - () => {} - ); - - await waitFor(() => { - expect(getByText('testusername')).toBeVisible(); - expect(getByText('Apr 20, 2020')).toBeVisible(); - expect(getByText('Review Request')).toBeVisible(); - }); - }); - - it('shows a table row for a rejected access request', async () => { - const { getByText, queryByText } = renderContainer( - [ - { - id: 1, - type: 1, - type_name: 'test type', - status: 1, - status_name: AdministrativeActivityStatusType.REJECTED, - description: 'test description', - notes: 'test notes', - data: { - name: 'test user', - username: 'testusername', - userGuid: 'aaaa', - email: 'email@email.com', - identitySource: SYSTEM_IDENTITY_SOURCE.IDIR, - displayName: 'test user', - company: 'test company', - reason: 'my reason' - }, - create_date: '2020-04-20' - } - ], - codes, - () => {} - ); - - await waitFor(() => { - expect(getByText('testusername')).toBeVisible(); - expect(getByText('Apr 20, 2020')).toBeVisible(); - expect(getByText('Denied')).toBeVisible(); - expect(queryByText('Review Request')).not.toBeInTheDocument(); - }); - }); - - it('shows a table row for a actioned access request', async () => { - const { getByText, queryByText } = renderContainer( - [ - { - id: 1, - type: 1, - type_name: 'test type', - status: 1, - status_name: AdministrativeActivityStatusType.ACTIONED, - description: 'test description', - notes: 'test notes', - data: { - name: 'test user', - username: 'testusername', - userGuid: 'aaaa', - email: 'email@email.com', - identitySource: SYSTEM_IDENTITY_SOURCE.IDIR, - displayName: 'test user', - company: 'test company', - reason: 'my reason' - }, - create_date: '2020-04-20' - } - ], - codes, - () => {} - ); - - await waitFor(() => { - expect(getByText('testusername')).toBeVisible(); - expect(getByText('Apr 20, 2020')).toBeVisible(); - expect(getByText('Approved')).toBeVisible(); - expect(queryByText('Review Request')).not.toBeInTheDocument(); - }); - }); - - it('shows a table row when the data object is null', async () => { - const { getByText } = renderContainer( - [ - { - id: 1, - type: 1, - type_name: 'test type', - status: 1, - status_name: AdministrativeActivityStatusType.PENDING, - description: 'test description', - notes: 'test notes', - data: null as unknown as IAccessRequestDataObject, - create_date: '2020-04-20' - } - ], - codes, - () => {} - ); - - await waitFor(() => { - expect(getByText('Apr 20, 2020')).toBeVisible(); - expect(getByText('Review Request')).toBeVisible(); - }); - }); - - it('opens the review dialog and calls approveAccessRequest on approval', async () => { - const refresh = jest.fn(); - - const { getByText, getByTestId } = renderContainer( - [ - { - id: 1, - type: 1, - type_name: 'test type', - status: 1, - status_name: AdministrativeActivityStatusType.PENDING, - description: 'test description', - notes: 'test notes', - data: { - name: 'test user', - username: 'testusername', - userGuid: 'aaaa', - email: 'email@email.com', - identitySource: SYSTEM_IDENTITY_SOURCE.IDIR, - displayName: 'test user', - company: 'test company', - reason: 'my reason' - }, - create_date: '2020-04-20' - } - ], - codes, - refresh - ); - - const reviewButton = getByText('Review Request'); - fireEvent.click(reviewButton); - - await waitFor(() => { - // wait for dialog to open - expect(getByText('Review Access Request')).toBeVisible(); - fireEvent.click(getByTestId('request_approve_button')); - }); - - await waitFor(() => { - expect(refresh).toHaveBeenCalledTimes(1); - expect(mockUseApi.admin.approveAccessRequest).toHaveBeenCalledTimes(1); - expect(mockUseApi.admin.approveAccessRequest).toHaveBeenCalledWith(1, { - displayName: 'test user', - email: 'email@email.com', - identitySource: 'IDIR', - roleIds: [], - userGuid: 'aaaa', - userIdentifier: 'testusername' - }); - }); - }); - - it('opens the review dialog and calls denyAccessRequest on denial', async () => { - const refresh = jest.fn(); - - const { getByText, getByTestId } = renderContainer( - [ - { - id: 1, - type: 1, - type_name: 'test type', - status: 1, - status_name: AdministrativeActivityStatusType.PENDING, - description: 'test description', - notes: 'test notes', - data: { - name: 'test user', - username: 'testusername', - userGuid: 'aaaa', - email: 'email@email.com', - identitySource: SYSTEM_IDENTITY_SOURCE.IDIR, - displayName: 'test user', - company: 'test company', - reason: 'my reason' - }, - create_date: '2020-04-20' - } - ], - codes, - refresh - ); - - const reviewButton = getByText('Review Request'); - fireEvent.click(reviewButton); - - await waitFor(() => { - // wait for dialog to open - expect(getByText('Review Access Request')).toBeVisible(); - fireEvent.click(getByTestId('request_deny_button')); - }); - - await waitFor(() => { - expect(refresh).toHaveBeenCalledTimes(1); - expect(mockUseApi.admin.denyAccessRequest).toHaveBeenCalledTimes(1); - expect(mockUseApi.admin.denyAccessRequest).toHaveBeenCalledWith(1); - }); - }); -}); diff --git a/app/src/features/admin/users/ManageUsersPage.tsx b/app/src/features/admin/users/ManageUsersPage.tsx index 69afec4863..9c11f7e044 100644 --- a/app/src/features/admin/users/ManageUsersPage.tsx +++ b/app/src/features/admin/users/ManageUsersPage.tsx @@ -3,13 +3,13 @@ import CircularProgress from '@mui/material/CircularProgress'; import Container from '@mui/material/Container'; import PageHeader from 'components/layout/PageHeader'; import { AdministrativeActivityStatusType, AdministrativeActivityType } from 'constants/misc'; -import AccessRequestList from 'features/admin/users/AccessRequestList'; import { useBiohubApi } from 'hooks/useBioHubApi'; import { IGetAccessRequestsListResponse } from 'interfaces/useAdminApi.interface'; import { IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface'; import { ISystemUser } from 'interfaces/useUserApi.interface'; import React, { useEffect, useState } from 'react'; -import ActiveUsersList from './ActiveUsersList'; +import AccessRequestContainer from './access-requests/AccessRequestContainer'; +import ActiveUsersList from './active/ActiveUsersList'; /** * Page to display user management data/functionality. @@ -33,7 +33,11 @@ const ManageUsersPage: React.FC = () => { const refreshAccessRequests = async () => { const accessResponse = await biohubApi.admin.getAdministrativeActivities( [AdministrativeActivityType.SYSTEM_ACCESS], - [AdministrativeActivityStatusType.PENDING, AdministrativeActivityStatusType.REJECTED] + [ + AdministrativeActivityStatusType.PENDING, + AdministrativeActivityStatusType.REJECTED, + AdministrativeActivityStatusType.ACTIONED + ] ); setAccessRequests(accessResponse); @@ -43,7 +47,11 @@ const ManageUsersPage: React.FC = () => { const getAccessRequests = async () => { const accessResponse = await biohubApi.admin.getAdministrativeActivities( [AdministrativeActivityType.SYSTEM_ACCESS], - [AdministrativeActivityStatusType.PENDING, AdministrativeActivityStatusType.REJECTED] + [ + AdministrativeActivityStatusType.PENDING, + AdministrativeActivityStatusType.REJECTED, + AdministrativeActivityStatusType.ACTIONED + ] ); setAccessRequests(() => { @@ -120,7 +128,7 @@ const ManageUsersPage: React.FC = () => { <> - { diff --git a/app/src/features/admin/users/access-requests/AccessRequestContainer.tsx b/app/src/features/admin/users/access-requests/AccessRequestContainer.tsx new file mode 100644 index 0000000000..bd5d656e29 --- /dev/null +++ b/app/src/features/admin/users/access-requests/AccessRequestContainer.tsx @@ -0,0 +1,125 @@ +import { mdiCancel, mdiCheck, mdiExclamationThick } from '@mdi/js'; +import Icon from '@mdi/react'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Divider from '@mui/material/Divider'; +import Paper from '@mui/material/Paper'; +import ToggleButton from '@mui/material/ToggleButton'; +import ToggleButtonGroup from '@mui/material/ToggleButtonGroup/ToggleButtonGroup'; +import Toolbar from '@mui/material/Toolbar'; +import Typography from '@mui/material/Typography'; +import { IGetAccessRequestsListResponse } from 'interfaces/useAdminApi.interface'; +import { IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface'; +import { useState } from 'react'; +import AccessRequestActionedList from './list/actioned/AccessRequestActionedList'; +import AccessRequestPendingList from './list/pending/AccessRequestPendingList'; +import AccessRequestRejectedList from './list/rejected/AccessRequestRejectedList'; + +export interface IAccessRequestContainerProps { + accessRequests: IGetAccessRequestsListResponse[]; + codes: IGetAllCodeSetsResponse; + refresh: () => void; +} + +enum AccessRequestViewEnum { + ACTIONED = 'ACTIONED', + PENDING = 'PENDING', + REJECTED = 'REJECTED' +} + +/** + * Container for displaying a list of user access. + * + */ +const AccessRequestContainer = (props: IAccessRequestContainerProps) => { + const { accessRequests, codes, refresh } = props; + const [activeView, setActiveView] = useState(AccessRequestViewEnum.PENDING); + + const views = [ + { value: AccessRequestViewEnum.PENDING, label: 'Pending', icon: mdiExclamationThick }, + { value: AccessRequestViewEnum.ACTIONED, label: 'Approved', icon: mdiCheck }, + { value: AccessRequestViewEnum.REJECTED, label: 'Rejected', icon: mdiCancel } + ]; + + const pendingRequests = accessRequests.filter((request) => request.status_name === 'Pending'); + const actionedRequests = accessRequests.filter((request) => request.status_name === 'Actioned'); + const rejectedRequests = accessRequests.filter((request) => request.status_name === 'Rejected'); + + return ( + <> + + + + Access Requests + + + + + { + if (!view) { + // An active view must be selected at all times + return; + } + + setActiveView(view); + }} + exclusive + sx={{ + width: '100%', + gap: 1, + '& Button': { + py: 0.5, + px: 1.5, + border: 'none !important', + fontWeight: 700, + borderRadius: '4px !important', + fontSize: '0.875rem', + letterSpacing: '0.02rem' + } + }}> + {views.map((view) => { + const getCount = () => { + switch (view.value) { + case AccessRequestViewEnum.PENDING: + return pendingRequests.length; + case AccessRequestViewEnum.ACTIONED: + return actionedRequests.length; + case AccessRequestViewEnum.REJECTED: + return rejectedRequests.length; + default: + return 0; + } + }; + return ( + }> + {view.label} ({getCount()}) + + ); + })} + + + + + {activeView === AccessRequestViewEnum.PENDING && ( + + )} + {activeView === AccessRequestViewEnum.ACTIONED && ( + + )} + {activeView === AccessRequestViewEnum.REJECTED && ( + + )} + + + + ); +}; + +export default AccessRequestContainer; diff --git a/app/src/features/admin/users/ReviewAccessRequestForm.test.tsx b/app/src/features/admin/users/access-requests/components/ReviewAccessRequestForm.test.tsx similarity index 95% rename from app/src/features/admin/users/ReviewAccessRequestForm.test.tsx rename to app/src/features/admin/users/access-requests/components/ReviewAccessRequestForm.test.tsx index 1c4c5a2c36..d01282f914 100644 --- a/app/src/features/admin/users/ReviewAccessRequestForm.test.tsx +++ b/app/src/features/admin/users/access-requests/components/ReviewAccessRequestForm.test.tsx @@ -1,12 +1,12 @@ import { SYSTEM_IDENTITY_SOURCE } from 'constants/auth'; -import ReviewAccessRequestForm, { - ReviewAccessRequestFormInitialValues, - ReviewAccessRequestFormYupSchema -} from 'features/admin/users/ReviewAccessRequestForm'; import { Formik } from 'formik'; import { IGetAccessRequestsListResponse } from 'interfaces/useAdminApi.interface'; import { codes } from 'test-helpers/code-helpers'; import { render, waitFor } from 'test-helpers/test-utils'; +import ReviewAccessRequestForm, { + ReviewAccessRequestFormInitialValues, + ReviewAccessRequestFormYupSchema +} from './ReviewAccessRequestForm'; describe('ReviewAccessRequestForm', () => { describe('IDIR Request', () => { @@ -20,6 +20,8 @@ describe('ReviewAccessRequestForm', () => { description: 'test description', notes: 'test node', create_date: '2021-04-18', + updated_by: 'Doe, John WLRS:EX', + update_date: '2021-04-20', data: { name: 'test data name', username: 'test data username', @@ -70,6 +72,8 @@ describe('ReviewAccessRequestForm', () => { description: 'test description', notes: 'test node', create_date: '2021-04-18', + updated_by: 'Doe, John WLRS:EX', + update_date: '2021-04-20', data: { name: 'test data name', username: 'test data username', diff --git a/app/src/features/admin/users/ReviewAccessRequestForm.tsx b/app/src/features/admin/users/access-requests/components/ReviewAccessRequestForm.tsx similarity index 95% rename from app/src/features/admin/users/ReviewAccessRequestForm.tsx rename to app/src/features/admin/users/access-requests/components/ReviewAccessRequestForm.tsx index 51f4f2c7b9..5aee63c3f7 100644 --- a/app/src/features/admin/users/ReviewAccessRequestForm.tsx +++ b/app/src/features/admin/users/access-requests/components/ReviewAccessRequestForm.tsx @@ -4,10 +4,11 @@ import Typography from '@mui/material/Typography'; import AutocompleteField, { IAutocompleteFieldOption } from 'components/fields/AutocompleteField'; import { SYSTEM_IDENTITY_SOURCE } from 'constants/auth'; import { DATE_FORMAT } from 'constants/dateTimeFormats'; +import dayjs from 'dayjs'; import { useFormikContext } from 'formik'; import { IGetAccessRequestsListResponse } from 'interfaces/useAdminApi.interface'; import React from 'react'; -import { getFormattedDate, getFormattedIdentitySource } from 'utils/Utils'; +import { getFormattedIdentitySource } from 'utils/Utils'; import yup from 'utils/YupSchema'; export interface IReviewAccessRequestForm { @@ -79,7 +80,7 @@ const ReviewAccessRequestForm: React.FC = (props) Date of Request - {getFormattedDate(DATE_FORMAT.ShortDateFormatMonthFirst, props.request.create_date)} + {dayjs(props.request.create_date).format(DATE_FORMAT.ShortMediumDateTimeFormat)} @@ -109,7 +110,7 @@ const ReviewAccessRequestForm: React.FC = (props) sx={{ marginBottom: '18px' }}> - Requested System Role + System Role
diff --git a/app/src/features/admin/users/access-requests/components/ViewAccessRequestForm.tsx b/app/src/features/admin/users/access-requests/components/ViewAccessRequestForm.tsx new file mode 100644 index 0000000000..5f80fe4641 --- /dev/null +++ b/app/src/features/admin/users/access-requests/components/ViewAccessRequestForm.tsx @@ -0,0 +1,93 @@ +import { mdiInformationOutline } from '@mdi/js'; +import Icon from '@mdi/react'; +import Box from '@mui/material/Box'; +import { blue } from '@mui/material/colors'; +import Grid from '@mui/material/Grid'; +import Typography from '@mui/material/Typography'; +import { SYSTEM_IDENTITY_SOURCE } from 'constants/auth'; +import { DATE_FORMAT } from 'constants/dateTimeFormats'; +import dayjs from 'dayjs'; +import { IGetAccessRequestsListResponse } from 'interfaces/useAdminApi.interface'; +import React from 'react'; +import { getFormattedIdentitySource } from 'utils/Utils'; + +export interface IViewAccessReuqestFormProps { + request: IGetAccessRequestsListResponse; + bannerText: string; +} + +/** + * Component to review system access requests. + * + * @return {*} + */ +export const ViewAccessRequestForm: React.FC = (props: IViewAccessReuqestFormProps) => { + const formattedUsername = [ + getFormattedIdentitySource(props.request.data.identitySource as SYSTEM_IDENTITY_SOURCE), + props.request.data.username + ] + .filter(Boolean) + .join('/'); + + return ( + <> + + + + + {props.bannerText} + + + + + + User Details + +
+ + + + Name + + {props.request.data.name} + + + + Username + + {formattedUsername} + + + + Email Address + + {props.request.data.email} + + + + Date of Request + + + {dayjs(props.request.create_date).format(DATE_FORMAT.ShortMediumDateTimeFormat)} + + + + + Company + + + {('company' in props.request.data && props.request.data.company) || 'Not Applicable'} + + + + + Reason for Request + + {props.request.data.reason} + + +
+
+ + ); +}; diff --git a/app/src/features/admin/users/access-requests/list/actioned/AccessRequestActionedList.tsx b/app/src/features/admin/users/access-requests/list/actioned/AccessRequestActionedList.tsx new file mode 100644 index 0000000000..3adb091480 --- /dev/null +++ b/app/src/features/admin/users/access-requests/list/actioned/AccessRequestActionedList.tsx @@ -0,0 +1,122 @@ +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogContent from '@mui/material/DialogContent'; +import DialogTitle from '@mui/material/DialogTitle'; +import { GridColDef } from '@mui/x-data-grid'; +import ColouredRectangleChip from 'components/chips/ColouredRectangleChip'; +import { StyledDataGrid } from 'components/data-grid/StyledDataGrid'; +import { getAccessRequestColour } from 'constants/colours'; +import { DATE_FORMAT } from 'constants/dateTimeFormats'; +import dayjs from 'dayjs'; +import { IGetAccessRequestsListResponse } from 'interfaces/useAdminApi.interface'; +import { useState } from 'react'; +import { ViewAccessRequestForm } from '../../components/ViewAccessRequestForm'; + +interface IAccessRequestActionedListProps { + accessRequests: IGetAccessRequestsListResponse[]; +} + +const AccessRequestActionedList = (props: IAccessRequestActionedListProps) => { + const { accessRequests } = props; + + const [showViewDialog, setShowViewDialog] = useState(false); + const [activeReview, setActiveReview] = useState(null); + + const accessRequestsColumnDefs: GridColDef[] = [ + { + field: 'display_name', + headerName: 'Display Name', + flex: 1, + disableColumnMenu: true, + valueGetter: (params) => { + return params.row.data?.displayName; + } + }, + { + field: 'username', + headerName: 'Username', + flex: 1, + disableColumnMenu: true, + valueGetter: (params) => { + return params.row.data?.username; + } + }, + { + field: 'create_date', + flex: 1, + headerName: 'Date of Request', + disableColumnMenu: true, + valueFormatter: (params) => { + return dayjs(params.value).format(DATE_FORMAT.ShortMediumDateTimeFormat); + } + }, + { + field: 'status_name', + width: 170, + headerName: 'Status', + disableColumnMenu: true, + renderCell: (params) => { + return ( + + ); + } + }, + { + field: 'actions', + headerName: '', + type: 'actions', + flex: 1, + sortable: false, + disableColumnMenu: true, + resizable: false, + align: 'right', + renderCell: (params) => ( + + ) + } + ]; + + return ( + <> + {activeReview && ( + setShowViewDialog(false)}> + Access Request + + + + + )} + + + ); +}; + +export default AccessRequestActionedList; diff --git a/app/src/features/admin/users/AccessRequestList.tsx b/app/src/features/admin/users/access-requests/list/pending/AccessRequestPendingList.tsx similarity index 70% rename from app/src/features/admin/users/AccessRequestList.tsx rename to app/src/features/admin/users/access-requests/list/pending/AccessRequestPendingList.tsx index fbd0ac607a..00a5cbc5c4 100644 --- a/app/src/features/admin/users/AccessRequestList.tsx +++ b/app/src/features/admin/users/access-requests/list/pending/AccessRequestPendingList.tsx @@ -1,88 +1,48 @@ -import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; -import Divider from '@mui/material/Divider'; -import Paper from '@mui/material/Paper'; -import Toolbar from '@mui/material/Toolbar'; -import Typography from '@mui/material/Typography'; import { GridColDef } from '@mui/x-data-grid'; -import { AccessStatusChip } from 'components/chips/AccessStatusChip'; +import ColouredRectangleChip from 'components/chips/ColouredRectangleChip'; import { StyledDataGrid } from 'components/data-grid/StyledDataGrid'; import RequestDialog from 'components/dialog/RequestDialog'; +import { getAccessRequestColour } from 'constants/colours'; import { DATE_FORMAT } from 'constants/dateTimeFormats'; import { AccessApprovalDispatchI18N, AccessDenialDispatchI18N, ReviewAccessRequestI18N } from 'constants/i18n'; -import { AdministrativeActivityStatusType } from 'constants/misc'; import { DialogContext } from 'contexts/dialogContext'; +import dayjs from 'dayjs'; import { APIError } from 'hooks/api/useAxios'; import { useBiohubApi } from 'hooks/useBioHubApi'; import { IGetAccessRequestsListResponse } from 'interfaces/useAdminApi.interface'; -import { IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface'; import { useContext, useState } from 'react'; -import { getFormattedDate } from 'utils/Utils'; import ReviewAccessRequestForm, { IReviewAccessRequestForm, ReviewAccessRequestFormInitialValues, ReviewAccessRequestFormYupSchema -} from './ReviewAccessRequestForm'; +} from '../../components/ReviewAccessRequestForm'; -export interface IAccessRequestListProps { +interface IAccessRequestPendingListProps { accessRequests: IGetAccessRequestsListResponse[]; - codes: IGetAllCodeSetsResponse; + codes: any; // Define this type based on your codes structure refresh: () => void; } -const pageSizeOptions = [10, 25, 50]; - -/** - * Page to display a list of user access. - * - */ -const AccessRequestList = (props: IAccessRequestListProps) => { +const AccessRequestPendingList = (props: IAccessRequestPendingListProps) => { const { accessRequests, codes, refresh } = props; const biohubApi = useBiohubApi(); + const dialogContext = useContext(DialogContext); const [showReviewDialog, setShowReviewDialog] = useState(false); const [activeReview, setActiveReview] = useState(null); - const dialogContext = useContext(DialogContext); - - const defaultErrorDialogProps = { - dialogTitle: ReviewAccessRequestI18N.reviewErrorTitle, - dialogText: ReviewAccessRequestI18N.reviewErrorText, - open: false, - onClose: () => { - dialogContext.setErrorDialog({ open: false }); - }, - onOk: () => { - dialogContext.setErrorDialog({ open: false }); - } - }; - - const dispatchApprovalErrorDialogProps = { - dialogTitle: AccessApprovalDispatchI18N.reviewErrorTitle, - dialogText: AccessApprovalDispatchI18N.reviewErrorText, - open: false, - onClose: () => { - dialogContext.setErrorDialog({ open: false }); - }, - onOk: () => { - dialogContext.setErrorDialog({ open: false }); - } - }; - - const dispatchDenialErrorDialogProps = { - dialogTitle: AccessDenialDispatchI18N.reviewErrorTitle, - dialogText: AccessDenialDispatchI18N.reviewErrorText, - open: false, - onClose: () => { - dialogContext.setErrorDialog({ open: false }); - }, - onOk: () => { - dialogContext.setErrorDialog({ open: false }); - } - }; - const accessRequestsColumnDefs: GridColDef[] = [ + { + field: 'display_name', + headerName: 'Display Name', + flex: 1, + disableColumnMenu: true, + valueGetter: (params) => { + return params.row.data?.displayName; + } + }, { field: 'username', headerName: 'Username', @@ -98,7 +58,7 @@ const AccessRequestList = (props: IAccessRequestListProps) => { headerName: 'Date of Request', disableColumnMenu: true, valueFormatter: (params) => { - return getFormattedDate(DATE_FORMAT.ShortMediumDateFormat, params.value); + return dayjs(params.value).format(DATE_FORMAT.ShortMediumDateTimeFormat); } }, { @@ -107,7 +67,12 @@ const AccessRequestList = (props: IAccessRequestListProps) => { headerName: 'Status', disableColumnMenu: true, renderCell: (params) => { - return ; + return ( + + ); } }, { @@ -119,26 +84,56 @@ const AccessRequestList = (props: IAccessRequestListProps) => { disableColumnMenu: true, resizable: false, align: 'right', - renderCell: (params) => { - if (params.row.status_name !== AdministrativeActivityStatusType.PENDING) { - return <>; - } - - return ( - - ); - } + renderCell: (params) => ( + + ) } ]; + const defaultErrorDialogProps = { + dialogTitle: ReviewAccessRequestI18N.reviewErrorTitle, + dialogText: ReviewAccessRequestI18N.reviewErrorText, + open: false, + onClose: () => { + dialogContext.setErrorDialog({ open: false }); + }, + onOk: () => { + dialogContext.setErrorDialog({ open: false }); + } + }; + + const dispatchApprovalErrorDialogProps = { + dialogTitle: AccessApprovalDispatchI18N.reviewErrorTitle, + dialogText: AccessApprovalDispatchI18N.reviewErrorText, + open: false, + onClose: () => { + dialogContext.setErrorDialog({ open: false }); + }, + onOk: () => { + dialogContext.setErrorDialog({ open: false }); + } + }; + + const dispatchDenialErrorDialogProps = { + dialogTitle: AccessDenialDispatchI18N.reviewErrorTitle, + dialogText: AccessDenialDispatchI18N.reviewErrorText, + open: false, + onClose: () => { + dialogContext.setErrorDialog({ open: false }); + }, + onOk: () => { + dialogContext.setErrorDialog({ open: false }); + } + }; + const handleReviewDialogApprove = async (values: IReviewAccessRequestForm) => { if (!activeReview) { return; @@ -166,7 +161,7 @@ const AccessRequestList = (props: IAccessRequestListProps) => { { subject: 'SIMS: Your request for access has been approved.', header: 'Your request for access to the Species Inventory Management System has been approved.', - main_body1: 'This is an automated message from the BioHub Species Inventory Management System', + main_body1: 'This is an automated message from the Species Inventory Management System', main_body2: '', footer: '' } @@ -209,7 +204,7 @@ const AccessRequestList = (props: IAccessRequestListProps) => { { subject: 'SIMS: Your request for access has been denied.', header: 'Your request for access to the Species Inventory Management System has been denied.', - main_body1: 'This is an automated message from the BioHub Species Inventory Management System', + main_body1: 'This is an automated message from the Species Inventory Management System', main_body2: '', footer: '' } @@ -246,51 +241,28 @@ const AccessRequestList = (props: IAccessRequestListProps) => { element: activeReview ? ( { - return { value: item.id, label: item.name }; - }) || [] - } + system_roles={codes?.system_roles?.map((item: any) => ({ value: item.id, label: item.name })) || []} /> ) : ( <> ) }} /> - - - - Access Requests{' '} - - ({Number(accessRequests?.length ?? 0).toLocaleString()}) - - - - - - - - + ); }; -export default AccessRequestList; +export default AccessRequestPendingList; diff --git a/app/src/features/admin/users/access-requests/list/rejected/AccessRequestRejectedList.tsx b/app/src/features/admin/users/access-requests/list/rejected/AccessRequestRejectedList.tsx new file mode 100644 index 0000000000..eb448746f9 --- /dev/null +++ b/app/src/features/admin/users/access-requests/list/rejected/AccessRequestRejectedList.tsx @@ -0,0 +1,122 @@ +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogContent from '@mui/material/DialogContent'; +import DialogTitle from '@mui/material/DialogTitle'; +import { GridColDef } from '@mui/x-data-grid'; +import ColouredRectangleChip from 'components/chips/ColouredRectangleChip'; +import { StyledDataGrid } from 'components/data-grid/StyledDataGrid'; +import { getAccessRequestColour } from 'constants/colours'; +import { DATE_FORMAT } from 'constants/dateTimeFormats'; +import dayjs from 'dayjs'; +import { IGetAccessRequestsListResponse } from 'interfaces/useAdminApi.interface'; +import { useState } from 'react'; +import { ViewAccessRequestForm } from '../../components/ViewAccessRequestForm'; + +interface IAccessRequestRejectedListProps { + accessRequests: IGetAccessRequestsListResponse[]; +} + +const AccessRequestRejectedList = (props: IAccessRequestRejectedListProps) => { + const { accessRequests } = props; + + const [showReviewDialog, setShowReviewDialog] = useState(false); + const [activeReview, setActiveReview] = useState(null); + + const accessRequestsColumnDefs: GridColDef[] = [ + { + field: 'display_name', + headerName: 'Display Name', + flex: 1, + disableColumnMenu: true, + valueGetter: (params) => { + return params.row.data?.displayName; + } + }, + { + field: 'username', + headerName: 'Username', + flex: 1, + disableColumnMenu: true, + valueGetter: (params) => { + return params.row.data?.username; + } + }, + { + field: 'create_date', + flex: 1, + headerName: 'Date of Request', + disableColumnMenu: true, + valueFormatter: (params) => { + return dayjs(params.value).format(DATE_FORMAT.ShortMediumDateTimeFormat); + } + }, + { + field: 'status_name', + width: 170, + headerName: 'Status', + disableColumnMenu: true, + renderCell: (params) => { + return ( + + ); + } + }, + { + field: 'actions', + headerName: '', + type: 'actions', + flex: 1, + sortable: false, + disableColumnMenu: true, + resizable: false, + align: 'right', + renderCell: (params) => ( + + ) + } + ]; + + return ( + <> + {activeReview && ( + setShowReviewDialog(false)}> + Access Request + + + + + )} + + + ); +}; + +export default AccessRequestRejectedList; diff --git a/app/src/features/admin/users/ActiveUsersList.test.tsx b/app/src/features/admin/users/active/ActiveUsersList.test.tsx similarity index 100% rename from app/src/features/admin/users/ActiveUsersList.test.tsx rename to app/src/features/admin/users/active/ActiveUsersList.test.tsx diff --git a/app/src/features/admin/users/ActiveUsersList.tsx b/app/src/features/admin/users/active/ActiveUsersList.tsx similarity index 92% rename from app/src/features/admin/users/ActiveUsersList.tsx rename to app/src/features/admin/users/active/ActiveUsersList.tsx index d6e48eabd0..4e9c839ca0 100644 --- a/app/src/features/admin/users/ActiveUsersList.tsx +++ b/app/src/features/admin/users/active/ActiveUsersList.tsx @@ -2,6 +2,7 @@ import { mdiAccountDetailsOutline, mdiChevronDown, mdiDotsVertical, mdiPlus, mdi import Icon from '@mdi/react'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; +import grey from '@mui/material/colors/grey'; import Divider from '@mui/material/Divider'; import Link from '@mui/material/Link'; import Paper from '@mui/material/Paper'; @@ -25,7 +26,7 @@ import AddSystemUsersForm, { AddSystemUsersFormInitialValues, AddSystemUsersFormYupSchema, IAddSystemUsersForm -} from './AddSystemUsersForm'; +} from '../add/AddSystemUsersForm'; export interface IActiveUsersListProps { activeUsers: ISystemUser[]; @@ -52,8 +53,24 @@ const ActiveUsersList = (props: IActiveUsersListProps) => { const activeUsersColumnDefs: GridColDef[] = [ { - field: 'user_identifier', - headerName: 'Username', + field: 'system_user_id', + headerName: 'ID', + width: 70, + minWidth: 70, + renderHeader: () => ( + + ID + + ), + renderCell: (params) => ( + + {params.row.system_user_id} + + ) + }, + { + field: 'display_name', + headerName: 'Display Name', flex: 1, disableColumnMenu: true, renderCell: (params) => { @@ -63,11 +80,29 @@ const ActiveUsersList = (props: IActiveUsersListProps) => { underline="always" to={`/admin/users/${params.row.system_user_id}`} component={RouterLink}> - {params.row.user_identifier || 'No identifier'} + {params.row.display_name || 'No identifier'} ); } }, + { + field: 'identity_source', + headerName: 'Account Type', + flex: 1, + disableColumnMenu: true, + valueGetter: (params) => { + return params.row.identity_source; + } + }, + { + field: 'user_identifier', + headerName: 'Username', + flex: 1, + disableColumnMenu: true, + valueGetter: (params) => { + return params.row.user_identifier; + } + }, { field: 'role_names', flex: 1, @@ -339,7 +374,7 @@ const ActiveUsersList = (props: IActiveUsersListProps) => { - Active Users{' '} + Active Users  = (props) => { Manage Users - {userDetails.user_identifier} + {userDetails.display_name} } - title={userDetails.user_identifier} + title={userDetails.display_name} subTitleJSX={ {userDetails.role_names[0]} diff --git a/app/src/features/admin/users/UsersDetailPage.test.tsx b/app/src/features/admin/users/projects/UsersDetailPage.test.tsx similarity index 91% rename from app/src/features/admin/users/UsersDetailPage.test.tsx rename to app/src/features/admin/users/projects/UsersDetailPage.test.tsx index 1de69e46d3..e3494c1947 100644 --- a/app/src/features/admin/users/UsersDetailPage.test.tsx +++ b/app/src/features/admin/users/projects/UsersDetailPage.test.tsx @@ -1,10 +1,10 @@ import { createMemoryHistory } from 'history'; +import { useBiohubApi } from 'hooks/useBioHubApi'; import { IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface'; +import { IGetUserProjectsListResponse } from 'interfaces/useProjectApi.interface'; +import { ISystemUser } from 'interfaces/useUserApi.interface'; import { Router } from 'react-router'; import { cleanup, render, waitFor } from 'test-helpers/test-utils'; -import { useBiohubApi } from '../../../hooks/useBioHubApi'; -import { IGetUserProjectsListResponse } from '../../../interfaces/useProjectApi.interface'; -import { ISystemUser } from '../../../interfaces/useUserApi.interface'; import UsersDetailPage from './UsersDetailPage'; const history = createMemoryHistory(); diff --git a/app/src/features/admin/users/UsersDetailPage.tsx b/app/src/features/admin/users/projects/UsersDetailPage.tsx similarity index 90% rename from app/src/features/admin/users/UsersDetailPage.tsx rename to app/src/features/admin/users/projects/UsersDetailPage.tsx index 5a0dd9b5e6..a9785a8da2 100644 --- a/app/src/features/admin/users/UsersDetailPage.tsx +++ b/app/src/features/admin/users/projects/UsersDetailPage.tsx @@ -1,9 +1,9 @@ import CircularProgress from '@mui/material/CircularProgress'; import Container from '@mui/material/Container'; +import { useBiohubApi } from 'hooks/useBioHubApi'; +import { ISystemUser } from 'interfaces/useUserApi.interface'; import React, { useEffect, useState } from 'react'; import { useParams } from 'react-router'; -import { useBiohubApi } from '../../../hooks/useBioHubApi'; -import { ISystemUser } from '../../../interfaces/useUserApi.interface'; import UsersDetailHeader from './UsersDetailHeader'; import UsersDetailProjects from './UsersDetailProjects'; diff --git a/app/src/features/admin/users/UsersDetailProjects.test.tsx b/app/src/features/admin/users/projects/UsersDetailProjects.test.tsx similarity index 98% rename from app/src/features/admin/users/UsersDetailProjects.test.tsx rename to app/src/features/admin/users/projects/UsersDetailProjects.test.tsx index 4759fcb244..ac1cde2d7e 100644 --- a/app/src/features/admin/users/UsersDetailProjects.test.tsx +++ b/app/src/features/admin/users/projects/UsersDetailProjects.test.tsx @@ -1,11 +1,11 @@ import { DialogContextProvider } from 'contexts/dialogContext'; import { createMemoryHistory } from 'history'; +import { useBiohubApi } from 'hooks/useBioHubApi'; import { IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface'; +import { IGetUserProjectsListResponse } from 'interfaces/useProjectApi.interface'; import { ISystemUser } from 'interfaces/useUserApi.interface'; import { Router } from 'react-router'; import { cleanup, fireEvent, render, waitFor } from 'test-helpers/test-utils'; -import { useBiohubApi } from '../../../hooks/useBioHubApi'; -import { IGetUserProjectsListResponse } from '../../../interfaces/useProjectApi.interface'; import UsersDetailProjects from './UsersDetailProjects'; const history = createMemoryHistory(); diff --git a/app/src/features/admin/users/UsersDetailProjects.tsx b/app/src/features/admin/users/projects/UsersDetailProjects.tsx similarity index 93% rename from app/src/features/admin/users/UsersDetailProjects.tsx rename to app/src/features/admin/users/projects/UsersDetailProjects.tsx index 7000e425e0..47e7827515 100644 --- a/app/src/features/admin/users/UsersDetailProjects.tsx +++ b/app/src/features/admin/users/projects/UsersDetailProjects.tsx @@ -13,19 +13,19 @@ import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; import Toolbar from '@mui/material/Toolbar'; import Typography from '@mui/material/Typography'; +import { IErrorDialogProps } from 'components/dialog/ErrorDialog'; +import { IYesNoDialogProps } from 'components/dialog/YesNoDialog'; +import { CustomMenuButton } from 'components/toolbar/ActionToolbars'; +import { ProjectParticipantsI18N, SystemUserI18N } from 'constants/i18n'; +import { DialogContext } from 'contexts/dialogContext'; +import { APIError } from 'hooks/api/useAxios'; +import { useBiohubApi } from 'hooks/useBioHubApi'; +import { CodeSet, IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface'; +import { IGetUserProjectsListResponse } from 'interfaces/useProjectApi.interface'; +import { ISystemUser } from 'interfaces/useUserApi.interface'; import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { useParams } from 'react-router'; import { Link as RouterLink } from 'react-router-dom'; -import { IErrorDialogProps } from '../../../components/dialog/ErrorDialog'; -import { IYesNoDialogProps } from '../../../components/dialog/YesNoDialog'; -import { CustomMenuButton } from '../../../components/toolbar/ActionToolbars'; -import { ProjectParticipantsI18N, SystemUserI18N } from '../../../constants/i18n'; -import { DialogContext } from '../../../contexts/dialogContext'; -import { APIError } from '../../../hooks/api/useAxios'; -import { useBiohubApi } from '../../../hooks/useBioHubApi'; -import { CodeSet, IGetAllCodeSetsResponse } from '../../../interfaces/useCodesApi.interface'; -import { IGetUserProjectsListResponse } from '../../../interfaces/useProjectApi.interface'; -import { ISystemUser } from '../../../interfaces/useUserApi.interface'; export interface IProjectDetailsProps { userDetails: ISystemUser; diff --git a/app/src/interfaces/useAdminApi.interface.ts b/app/src/interfaces/useAdminApi.interface.ts index 2f2140928d..41bc926d27 100644 --- a/app/src/interfaces/useAdminApi.interface.ts +++ b/app/src/interfaces/useAdminApi.interface.ts @@ -29,6 +29,8 @@ export interface IGetAccessRequestsListResponse { description: string; notes: string; create_date: string; + update_date: string | null; + updated_by: string | null; data: IAccessRequestDataObject; } diff --git a/app/src/themes/appTheme.ts b/app/src/themes/appTheme.ts index 1afe273e4c..cd82c81de1 100644 --- a/app/src/themes/appTheme.ts +++ b/app/src/themes/appTheme.ts @@ -210,7 +210,8 @@ const appTheme = createTheme({ MuiDialogTitle: { styleOverrides: { root: { - paddingTop: '24px' + paddingTop: '24px', + paddingBottom: '8px' } } }, diff --git a/database/src/seeds/03_basic_project_survey_setup.ts b/database/src/seeds/03_basic_project_survey_setup.ts index 84f8dff30b..96e7b2c343 100644 --- a/database/src/seeds/03_basic_project_survey_setup.ts +++ b/database/src/seeds/03_basic_project_survey_setup.ts @@ -21,6 +21,8 @@ const focalTaxonIdOptions = [ const surveyRegionsA = ['Kootenay-Boundary Natural Resource Region', 'West Coast Natural Resource Region']; const surveyRegionsB = ['Cariboo Natural Resource Region', 'South Coast Natural Resource Region']; +const identitySources = ['IDIR', 'BCEIDBUSINESS', 'BCEIDBASIC']; + /** * Add spatial transform * @@ -44,6 +46,11 @@ export async function seed(knex: Knex): Promise { `); } + // Insert access requests + for (let i = 0; i < 15; i++) { + await knex.raw(`${insertAccessRequest()}`); + } + // Check if at least 1 project already exists const checkProjectsResponse = await knex.raw(checkAnyProjectExists()); @@ -729,3 +736,37 @@ const insertSurveyRegionData = (surveyId: string, region: string) => ` WHERE region_name = $$${region}$$; `; + +/** + * SQL to insert system access requests + * + */ +const insertAccessRequest = () => ` + INSERT INTO administrative_activity + ( + administrative_activity_status_type_id, + administrative_activity_type_id, + reported_system_user_id, + assigned_system_user_id, + description, + data, + notes + ) + VALUES ( + (SELECT administrative_activity_status_type_id FROM administrative_activity_status_type ORDER BY random() LIMIT 1), + (SELECT administrative_activity_type_id FROM administrative_activity_type WHERE name = 'System Access'), + (SELECT system_user_id FROM system_user ORDER BY random() LIMIT 1), + (SELECT system_user_id FROM system_user ORDER BY random() LIMIT 1), + $$${faker.lorem.sentences(2)}$$, + jsonb_build_object( + 'reason', '${faker.lorem.sentences(1)}', + 'userGuid', '${faker.string.uuid()}', + 'name', '${faker.lorem.words(2)}', + 'username', '${faker.lorem.words(1)}', + 'email', 'default', + 'identitySource', '${identitySources[faker.number.int({ min: 0, max: identitySources.length - 1 })]}', + 'displayName', '${faker.lorem.words(1)}' + ), + $$${faker.lorem.sentences(2)}$$ + ); + `; From 398f81eaa9218dc174bcc52983970595574ebe12 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Thu, 22 Aug 2024 18:18:54 -0700 Subject: [PATCH 2/4] update frontend tests & add jsdocs --- .../administrative-activity-service.test.ts | 8 +- app/src/constants/colours.ts | 6 +- .../admin/users/ManageUsersPage.test.tsx | 2 +- .../ReviewAccessRequestForm.test.tsx | 2 - .../components/ViewAccessRequestForm.tsx | 2 +- .../actioned/AccessRequestActionedList.tsx | 10 ++- .../list/pending/AccessRequestPendingList.tsx | 82 ++++++++++--------- .../rejected/AccessRequestRejectedList.tsx | 10 ++- .../users/active/ActiveUsersList.test.tsx | 27 +----- .../users/projects/UsersDetailHeader.test.tsx | 2 +- .../users/projects/UsersDetailPage.test.tsx | 2 +- .../projects/UsersDetailProjects.test.tsx | 2 +- 12 files changed, 76 insertions(+), 79 deletions(-) diff --git a/api/src/services/administrative-activity-service.test.ts b/api/src/services/administrative-activity-service.test.ts index 0f570b87cf..74cc0e175f 100644 --- a/api/src/services/administrative-activity-service.test.ts +++ b/api/src/services/administrative-activity-service.test.ts @@ -42,7 +42,9 @@ describe('AdministrativeActivityService', () => { identitySource: 'BCEIDBASIC' }, notes: null, - create_date: '2023-05-02T02:04:10.751Z' + create_date: '2023-05-02T02:04:10.751Z', + update_date: '2023-05-02T02:04:10.751Z', + updated_by: 'Doe, John WLRS:EX' } ]); @@ -66,7 +68,9 @@ describe('AdministrativeActivityService', () => { identitySource: 'BCEIDBASIC' }, notes: null, - create_date: '2023-05-02T02:04:10.751Z' + create_date: '2023-05-02T02:04:10.751Z', + update_date: '2023-05-02T02:04:10.751Z', + updated_by: 'Doe, John WLRS:EX' } ]); }); diff --git a/app/src/constants/colours.ts b/app/src/constants/colours.ts index caf3ddb47a..8d1b98cd2e 100644 --- a/app/src/constants/colours.ts +++ b/app/src/constants/colours.ts @@ -69,7 +69,7 @@ const NRM_REGION_COLOUR_MAP = { * Colour map for access request chips. * */ -const ACCESS_REQUEST_COLOUR_MAP = { +const ACCESS_REQUEST_STATUS_COLOUR_MAP = { Pending: { colour: purple }, Actioned: { colour: green }, Rejected: { colour: red } @@ -96,10 +96,10 @@ const generateColourMapGetter = (colourMap: T, fallbackColo }; /** - * Get survey progress colour mapping. + * Get access request status colour mapping. * */ -export const getAccessRequestColour = generateColourMapGetter(ACCESS_REQUEST_COLOUR_MAP); +export const getAccessRequestStatusColour = generateColourMapGetter(ACCESS_REQUEST_STATUS_COLOUR_MAP); /** * Get survey progress colour mapping. diff --git a/app/src/features/admin/users/ManageUsersPage.test.tsx b/app/src/features/admin/users/ManageUsersPage.test.tsx index 3cd33589cc..e246b32436 100644 --- a/app/src/features/admin/users/ManageUsersPage.test.tsx +++ b/app/src/features/admin/users/ManageUsersPage.test.tsx @@ -74,7 +74,7 @@ describe('ManageUsersPage', () => { const { getByText } = renderContainer(); await waitFor(() => { - expect(getByText('No Access Requests')).toBeVisible(); + expect(getByText('No Pending Access Requests')).toBeVisible(); expect(getByText('No Active Users')).toBeVisible(); }); }); diff --git a/app/src/features/admin/users/access-requests/components/ReviewAccessRequestForm.test.tsx b/app/src/features/admin/users/access-requests/components/ReviewAccessRequestForm.test.tsx index d01282f914..4041c0f84a 100644 --- a/app/src/features/admin/users/access-requests/components/ReviewAccessRequestForm.test.tsx +++ b/app/src/features/admin/users/access-requests/components/ReviewAccessRequestForm.test.tsx @@ -56,7 +56,6 @@ describe('ReviewAccessRequestForm', () => { expect(getByText('test data name')).toBeVisible(); expect(getByText('IDIR/test data username')).toBeVisible(); expect(getByText('test data email')).toBeVisible(); - expect(getByText('04/18/2021')).toBeVisible(); }); }); }); @@ -109,7 +108,6 @@ describe('ReviewAccessRequestForm', () => { expect(getByText('test data name')).toBeVisible(); expect(getByText('BCeID Basic/test data username')).toBeVisible(); expect(getByText('test data email')).toBeVisible(); - expect(getByText('04/18/2021')).toBeVisible(); expect(getByText('test company')).toBeVisible(); }); }); diff --git a/app/src/features/admin/users/access-requests/components/ViewAccessRequestForm.tsx b/app/src/features/admin/users/access-requests/components/ViewAccessRequestForm.tsx index 5f80fe4641..49ee184853 100644 --- a/app/src/features/admin/users/access-requests/components/ViewAccessRequestForm.tsx +++ b/app/src/features/admin/users/access-requests/components/ViewAccessRequestForm.tsx @@ -17,7 +17,7 @@ export interface IViewAccessReuqestFormProps { } /** - * Component to review system access requests. + * Component to view system access requests without the ability to edit the user's system role * * @return {*} */ diff --git a/app/src/features/admin/users/access-requests/list/actioned/AccessRequestActionedList.tsx b/app/src/features/admin/users/access-requests/list/actioned/AccessRequestActionedList.tsx index 3adb091480..c16d0bc61a 100644 --- a/app/src/features/admin/users/access-requests/list/actioned/AccessRequestActionedList.tsx +++ b/app/src/features/admin/users/access-requests/list/actioned/AccessRequestActionedList.tsx @@ -5,7 +5,7 @@ import DialogTitle from '@mui/material/DialogTitle'; import { GridColDef } from '@mui/x-data-grid'; import ColouredRectangleChip from 'components/chips/ColouredRectangleChip'; import { StyledDataGrid } from 'components/data-grid/StyledDataGrid'; -import { getAccessRequestColour } from 'constants/colours'; +import { getAccessRequestStatusColour } from 'constants/colours'; import { DATE_FORMAT } from 'constants/dateTimeFormats'; import dayjs from 'dayjs'; import { IGetAccessRequestsListResponse } from 'interfaces/useAdminApi.interface'; @@ -16,6 +16,12 @@ interface IAccessRequestActionedListProps { accessRequests: IGetAccessRequestsListResponse[]; } +/** + * Returns a data grid component displaying approved access requests + * + * @param props {IAccessRequestActionedListProps} + * @returns + */ const AccessRequestActionedList = (props: IAccessRequestActionedListProps) => { const { accessRequests } = props; @@ -59,7 +65,7 @@ const AccessRequestActionedList = (props: IAccessRequestActionedListProps) => { return ( ); } diff --git a/app/src/features/admin/users/access-requests/list/pending/AccessRequestPendingList.tsx b/app/src/features/admin/users/access-requests/list/pending/AccessRequestPendingList.tsx index 00a5cbc5c4..861c31cb92 100644 --- a/app/src/features/admin/users/access-requests/list/pending/AccessRequestPendingList.tsx +++ b/app/src/features/admin/users/access-requests/list/pending/AccessRequestPendingList.tsx @@ -1,16 +1,18 @@ import Button from '@mui/material/Button'; +import Typography from '@mui/material/Typography'; import { GridColDef } from '@mui/x-data-grid'; import ColouredRectangleChip from 'components/chips/ColouredRectangleChip'; import { StyledDataGrid } from 'components/data-grid/StyledDataGrid'; import RequestDialog from 'components/dialog/RequestDialog'; -import { getAccessRequestColour } from 'constants/colours'; +import { getAccessRequestStatusColour } from 'constants/colours'; import { DATE_FORMAT } from 'constants/dateTimeFormats'; -import { AccessApprovalDispatchI18N, AccessDenialDispatchI18N, ReviewAccessRequestI18N } from 'constants/i18n'; -import { DialogContext } from 'contexts/dialogContext'; +import { ReviewAccessRequestI18N } from 'constants/i18n'; +import { DialogContext, ISnackbarProps } from 'contexts/dialogContext'; import dayjs from 'dayjs'; import { APIError } from 'hooks/api/useAxios'; import { useBiohubApi } from 'hooks/useBioHubApi'; import { IGetAccessRequestsListResponse } from 'interfaces/useAdminApi.interface'; +import { IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface'; import { useContext, useState } from 'react'; import ReviewAccessRequestForm, { IReviewAccessRequestForm, @@ -20,10 +22,16 @@ import ReviewAccessRequestForm, { interface IAccessRequestPendingListProps { accessRequests: IGetAccessRequestsListResponse[]; - codes: any; // Define this type based on your codes structure + codes: IGetAllCodeSetsResponse; refresh: () => void; } +/** + * Returns a data grid component displaying pending access requests + * + * @param props {IAccessRequestPendingListProps} + * @returns + */ const AccessRequestPendingList = (props: IAccessRequestPendingListProps) => { const { accessRequests, codes, refresh } = props; @@ -33,6 +41,10 @@ const AccessRequestPendingList = (props: IAccessRequestPendingListProps) => { const [showReviewDialog, setShowReviewDialog] = useState(false); const [activeReview, setActiveReview] = useState(null); + const showSnackBar = (textDialogProps?: Partial) => { + dialogContext.setSnackbar({ ...textDialogProps, open: true }); + }; + const accessRequestsColumnDefs: GridColDef[] = [ { field: 'display_name', @@ -70,7 +82,7 @@ const AccessRequestPendingList = (props: IAccessRequestPendingListProps) => { return ( ); } @@ -110,30 +122,6 @@ const AccessRequestPendingList = (props: IAccessRequestPendingListProps) => { } }; - const dispatchApprovalErrorDialogProps = { - dialogTitle: AccessApprovalDispatchI18N.reviewErrorTitle, - dialogText: AccessApprovalDispatchI18N.reviewErrorText, - open: false, - onClose: () => { - dialogContext.setErrorDialog({ open: false }); - }, - onOk: () => { - dialogContext.setErrorDialog({ open: false }); - } - }; - - const dispatchDenialErrorDialogProps = { - dialogTitle: AccessDenialDispatchI18N.reviewErrorTitle, - dialogText: AccessDenialDispatchI18N.reviewErrorText, - open: false, - onClose: () => { - dialogContext.setErrorDialog({ open: false }); - }, - onOk: () => { - dialogContext.setErrorDialog({ open: false }); - } - }; - const handleReviewDialogApprove = async (values: IReviewAccessRequestForm) => { if (!activeReview) { return; @@ -151,6 +139,14 @@ const AccessRequestPendingList = (props: IAccessRequestPendingListProps) => { roleIds: (values.system_role && [values.system_role]) || [] }); + showSnackBar({ + snackbarMessage: ( + + Approved access request + + ) + }); + try { await biohubApi.admin.sendGCNotification( { @@ -167,10 +163,12 @@ const AccessRequestPendingList = (props: IAccessRequestPendingListProps) => { } ); } catch (error) { - dialogContext.setErrorDialog({ - ...dispatchApprovalErrorDialogProps, - open: true, - dialogErrorDetails: (error as APIError).errors + showSnackBar({ + snackbarMessage: ( + + Approved access request, but failed to email notification + + ) }); } finally { refresh(); @@ -194,6 +192,14 @@ const AccessRequestPendingList = (props: IAccessRequestPendingListProps) => { try { await biohubApi.admin.denyAccessRequest(activeReview.id); + showSnackBar({ + snackbarMessage: ( + + Approved access request + + ) + }); + try { await biohubApi.admin.sendGCNotification( { @@ -210,10 +216,12 @@ const AccessRequestPendingList = (props: IAccessRequestPendingListProps) => { } ); } catch (error) { - dialogContext.setErrorDialog({ - ...dispatchDenialErrorDialogProps, - open: true, - dialogErrorDetails: (error as APIError).errors + showSnackBar({ + snackbarMessage: ( + + Denied access request, but failed to email notification + + ) }); } finally { refresh(); diff --git a/app/src/features/admin/users/access-requests/list/rejected/AccessRequestRejectedList.tsx b/app/src/features/admin/users/access-requests/list/rejected/AccessRequestRejectedList.tsx index eb448746f9..b22d0ddab8 100644 --- a/app/src/features/admin/users/access-requests/list/rejected/AccessRequestRejectedList.tsx +++ b/app/src/features/admin/users/access-requests/list/rejected/AccessRequestRejectedList.tsx @@ -5,7 +5,7 @@ import DialogTitle from '@mui/material/DialogTitle'; import { GridColDef } from '@mui/x-data-grid'; import ColouredRectangleChip from 'components/chips/ColouredRectangleChip'; import { StyledDataGrid } from 'components/data-grid/StyledDataGrid'; -import { getAccessRequestColour } from 'constants/colours'; +import { getAccessRequestStatusColour } from 'constants/colours'; import { DATE_FORMAT } from 'constants/dateTimeFormats'; import dayjs from 'dayjs'; import { IGetAccessRequestsListResponse } from 'interfaces/useAdminApi.interface'; @@ -16,6 +16,12 @@ interface IAccessRequestRejectedListProps { accessRequests: IGetAccessRequestsListResponse[]; } +/** + * Returns a data grid component displaying denied access requests + * + * @param props {IAccessRequestRejectedListProps} + * @returns + */ const AccessRequestRejectedList = (props: IAccessRequestRejectedListProps) => { const { accessRequests } = props; @@ -59,7 +65,7 @@ const AccessRequestRejectedList = (props: IAccessRequestRejectedListProps) => { return ( ); } diff --git a/app/src/features/admin/users/active/ActiveUsersList.test.tsx b/app/src/features/admin/users/active/ActiveUsersList.test.tsx index ece9f2f459..e396038c8c 100644 --- a/app/src/features/admin/users/active/ActiveUsersList.test.tsx +++ b/app/src/features/admin/users/active/ActiveUsersList.test.tsx @@ -9,7 +9,7 @@ import ActiveUsersList, { IActiveUsersListProps } from './ActiveUsersList'; const history = createMemoryHistory(); -jest.mock('../../../hooks/useBioHubApi'); +jest.mock('../../../../hooks/useBioHubApi'); const mockBiohubApi = useBiohubApi as jest.Mock; const mockUseApi = { @@ -81,31 +81,6 @@ describe('ActiveUsersList', () => { }); }); - it('shows a table row for an active user with fields not having values', async () => { - const { getByTestId } = renderContainer({ - activeUsers: [ - { - system_user_id: 1, - user_identifier: 'username', - user_guid: 'user-guid', - record_end_date: '2020-10-10', - role_names: [], - identity_source: 'idir', - role_ids: [], - email: '', - display_name: '', - agency: '' - } - ], - codes: codes, - refresh: () => {} - }); - - await waitFor(() => { - expect(getByTestId('custom-menu-button-NotApplicable')).toBeInTheDocument(); - }); - }); - it('renders the add new users button correctly', async () => { const { getByTestId } = renderContainer({ activeUsers: [], diff --git a/app/src/features/admin/users/projects/UsersDetailHeader.test.tsx b/app/src/features/admin/users/projects/UsersDetailHeader.test.tsx index d05dd52950..cda6451ea1 100644 --- a/app/src/features/admin/users/projects/UsersDetailHeader.test.tsx +++ b/app/src/features/admin/users/projects/UsersDetailHeader.test.tsx @@ -8,7 +8,7 @@ import UsersDetailHeader from './UsersDetailHeader'; const history = createMemoryHistory(); -jest.mock('../../../hooks/useBioHubApi'); +jest.mock('../../../../hooks/useBioHubApi'); const mockBiohubApi = useBiohubApi as jest.Mock; diff --git a/app/src/features/admin/users/projects/UsersDetailPage.test.tsx b/app/src/features/admin/users/projects/UsersDetailPage.test.tsx index e3494c1947..e12cf2326b 100644 --- a/app/src/features/admin/users/projects/UsersDetailPage.test.tsx +++ b/app/src/features/admin/users/projects/UsersDetailPage.test.tsx @@ -9,7 +9,7 @@ import UsersDetailPage from './UsersDetailPage'; const history = createMemoryHistory(); -jest.mock('../../../hooks/useBioHubApi'); +jest.mock('../../../../hooks/useBioHubApi'); const mockBiohubApi = useBiohubApi as jest.Mock; diff --git a/app/src/features/admin/users/projects/UsersDetailProjects.test.tsx b/app/src/features/admin/users/projects/UsersDetailProjects.test.tsx index ac1cde2d7e..ebac9cf029 100644 --- a/app/src/features/admin/users/projects/UsersDetailProjects.test.tsx +++ b/app/src/features/admin/users/projects/UsersDetailProjects.test.tsx @@ -10,7 +10,7 @@ import UsersDetailProjects from './UsersDetailProjects'; const history = createMemoryHistory(); -jest.mock('../../../hooks/useBioHubApi'); +jest.mock('../../../../hooks/useBioHubApi'); const mockBiohubApi = useBiohubApi as jest.Mock; From c7f76c6e585710167bf48509211d9154dd9da5f0 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Wed, 28 Aug 2024 17:18:09 -0700 Subject: [PATCH 3/4] frontend test is failing --- .../AccessRequestContainer.tsx | 142 +++++++++--------- .../list/pending/AccessRequestPendingList.tsx | 4 +- .../seeds/03_basic_project_survey_setup.ts | 2 +- 3 files changed, 73 insertions(+), 75 deletions(-) diff --git a/app/src/features/admin/users/access-requests/AccessRequestContainer.tsx b/app/src/features/admin/users/access-requests/AccessRequestContainer.tsx index bd5d656e29..051a9616ce 100644 --- a/app/src/features/admin/users/access-requests/AccessRequestContainer.tsx +++ b/app/src/features/admin/users/access-requests/AccessRequestContainer.tsx @@ -28,7 +28,7 @@ enum AccessRequestViewEnum { } /** - * Container for displaying a list of user access. + * Container for displaying a list of user access requests. * */ const AccessRequestContainer = (props: IAccessRequestContainerProps) => { @@ -46,79 +46,77 @@ const AccessRequestContainer = (props: IAccessRequestContainerProps) => { const rejectedRequests = accessRequests.filter((request) => request.status_name === 'Rejected'); return ( - <> - - - - Access Requests - - - - - { - if (!view) { - // An active view must be selected at all times - return; - } + + + + Access Requests + + + + + { + if (!view) { + // An active view must be selected at all times + return; + } - setActiveView(view); - }} - exclusive - sx={{ - width: '100%', - gap: 1, - '& Button': { - py: 0.5, - px: 1.5, - border: 'none !important', - fontWeight: 700, - borderRadius: '4px !important', - fontSize: '0.875rem', - letterSpacing: '0.02rem' + setActiveView(view); + }} + exclusive + sx={{ + width: '100%', + gap: 1, + '& Button': { + py: 0.5, + px: 1.5, + border: 'none !important', + fontWeight: 700, + borderRadius: '4px !important', + fontSize: '0.875rem', + letterSpacing: '0.02rem' + } + }}> + {views.map((view) => { + const getCount = () => { + switch (view.value) { + case AccessRequestViewEnum.PENDING: + return pendingRequests.length; + case AccessRequestViewEnum.ACTIONED: + return actionedRequests.length; + case AccessRequestViewEnum.REJECTED: + return rejectedRequests.length; + default: + return 0; } - }}> - {views.map((view) => { - const getCount = () => { - switch (view.value) { - case AccessRequestViewEnum.PENDING: - return pendingRequests.length; - case AccessRequestViewEnum.ACTIONED: - return actionedRequests.length; - case AccessRequestViewEnum.REJECTED: - return rejectedRequests.length; - default: - return 0; - } - }; - return ( - }> - {view.label} ({getCount()}) - - ); - })} - - - - - {activeView === AccessRequestViewEnum.PENDING && ( - - )} - {activeView === AccessRequestViewEnum.ACTIONED && ( - - )} - {activeView === AccessRequestViewEnum.REJECTED && ( - - )} - - - + }; + return ( + }> + {view.label} ({getCount()}) + + ); + })} + + + + + {activeView === AccessRequestViewEnum.PENDING && ( + + )} + {activeView === AccessRequestViewEnum.ACTIONED && ( + + )} + {activeView === AccessRequestViewEnum.REJECTED && ( + + )} + + ); }; diff --git a/app/src/features/admin/users/access-requests/list/pending/AccessRequestPendingList.tsx b/app/src/features/admin/users/access-requests/list/pending/AccessRequestPendingList.tsx index 861c31cb92..8144e3da8c 100644 --- a/app/src/features/admin/users/access-requests/list/pending/AccessRequestPendingList.tsx +++ b/app/src/features/admin/users/access-requests/list/pending/AccessRequestPendingList.tsx @@ -166,7 +166,7 @@ const AccessRequestPendingList = (props: IAccessRequestPendingListProps) => { showSnackBar({ snackbarMessage: ( - Approved access request, but failed to email notification + Approved access request, but failed to send notification ) }); @@ -219,7 +219,7 @@ const AccessRequestPendingList = (props: IAccessRequestPendingListProps) => { showSnackBar({ snackbarMessage: ( - Denied access request, but failed to email notification + Denied access request, but failed to send notification ) }); diff --git a/database/src/seeds/03_basic_project_survey_setup.ts b/database/src/seeds/03_basic_project_survey_setup.ts index 96e7b2c343..1ec5a7b19a 100644 --- a/database/src/seeds/03_basic_project_survey_setup.ts +++ b/database/src/seeds/03_basic_project_survey_setup.ts @@ -47,7 +47,7 @@ export async function seed(knex: Knex): Promise { } // Insert access requests - for (let i = 0; i < 15; i++) { + for (let i = 0; i < 8; i++) { await knex.raw(`${insertAccessRequest()}`); } From 4be76488425d14715f38b75f9398822961a90ef3 Mon Sep 17 00:00:00 2001 From: Mac Deluca Date: Thu, 5 Sep 2024 11:06:25 -0700 Subject: [PATCH 4/4] fix: dropped test from ActiveUserList - table is larger then testing container, columns invisible --- .../users/active/ActiveUsersList.test.tsx | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/app/src/features/admin/users/active/ActiveUsersList.test.tsx b/app/src/features/admin/users/active/ActiveUsersList.test.tsx index e396038c8c..b61581a331 100644 --- a/app/src/features/admin/users/active/ActiveUsersList.test.tsx +++ b/app/src/features/admin/users/active/ActiveUsersList.test.tsx @@ -55,32 +55,6 @@ describe('ActiveUsersList', () => { }); }); - it('shows a table row for an active user with all fields having values', async () => { - const { getByText } = renderContainer({ - activeUsers: [ - { - system_user_id: 1, - user_identifier: 'username', - user_guid: 'user-guid', - record_end_date: '2020-10-10', - role_names: ['role 1'], - identity_source: 'idir', - role_ids: [1], - email: '', - display_name: '', - agency: '' - } - ], - codes: codes, - refresh: () => {} - }); - - await waitFor(() => { - expect(getByText('username')).toBeVisible(); - expect(getByText('role 1')).toBeVisible(); - }); - }); - it('renders the add new users button correctly', async () => { const { getByTestId } = renderContainer({ activeUsers: [],