diff --git a/cypress/local-only/tests/integration/fileAPetitionUpdated/file-a-petition-step-1-myself.cy.ts b/cypress/local-only/tests/integration/fileAPetitionUpdated/file-a-petition-step-1-myself.cy.ts index 90a4ce95162..8300dcdf095 100644 --- a/cypress/local-only/tests/integration/fileAPetitionUpdated/file-a-petition-step-1-myself.cy.ts +++ b/cypress/local-only/tests/integration/fileAPetitionUpdated/file-a-petition-step-1-myself.cy.ts @@ -48,7 +48,7 @@ describe('File a petition: Step 1 - Petitioner Information', () => { cy.get('[data-testid="filing-type-0"]').click(); cy.get('[data-testid="contact-primary-name-label"]').should( 'have.text', - 'Full Name', + 'Full name', ); }); diff --git a/shared/src/business/entities/trialSessions/SpecialTrialSessions.ts b/shared/src/business/entities/trialSessions/SpecialTrialSessions.ts new file mode 100644 index 00000000000..319e9aafd64 --- /dev/null +++ b/shared/src/business/entities/trialSessions/SpecialTrialSessions.ts @@ -0,0 +1,14 @@ +export type SpecialTrialSession = { + userId: string; + trialSessionId: string; +}; + +export type SpecialTrialSessionKey = { + pk: string; + sk: string; +}; + +export type TrialSessionWorkingCopyNotes = { + sessionNotes: string; + trialSessionId: string; +}; diff --git a/shared/src/business/useCases/getCaseInteractor.test.ts b/shared/src/business/useCases/getCaseInteractor.test.ts index e652316188e..a93c52af901 100644 --- a/shared/src/business/useCases/getCaseInteractor.test.ts +++ b/shared/src/business/useCases/getCaseInteractor.test.ts @@ -134,6 +134,57 @@ describe('getCaseInteractor', () => { expect(result.docketNumber).toEqual('101-00'); }); + it('should filter out docket entries that are not on the docket record when the currentUser is an external user associated with an unsealed case', async () => { + const expectedDocketEntries = testCase.docketEntries.filter( + de => de.isOnDocketRecord, + ); + applicationContext + .getPersistenceGateway() + .getCaseByDocketNumber.mockResolvedValue(testCase); + + const result = await getCaseInteractor( + applicationContext, + { + docketNumber: testCase.docketNumber, + }, + { + email: mockCaseContactPrimary.email, + name: mockCaseContactPrimary.name, + role: ROLES.petitioner, + userId: mockCaseContactPrimary.contactId, + }, + ); + + expect(result.docketEntries).toMatchObject(expectedDocketEntries); + }); + + it('should filter out docket entries that are not on the docket record when the currentUser is an external user associated with a sealed case', async () => { + const expectedDocketEntries = testCase.docketEntries.filter( + de => de.isOnDocketRecord, + ); + applicationContext + .getPersistenceGateway() + .getCaseByDocketNumber.mockResolvedValue({ + ...testCase, + isSealed: true, + }); + + const result = await getCaseInteractor( + applicationContext, + { + docketNumber: testCase.docketNumber, + }, + { + email: mockCaseContactPrimary.email, + name: mockCaseContactPrimary.name, + role: ROLES.petitioner, + userId: mockCaseContactPrimary.contactId, + }, + ); + + expect(result.docketEntries).toMatchObject(expectedDocketEntries); + }); + it('should return the case when the currentUser is an irs superuser even if the case has sealed documents', async () => { applicationContext .getPersistenceGateway() diff --git a/shared/src/business/useCases/getCaseInteractor.ts b/shared/src/business/useCases/getCaseInteractor.ts index 085a6c54ce2..8981c6da3f8 100644 --- a/shared/src/business/useCases/getCaseInteractor.ts +++ b/shared/src/business/useCases/getCaseInteractor.ts @@ -12,6 +12,10 @@ import { isAssociatedUser, isUserPartOfGroup, } from '../entities/cases/Case'; +import { + INITIAL_DOCUMENT_TYPES, + ROLES, +} from '@shared/business/entities/EntityConstants'; import { NotFoundError, UnauthorizedError } from '@web-api/errors/errors'; import { PublicCase } from '../entities/cases/PublicCase'; import { @@ -49,6 +53,10 @@ const getSealedCase = ({ } } + if (!User.isInternalUser(authorizedUser.role)) { + filterDocketEntriesNotOnDocketRecord({ authorizedUser, caseRecord }); + } + if (isAuthorizedToViewSealedCase || isAssociatedWithCase) { return new Case(caseRecord, { authorizedUser }).validate().toRawObject(); } else { @@ -68,6 +76,7 @@ const getCaseForExternalUser = ({ isAssociatedWithCase, isAuthorizedToGetCase, }) => { + filterDocketEntriesNotOnDocketRecord({ authorizedUser, caseRecord }); if (isAuthorizedToGetCase && isAssociatedWithCase) { return new Case(caseRecord, { authorizedUser }).validate().toRawObject(); } else { @@ -202,3 +211,18 @@ export const getCaseInteractor = async ( ); return caseDetailRaw; }; + +const filterDocketEntriesNotOnDocketRecord = ({ + authorizedUser, + caseRecord, +}: { + caseRecord: RawCase; + authorizedUser: AuthUser; +}) => { + caseRecord.docketEntries = caseRecord.docketEntries.filter( + d => + d.isOnDocketRecord || + (d.documentType === INITIAL_DOCUMENT_TYPES.stin.documentType && + authorizedUser.role === ROLES.irsSuperuser), + ); +}; diff --git a/shared/src/proxies/trialSessions/getBulkSpecialTrialSessionCopyNotesProxy.ts b/shared/src/proxies/trialSessions/getBulkSpecialTrialSessionCopyNotesProxy.ts new file mode 100644 index 00000000000..ccad809f5b1 --- /dev/null +++ b/shared/src/proxies/trialSessions/getBulkSpecialTrialSessionCopyNotesProxy.ts @@ -0,0 +1,19 @@ +import { + SpecialTrialSession, + TrialSessionWorkingCopyNotes, +} from '@shared/business/entities/trialSessions/SpecialTrialSessions'; +import { post } from '../requests'; + +export const getBulkSpecialTrialSessionCopyNotesInteractor = ( + applicationContext, + { + specialTrialSessions, + }: { + specialTrialSessions: Array; + }, +): Array => + post({ + applicationContext, + body: { specialTrialSessions }, + endpoint: '/trial-sessions/bulk-copy-notes', + }); diff --git a/web-api/src/app.ts b/web-api/src/app.ts index ba58a87ea99..7f3038e8a1c 100644 --- a/web-api/src/app.ts +++ b/web-api/src/app.ts @@ -71,6 +71,7 @@ import { generateTrialCalendarPdfLambda } from './lambdas/trialSessions/generate import { getAllFeatureFlagsLambda } from './lambdas/featureFlag/getAllFeatureFlagsLambda'; import { getAllUsersByRoleLambda } from '@web-api/lambdas/users/getAllUsersByRoleLambda'; import { getBlockedCasesLambda } from './lambdas/reports/getBlockedCasesLambda'; +import { getBulkTrialSessionCopyNotesLambda } from './lambdas/trialSessions/getBulkTrialSessionCopyNotesLambda'; import { getCalendaredCasesForTrialSessionLambda } from './lambdas/trialSessions/getCalendaredCasesForTrialSessionLambda'; import { getCaseDeadlinesForCaseLambda } from './lambdas/caseDeadline/getCaseDeadlinesForCaseLambda'; import { getCaseDeadlinesLambda } from './lambdas/caseDeadline/getCaseDeadlinesLambda'; @@ -921,6 +922,10 @@ app.delete( '/trial-sessions/:trialSessionId/working-copy', lambdaWrapper(getTrialSessionWorkingCopyLambda), ); + app.post( + '/trial-sessions/bulk-copy-notes', + lambdaWrapper(getBulkTrialSessionCopyNotesLambda), + ); app.put( '/trial-sessions/:trialSessionId/working-copy', lambdaWrapper(updateTrialSessionWorkingCopyLambda), diff --git a/web-api/src/business/useCases/trialSessions/getBulkTrialSessionCopyNotesInteractor.test.ts b/web-api/src/business/useCases/trialSessions/getBulkTrialSessionCopyNotesInteractor.test.ts new file mode 100644 index 00000000000..31b0033007d --- /dev/null +++ b/web-api/src/business/useCases/trialSessions/getBulkTrialSessionCopyNotesInteractor.test.ts @@ -0,0 +1,47 @@ +import { UnauthorizedError } from '@web-api/errors/errors'; +import { applicationContext } from '../../../../../shared/src/business/test/createTestApplicationContext'; +import { getBulkTrialSessionCopyNotesInteractor } from './getBulkTrialSessionCopyNotesInteractor'; +import { mockAdminUser, mockJudgeUser } from '@shared/test/mockAuthUsers'; + +const MOCK_WORKING_COPY_NOTES = [ + { + sessionNotes: 'Test notes', + trialSessionId: '123', + }, + { + sessionNotes: 'Test notes 2', + trialSessionId: '456', + }, +]; +describe('getBulkTrialSessionCopyNotesInteractor', () => { + beforeEach(() => { + applicationContext + .getPersistenceGateway() + .getBulkTrialSessionWorkingCopyNotes.mockReturnValue( + MOCK_WORKING_COPY_NOTES, + ); + }); + it('should throw an error if the user is unauthorized', async () => { + await expect( + getBulkTrialSessionCopyNotesInteractor( + applicationContext, + { specialTrialSessions: [] }, + mockAdminUser, + ), + ).rejects.toThrow(UnauthorizedError); + }); + + it('should return session notes for multiple trial sessions', async () => { + const result = await getBulkTrialSessionCopyNotesInteractor( + applicationContext, + { + specialTrialSessions: [ + { trialSessionId: '123', userId: '123' }, + { trialSessionId: '456', userId: '456' }, + ], + }, + mockJudgeUser, + ); + expect(result).toEqual(MOCK_WORKING_COPY_NOTES); + }); +}); diff --git a/web-api/src/business/useCases/trialSessions/getBulkTrialSessionCopyNotesInteractor.ts b/web-api/src/business/useCases/trialSessions/getBulkTrialSessionCopyNotesInteractor.ts new file mode 100644 index 00000000000..16ae542cac0 --- /dev/null +++ b/web-api/src/business/useCases/trialSessions/getBulkTrialSessionCopyNotesInteractor.ts @@ -0,0 +1,37 @@ +import { + ROLE_PERMISSIONS, + isAuthorized, +} from '../../../../../shared/src/authorization/authorizationClientService'; +import { ServerApplicationContext } from '@web-api/applicationContext'; +import { + SpecialTrialSession, + SpecialTrialSessionKey, + TrialSessionWorkingCopyNotes, +} from '@shared/business/entities/trialSessions/SpecialTrialSessions'; +import { UnauthorizedError } from '@web-api/errors/errors'; +import { UnknownAuthUser } from '@shared/business/entities/authUser/AuthUser'; + +export const getBulkTrialSessionCopyNotesInteractor = async ( + applicationContext: ServerApplicationContext, + { specialTrialSessions }: { specialTrialSessions: SpecialTrialSession[] }, + authorizedUser: UnknownAuthUser, +): Promise> => { + if (!isAuthorized(authorizedUser, ROLE_PERMISSIONS.TRIAL_SESSIONS)) { + throw new UnauthorizedError('Unauthorized'); + } + + const specialTrialSessionKeys: Array = + specialTrialSessions.map( + (t: SpecialTrialSession): SpecialTrialSessionKey => ({ + pk: `trial-session-working-copy|${t.trialSessionId}`, + sk: `user|${t.userId}`, + }), + ); + + return await applicationContext + .getPersistenceGateway() + .getBulkTrialSessionWorkingCopyNotes({ + applicationContext, + specialTrialSessions: specialTrialSessionKeys, + }); +}; diff --git a/web-api/src/getPersistenceGateway.ts b/web-api/src/getPersistenceGateway.ts index cc0cf0586d7..3874319bf80 100644 --- a/web-api/src/getPersistenceGateway.ts +++ b/web-api/src/getPersistenceGateway.ts @@ -55,6 +55,7 @@ import { getAllPendingMotionDocketEntriesForJudge } from '@web-api/persistence/e import { getAllUsersByRole } from '@web-api/persistence/elasticsearch/users/getAllUsersByRole'; import { getAllWebSocketConnections } from './persistence/dynamo/notifications/getAllWebSocketConnections'; import { getBlockedCases } from './persistence/elasticsearch/getBlockedCases'; +import { getBulkTrialSessionWorkingCopies } from './persistence/dynamo/trialSessions/getBulkTrialSessionWorkingCopies'; import { getCalendaredCasesForTrialSession } from './persistence/dynamo/trialSessions/getCalendaredCasesForTrialSession'; import { getCaseByDocketNumber } from './persistence/dynamo/cases/getCaseByDocketNumber'; import { getCaseDeadlinesByDateRange } from './persistence/elasticsearch/caseDeadlines/getCaseDeadlinesByDateRange'; @@ -286,6 +287,7 @@ const gatewayMethods = { getAllUsersByRole, getAllWebSocketConnections, getBlockedCases, + getBulkTrialSessionWorkingCopyNotes: getBulkTrialSessionWorkingCopies, getCalendaredCasesForTrialSession, getCaseByDocketNumber, getCaseDeadlinesByDateRange, diff --git a/web-api/src/lambdas/trialSessions/getBulkTrialSessionCopyNotesLambda.ts b/web-api/src/lambdas/trialSessions/getBulkTrialSessionCopyNotesLambda.ts new file mode 100644 index 00000000000..f43b76b1c7d --- /dev/null +++ b/web-api/src/lambdas/trialSessions/getBulkTrialSessionCopyNotesLambda.ts @@ -0,0 +1,18 @@ +import { UnknownAuthUser } from '@shared/business/entities/authUser/AuthUser'; +import { genericHandler } from '../../genericHandler'; +import { getBulkTrialSessionCopyNotesInteractor } from '@web-api/business/useCases/trialSessions/getBulkTrialSessionCopyNotesInteractor'; + +export const getBulkTrialSessionCopyNotesLambda = ( + event, + authorizedUser: UnknownAuthUser, +) => + genericHandler(event, async ({ applicationContext }) => { + const { specialTrialSessions } = JSON.parse(event.body || '{}'); + return await getBulkTrialSessionCopyNotesInteractor( + applicationContext, + { + specialTrialSessions, + }, + authorizedUser, + ); + }); diff --git a/web-api/src/persistence/dynamo/trialSessions/getBulkTrialSessionWorkingCopies.test.ts b/web-api/src/persistence/dynamo/trialSessions/getBulkTrialSessionWorkingCopies.test.ts new file mode 100644 index 00000000000..c55bfececdf --- /dev/null +++ b/web-api/src/persistence/dynamo/trialSessions/getBulkTrialSessionWorkingCopies.test.ts @@ -0,0 +1,92 @@ +import { applicationContext } from '../../../../../shared/src/business/test/createTestApplicationContext'; +import { batchGet } from '../../dynamodbClientService'; +import { getBulkTrialSessionWorkingCopies } from './getBulkTrialSessionWorkingCopies'; + +jest.mock('../../dynamodbClientService'); + +const batchGetMock = batchGet as jest.Mock; + +batchGetMock.mockReturnValue([ + { + caseMetadata: {}, + entityName: 'TrialSessionWorkingCopy', + filters: { + basisReached: true, + continued: true, + definiteTrial: true, + dismissed: true, + motionToDismiss: true, + probableSettlement: true, + probableTrial: true, + recall: true, + rule122: true, + setForTrial: true, + settled: true, + showAll: true, + statusUnassigned: true, + submittedCAV: true, + }, + pk: 'trial-session-working-copy|111ac21b-99f9-4321-98c8-b95db00af96b', + sessionNotes: 'Judge Colvin Super notes!', + sk: 'user|dabbad00-18d0-43ec-bafb-654e83405416', + sort: 'docket', + sortOrder: 'asc', + trialSessionId: '111ac21b-99f9-4321-98c8-b95db00af96b', + userId: 'dabbad00-18d0-43ec-bafb-654e83405416', + }, + { + caseMetadata: {}, + entityName: 'TrialSessionWorkingCopy', + filters: { + basisReached: true, + continued: true, + definiteTrial: true, + dismissed: true, + motionToDismiss: true, + probableSettlement: true, + probableTrial: true, + recall: true, + rule122: true, + setForTrial: true, + settled: true, + showAll: true, + statusUnassigned: true, + submittedCAV: true, + }, + pk: 'trial-session-working-copy|0d943468-bc2e-4631-84e3-b084cf5b1fbb', + sessionNotes: 'Cohen Cohen Cohen Notes', + sk: 'user|dabbad04-18d0-43ec-bafb-654e83405416', + sort: 'docket', + sortOrder: 'asc', + trialSessionId: '0d943468-bc2e-4631-84e3-b084cf5b1fbb', + userId: 'dabbad04-18d0-43ec-bafb-654e83405416', + }, +]); + +describe('getBulkTrialSessionWorkingCopies', () => { + it('should get the trial session notes by special session array', async () => { + const specialTrialSessions = [ + { pk: '123', sk: '456' }, + { pk: '456', sk: '789' }, + ]; + + const result = await getBulkTrialSessionWorkingCopies({ + applicationContext, + specialTrialSessions, + }); + expect(result).toEqual([ + { + sessionNotes: 'Judge Colvin Super notes!', + trialSessionId: '111ac21b-99f9-4321-98c8-b95db00af96b', + }, + { + sessionNotes: 'Cohen Cohen Cohen Notes', + trialSessionId: '0d943468-bc2e-4631-84e3-b084cf5b1fbb', + }, + ]); + expect(batchGetMock).toHaveBeenCalledWith({ + applicationContext, + keys: specialTrialSessions, + }); + }); +}); diff --git a/web-api/src/persistence/dynamo/trialSessions/getBulkTrialSessionWorkingCopies.ts b/web-api/src/persistence/dynamo/trialSessions/getBulkTrialSessionWorkingCopies.ts new file mode 100644 index 00000000000..84851d0be06 --- /dev/null +++ b/web-api/src/persistence/dynamo/trialSessions/getBulkTrialSessionWorkingCopies.ts @@ -0,0 +1,20 @@ +import { TDynamoRecord } from '../dynamoTypes'; +import { TrialSessionWorkingCopyNotes } from '@shared/business/entities/trialSessions/SpecialTrialSessions'; +import { batchGet } from '../../dynamodbClientService'; + +export const getBulkTrialSessionWorkingCopies = async ({ + applicationContext, + specialTrialSessions, +}: { + applicationContext: IApplicationContext; + specialTrialSessions: Array<{ pk: string; sk: string }>; +}): Promise> => { + const records: TDynamoRecord[] = await batchGet({ + applicationContext, + keys: specialTrialSessions, + }); + return records.map(record => ({ + sessionNotes: record.sessionNotes, + trialSessionId: record.trialSessionId, + })); +}; diff --git a/web-api/src/persistence/dynamo/trialSessions/getTrialSessionWorkingCopy.ts b/web-api/src/persistence/dynamo/trialSessions/getTrialSessionWorkingCopy.ts index a25f30ee988..c7d6f777b5d 100644 --- a/web-api/src/persistence/dynamo/trialSessions/getTrialSessionWorkingCopy.ts +++ b/web-api/src/persistence/dynamo/trialSessions/getTrialSessionWorkingCopy.ts @@ -1,14 +1,33 @@ import { get } from '../../dynamodbClientService'; -/** - * getTrialSessionWorkingCopy - * - * @param {object} providers the providers object - * @param {object} providers.applicationContext the application context - * @param {string} providers.trialSessionId the id of the trial session - * @param {string} providers.userId the id of the user - * @returns {Promise} the promise of the call to persistence - */ +type TrialSessionWorkingCopy = { + entityName: string; + sortOrder: string; + sk: string; + filters: { + definiteTrial: boolean; + probableTrial: boolean; + motionToDismiss: boolean; + settled: boolean; + dismissed: boolean; + basisReached: boolean; + continued: boolean; + submittedCAV: boolean; + showAll: boolean; + probableSettlement: boolean; + setForTrial: boolean; + recall: boolean; + rule122: boolean; + statusUnassigned: boolean; + }; + sort: string; + pk: string; + sessionNotes: string; + userId: string; + caseMetadata: object; + trialSessionId: string; +}; + export const getTrialSessionWorkingCopy = ({ applicationContext, trialSessionId, @@ -17,7 +36,7 @@ export const getTrialSessionWorkingCopy = ({ applicationContext: IApplicationContext; trialSessionId: string; userId: string; -}) => +}): TrialSessionWorkingCopy => get({ Key: { pk: `trial-session-working-copy|${trialSessionId}`, diff --git a/web-client/integration-tests/caseJourney.test.ts b/web-client/integration-tests/caseJourney.test.ts index 28a235e9f19..f22c01b715c 100644 --- a/web-client/integration-tests/caseJourney.test.ts +++ b/web-client/integration-tests/caseJourney.test.ts @@ -54,15 +54,15 @@ describe('Case journey', () => { loginAs(cerebralTest, 'irspractitioner@example.com'); respondentViewsDashboard(cerebralTest); - const documentCountPreStipDecision = 6; + const documentsOnRecordCount = 5; respondentAddsAnswer(cerebralTest, fakeFile, { - documentCount: documentCountPreStipDecision, + documentCount: documentsOnRecordCount, }); respondentAddsStipulatedDecision(cerebralTest, fakeFile, { - documentCount: documentCountPreStipDecision + 1, + documentCount: documentsOnRecordCount + 1, }); respondentAddsMotionWithBrief(cerebralTest, fakeFile, { - documentCount: documentCountPreStipDecision + 3, + documentCount: documentsOnRecordCount + 3, }); loginAs(cerebralTest, 'docketclerk@example.com'); diff --git a/web-client/integration-tests/petitionerFilesADocument.test.ts b/web-client/integration-tests/petitionerFilesADocument.test.ts index 5d7dabb49c9..1e336e04174 100644 --- a/web-client/integration-tests/petitionerFilesADocument.test.ts +++ b/web-client/integration-tests/petitionerFilesADocument.test.ts @@ -28,11 +28,11 @@ describe('petitioner files document', () => { loginAs(cerebralTest, 'petitioner@example.com'); petitionerViewsCaseDetail(cerebralTest, { - documentCount: 4, + documentCount: 3, }); petitionerFilesDocumentForCase(cerebralTest, fakeFile); petitionerViewsCaseDetailAfterFilingDocument(cerebralTest, { - documentCount: 8, + documentCount: 7, }); petitionerFilesAmendedMotion(cerebralTest, fakeFile); }); diff --git a/web-client/src/applicationContext.ts b/web-client/src/applicationContext.ts index a9cf426f7de..a23143e4caa 100644 --- a/web-client/src/applicationContext.ts +++ b/web-client/src/applicationContext.ts @@ -152,6 +152,7 @@ import { generateTrialCalendarPdfInteractor } from '../../shared/src/proxies/tri import { getAllFeatureFlagsInteractor } from '../../shared/src/proxies/featureFlag/getAllFeatureFlagsProxy'; import { getAllUsersByRoleInteractor } from '@shared/proxies/users/getAllUsersByRoleProxy'; import { getBlockedCasesInteractor } from '../../shared/src/proxies/reports/getBlockedCasesProxy'; +import { getBulkSpecialTrialSessionCopyNotesInteractor } from '@shared/proxies/trialSessions/getBulkSpecialTrialSessionCopyNotesProxy'; import { getCalendaredCasesForTrialSessionInteractor } from '../../shared/src/proxies/trialSessions/getCalendaredCasesForTrialSessionProxy'; import { getCaseDeadlinesForCaseInteractor } from '../../shared/src/proxies/caseDeadline/getCaseDeadlinesForCaseProxy'; import { getCaseDeadlinesInteractor } from '../../shared/src/proxies/caseDeadline/getCaseDeadlinesProxy'; @@ -438,6 +439,7 @@ const allUseCases = { getAllFeatureFlagsInteractor, getAllUsersByRoleInteractor, getBlockedCasesInteractor, + getBulkSpecialTrialSessionCopyNotesInteractor, getCalendaredCasesForTrialSessionInteractor, getCaseDeadlinesForCaseInteractor, getCaseDeadlinesInteractor, diff --git a/web-client/src/presenter/actions/TrialSession/getBulkSpecialTrialSessionCopyNotesAction.test.ts b/web-client/src/presenter/actions/TrialSession/getBulkSpecialTrialSessionCopyNotesAction.test.ts new file mode 100644 index 00000000000..a01d2018aad --- /dev/null +++ b/web-client/src/presenter/actions/TrialSession/getBulkSpecialTrialSessionCopyNotesAction.test.ts @@ -0,0 +1,58 @@ +import { applicationContextForClient as applicationContext } from '@web-client/test/createClientTestApplicationContext'; +import { getBulkSpecialTrialSessionCopyNotesAction } from './getBulkSpecialTrialSessionCopyNotesAction'; +import { presenter } from '../../presenter-mock'; +import { runAction } from '@web-client/presenter/test.cerebral'; + +describe('getBulkSpecialTrialSessionCopyNotesAction', () => { + beforeAll(() => { + presenter.providers.applicationContext = applicationContext; + applicationContext + .getUseCases() + .getBulkSpecialTrialSessionCopyNotesInteractor.mockResolvedValue([ + { + sessionNotes: 'notes', + trialSessionId: '123', + }, + ]); + }); + + it('call the use case to get the bulk special trial session copy notes', async () => { + const result = await runAction(getBulkSpecialTrialSessionCopyNotesAction, { + modules: { + presenter, + }, + props: { + trialSessions: [ + { + judge: { + userId: 'abc', + }, + sessionType: 'Special', + trialSessionId: '123', + }, + { + judge: { + userId: 'xyz', + }, + sessionType: 'Hybrid', + trialSessionId: '369', + }, + ], + }, + state: {}, + }); + + expect( + applicationContext.getUseCases() + .getBulkSpecialTrialSessionCopyNotesInteractor, + ).toHaveBeenCalledWith(expect.anything(), { + specialTrialSessions: [{ trialSessionId: '123', userId: 'abc' }], + }); + + expect(result.output).toEqual({ + specialTrialSessionCopyNotes: [ + { sessionNotes: 'notes', trialSessionId: '123' }, + ], + }); + }); +}); diff --git a/web-client/src/presenter/actions/TrialSession/getBulkSpecialTrialSessionCopyNotesAction.ts b/web-client/src/presenter/actions/TrialSession/getBulkSpecialTrialSessionCopyNotesAction.ts new file mode 100644 index 00000000000..704bbbede2c --- /dev/null +++ b/web-client/src/presenter/actions/TrialSession/getBulkSpecialTrialSessionCopyNotesAction.ts @@ -0,0 +1,27 @@ +import { SESSION_TYPES } from '@shared/business/entities/EntityConstants'; +import { + SpecialTrialSession, + TrialSessionWorkingCopyNotes, +} from '@shared/business/entities/trialSessions/SpecialTrialSessions'; +const getSpecialTrialSessions = trialSessions => + trialSessions + .filter(trialSession => trialSession.sessionType === SESSION_TYPES.special) + .map(trialSession => ({ + trialSessionId: trialSession.trialSessionId, + userId: trialSession.judge?.userId, + })); +export const getBulkSpecialTrialSessionCopyNotesAction = async ({ + applicationContext, + props, +}: ActionProps<{ + trialSessions: SpecialTrialSession[]; +}>) => { + const specialTrialSessions = getSpecialTrialSessions(props.trialSessions); + const specialTrialSessionCopyNotes: Array = + await applicationContext + .getUseCases() + .getBulkSpecialTrialSessionCopyNotesInteractor(applicationContext, { + specialTrialSessions, + }); + return { specialTrialSessionCopyNotes }; +}; diff --git a/web-client/src/presenter/actions/TrialSession/setBulkSpecialTrialSessionCopyNotesAction.ts b/web-client/src/presenter/actions/TrialSession/setBulkSpecialTrialSessionCopyNotesAction.ts new file mode 100644 index 00000000000..3d9f51ef728 --- /dev/null +++ b/web-client/src/presenter/actions/TrialSession/setBulkSpecialTrialSessionCopyNotesAction.ts @@ -0,0 +1,14 @@ +import { state } from '@web-client/presenter/app.cerebral'; + +export const setBulkSpecialTrialSessionCopyNotesAction = ({ props, store }) => { + const specialTrialSessionCopyNotesObject = + props.specialTrialSessionCopyNotes.reduce((acc, specialTrialSession) => { + acc[specialTrialSession.trialSessionId] = + specialTrialSession.sessionNotes; + return acc; + }, {}); + store.set( + state.trialSessionsPage.specialTrialSessionCopyNotesObject, + specialTrialSessionCopyNotesObject, + ); +}; diff --git a/web-client/src/presenter/computeds/filePetitionHelper.test.ts b/web-client/src/presenter/computeds/filePetitionHelper.test.ts index 84133e4d1ce..178fc7fc57a 100644 --- a/web-client/src/presenter/computeds/filePetitionHelper.test.ts +++ b/web-client/src/presenter/computeds/filePetitionHelper.test.ts @@ -387,7 +387,7 @@ describe('filePetitionHelper', () => { user: petitionerUser, }, }); - expect(result.primaryContactNameLabel).toEqual('Full Name'); + expect(result.primaryContactNameLabel).toEqual('Full name'); }); it('should return the correct primary contact name label when filing as a private practitioner', () => { const result = runCompute(filePetitionHelper, { diff --git a/web-client/src/presenter/computeds/filePetitionHelper.ts b/web-client/src/presenter/computeds/filePetitionHelper.ts index 0d9dea922ea..ee4ac7a2f8d 100644 --- a/web-client/src/presenter/computeds/filePetitionHelper.ts +++ b/web-client/src/presenter/computeds/filePetitionHelper.ts @@ -49,7 +49,7 @@ export const filePetitionHelper = ( const otherFilingOptions = getOtherFilingOptions(isPetitioner); const primaryContactNameLabel = isPetitioner - ? 'Full Name' + ? 'Full name' : 'Petitioner’s full name'; const businessFieldNames = getBusinessFieldLabels(businessType); diff --git a/web-client/src/presenter/computeds/formattedDocketEntries.test.ts b/web-client/src/presenter/computeds/formattedDocketEntries.test.ts index 6742f25475f..18e73b6165d 100644 --- a/web-client/src/presenter/computeds/formattedDocketEntries.test.ts +++ b/web-client/src/presenter/computeds/formattedDocketEntries.test.ts @@ -1008,7 +1008,7 @@ describe('formattedDocketEntries', () => { expect(result).toEqual([ { icon: ['fas', 'file-alt'], - title: 'is paper', + title: 'Is paper', }, ]); }); @@ -1027,7 +1027,7 @@ describe('formattedDocketEntries', () => { expect(result).toEqual([ { icon: ['fas', 'thumbtack'], - title: 'in progress', + title: 'In progress', }, ]); }); @@ -1045,7 +1045,7 @@ describe('formattedDocketEntries', () => { expect(result).toEqual([ { icon: ['fa', 'star'], - title: 'is untouched', + title: 'Is untouched', }, ]); }); @@ -1063,7 +1063,7 @@ describe('formattedDocketEntries', () => { { className: 'fa-spin spinner', icon: ['fa-spin', 'spinner'], - title: 'is loading', + title: 'Is loading', }, ]); }); diff --git a/web-client/src/presenter/computeds/formattedDocketEntries.ts b/web-client/src/presenter/computeds/formattedDocketEntries.ts index cd44210db60..56d44b99b68 100644 --- a/web-client/src/presenter/computeds/formattedDocketEntries.ts +++ b/web-client/src/presenter/computeds/formattedDocketEntries.ts @@ -34,23 +34,23 @@ export const setupIconsToDisplay = ({ formattedResult, isExternalUser }) => { } else if (formattedResult.isPaper) { iconsToDisplay.push({ icon: ['fas', 'file-alt'], - title: 'is paper', + title: 'Is paper', }); } else if (formattedResult.isInProgress) { iconsToDisplay.push({ icon: ['fas', 'thumbtack'], - title: 'in progress', + title: 'In progress', }); } else if (formattedResult.qcNeeded) { iconsToDisplay.push({ icon: ['fa', 'star'], - title: 'is untouched', + title: 'Is untouched', }); } else if (formattedResult.showLoadingIcon) { iconsToDisplay.push({ className: 'fa-spin spinner', icon: ['fa-spin', 'spinner'], - title: 'is loading', + title: 'Is loading', }); } diff --git a/web-client/src/presenter/computeds/formattedTrialSessionDetails.test.ts b/web-client/src/presenter/computeds/formattedTrialSessionDetails.test.ts index cd255d525bb..23457b66448 100644 --- a/web-client/src/presenter/computeds/formattedTrialSessionDetails.test.ts +++ b/web-client/src/presenter/computeds/formattedTrialSessionDetails.test.ts @@ -14,6 +14,7 @@ import { applicationContextForClient as applicationContext } from '@web-client/t import { colvinsChambersUser, docketClerkUser, + judgeUser, trialClerkUser, } from '../../../../shared/src/test/mockUsers'; import { formattedTrialSessionDetails as formattedTrialSessionDetailsComputed } from './formattedTrialSessionDetails'; @@ -377,6 +378,25 @@ describe('formattedTrialSessionDetails', () => { }); }); + it('should NOT allow judge to edit trial session info page', () => { + mockTrialSession = { + ...TRIAL_SESSION, + sessionStatus: SESSION_STATUS_GROUPS.open, + startDate: FUTURE_DATE, + }; + + const result: any = runCompute(formattedTrialSessionDetails, { + state: { + trialSession: {}, + user: judgeUser, + }, + }); + + expect(result).toMatchObject({ + canEdit: false, + }); + }); + it('should be false when trial session start date is in the future and it is closed', () => { mockTrialSession = { ...TRIAL_SESSION, diff --git a/web-client/src/presenter/computeds/formattedTrialSessionDetails.ts b/web-client/src/presenter/computeds/formattedTrialSessionDetails.ts index f257df9f7ef..749e8f46284 100644 --- a/web-client/src/presenter/computeds/formattedTrialSessionDetails.ts +++ b/web-client/src/presenter/computeds/formattedTrialSessionDetails.ts @@ -93,6 +93,7 @@ export const formattedTrialSessionDetails = ( const user = get(state.user); const isChambersUser = user.role === USER_ROLES.chambers; + const isJudgeUser = user.role === USER_ROLES.judge; const trialDateInFuture = trialDateFormatted > nowDateFormatted; const docketClerkCanEditCheck = sessionType => { const editableSessionTypes = ['Special', 'Motion/Hearing']; @@ -103,7 +104,8 @@ export const formattedTrialSessionDetails = ( canEdit = trialDateInFuture && formattedTrialSession.sessionStatus !== SESSION_STATUS_GROUPS.closed && - !isChambersUser; + !isChambersUser && + !isJudgeUser; if (user.role === USER_ROLES.docketClerk && canEdit) { canEdit = docketClerkCanEditCheck(formattedTrialSession.sessionType); diff --git a/web-client/src/presenter/sequences/gotoTrialSessionsSequence.ts b/web-client/src/presenter/sequences/gotoTrialSessionsSequence.ts index 63e3b3fe985..f001c1aa7fd 100644 --- a/web-client/src/presenter/sequences/gotoTrialSessionsSequence.ts +++ b/web-client/src/presenter/sequences/gotoTrialSessionsSequence.ts @@ -1,6 +1,7 @@ import { TrialSessionsFilters } from '@web-client/presenter/state/trialSessionsPageState'; import { clearErrorAlertsAction } from '../actions/clearErrorAlertsAction'; import { closeMobileMenuAction } from '../actions/closeMobileMenuAction'; +import { getBulkSpecialTrialSessionCopyNotesAction } from '../actions/TrialSession/getBulkSpecialTrialSessionCopyNotesAction'; import { getJudgeForCurrentUserAction } from '../actions/getJudgeForCurrentUserAction'; import { getNotificationsAction } from '../actions/getNotificationsAction'; import { getTrialSessionsAction } from '../actions/TrialSession/getTrialSessionsAction'; @@ -8,6 +9,7 @@ import { getUsersInSectionAction } from '../actions/getUsersInSectionAction'; import { parallel } from 'cerebral/factories'; import { resetTrialSessionsFiltersAction } from '@web-client/presenter/actions/TrialSession/resetTrialSessionsFiltersAction'; import { setAllAndCurrentJudgesAction } from '../actions/setAllAndCurrentJudgesAction'; +import { setBulkSpecialTrialSessionCopyNotesAction } from '../actions/TrialSession/setBulkSpecialTrialSessionCopyNotesAction'; import { setJudgeUserAction } from '../actions/setJudgeUserAction'; import { setNotificationsAction } from '../actions/setNotificationsAction'; import { setTrialSessionsFiltersAction } from '@web-client/presenter/actions/TrialSession/setTrialSessionsFiltersAction'; @@ -31,6 +33,10 @@ export const gotoTrialSessionsSequence = setAllAndCurrentJudgesAction, ], ]), + [ + getBulkSpecialTrialSessionCopyNotesAction, + setBulkSpecialTrialSessionCopyNotesAction, + ], trialSessionQueryParamsAction, setTrialSessionsFiltersAction, setupCurrentPageAction('TrialSessions'), diff --git a/web-client/src/presenter/state/trialSessionsPageState.ts b/web-client/src/presenter/state/trialSessionsPageState.ts index a198a815870..4a3b37a527a 100644 --- a/web-client/src/presenter/state/trialSessionsPageState.ts +++ b/web-client/src/presenter/state/trialSessionsPageState.ts @@ -19,6 +19,7 @@ const filters: TrialSessionsFilters = { export const initialTrialSessionPageState = { filters, + specialTrialSessionCopyNotesObject: {} as { trialSessionId: string }, trialSessions: [] as TrialSessionInfoDTO[], }; diff --git a/web-client/src/styles/custom.scss b/web-client/src/styles/custom.scss index 1e50390b8af..1972fa2c42c 100644 --- a/web-client/src/styles/custom.scss +++ b/web-client/src/styles/custom.scss @@ -2367,3 +2367,22 @@ button.change-scanner-button { border-bottom: 1px solid !important; margin-bottom: 5px !important; } + +.actions-buttons { + @media only screen and (max-width: ($medium-screen - 1)) { + display: flex; + flex-direction: column; + gap: 0.5rem; + } +} + +.session-card { + @media only screen and (max-width: ($medium-screen - 1)) { + width: 100%; + + .action-button-wrapper { + display: inline-flex; + margin: 0; + } + } +} diff --git a/web-client/src/views/CaseDeadlines/CaseDeadlines.tsx b/web-client/src/views/CaseDeadlines/CaseDeadlines.tsx index c109f979b74..6c7d87d212e 100644 --- a/web-client/src/views/CaseDeadlines/CaseDeadlines.tsx +++ b/web-client/src/views/CaseDeadlines/CaseDeadlines.tsx @@ -130,7 +130,7 @@ export const CaseDeadlines = connect( aria-hidden="true" className="consolidated-case-column" > - Docket No. + Docket No. Case Title Description Judge diff --git a/web-client/src/views/CaseInventoryReport/CaseInventoryReport.tsx b/web-client/src/views/CaseInventoryReport/CaseInventoryReport.tsx index 443b069723c..fec7a912e1a 100644 --- a/web-client/src/views/CaseInventoryReport/CaseInventoryReport.tsx +++ b/web-client/src/views/CaseInventoryReport/CaseInventoryReport.tsx @@ -118,7 +118,7 @@ export const CaseInventoryReport = connect( Consolidated Case Indicator - Docket No. + Docket No. Case Title {caseInventoryReportHelper.showJudgeColumn && ( Judge diff --git a/web-client/src/views/DocketRecord/DocumentViewer.tsx b/web-client/src/views/DocketRecord/DocumentViewer.tsx index a7fb4f384b9..fa3e8970303 100644 --- a/web-client/src/views/DocketRecord/DocumentViewer.tsx +++ b/web-client/src/views/DocketRecord/DocumentViewer.tsx @@ -99,7 +99,7 @@ export const DocumentViewer = connect( )} diff --git a/web-client/src/views/DocketRecord/FilingsAndProceedings.tsx b/web-client/src/views/DocketRecord/FilingsAndProceedings.tsx index 5fc73164b47..2d764ed0578 100644 --- a/web-client/src/views/DocketRecord/FilingsAndProceedings.tsx +++ b/web-client/src/views/DocketRecord/FilingsAndProceedings.tsx @@ -137,7 +137,7 @@ export const FilingsAndProceedings = connect< )} diff --git a/web-client/src/views/JudgeActivityReport/SubmittedAndCav.tsx b/web-client/src/views/JudgeActivityReport/SubmittedAndCav.tsx index 14d64c86221..bce97bcc484 100644 --- a/web-client/src/views/JudgeActivityReport/SubmittedAndCav.tsx +++ b/web-client/src/views/JudgeActivityReport/SubmittedAndCav.tsx @@ -40,7 +40,7 @@ export const SubmittedAndCav = connect( Consolidated Case Indicator - Docket No. + Docket No. No. of Cases {!message.isRead && ( - Zip code + ZIP code - Zip code + ZIP code - Zip code + ZIP code - Zip code + ZIP code Docket No. - + Case Title Petitioner Counsel Respondent Counsel @@ -91,7 +91,7 @@ export const EligibleCases = connect( {item.isManuallyAdded && ( - + - Docket No. - + Docket No. + Case Title Petitioner Counsel Respondent Counsel @@ -56,7 +56,7 @@ export const OpenCases = connect( {item.isManuallyAdded && ( - +
-
+

Details

diff --git a/web-client/src/views/TrialSessionWorkingCopy/CaseListRowTrialSession.tsx b/web-client/src/views/TrialSessionWorkingCopy/CaseListRowTrialSession.tsx index 0aeeb1b3bbf..bec7712937d 100644 --- a/web-client/src/views/TrialSessionWorkingCopy/CaseListRowTrialSession.tsx +++ b/web-client/src/views/TrialSessionWorkingCopy/CaseListRowTrialSession.tsx @@ -35,7 +35,7 @@ const getCaseRow = ({ {formattedCase.isManuallyAdded && ( - + +

Assignments

diff --git a/web-client/src/views/TrialSessionWorkingCopy/SessionNotes.tsx b/web-client/src/views/TrialSessionWorkingCopy/SessionNotes.tsx index 9d9f74fc383..e5eddee08de 100644 --- a/web-client/src/views/TrialSessionWorkingCopy/SessionNotes.tsx +++ b/web-client/src/views/TrialSessionWorkingCopy/SessionNotes.tsx @@ -19,54 +19,52 @@ export const SessionNotes = connect( }) { return ( <> -
-
+
+
{!sessionNotes && ( - +
+ +
)} -

Session Notes

{sessionNotes && ( <> -
- -
-
-
- -
-
- -
+
+ +
)} +

Session Notes

+
+ +
diff --git a/web-client/src/views/TrialSessionWorkingCopy/TrialSessionWorkingCopy.tsx b/web-client/src/views/TrialSessionWorkingCopy/TrialSessionWorkingCopy.tsx index 7aeb2063dc6..3036097bf9c 100644 --- a/web-client/src/views/TrialSessionWorkingCopy/TrialSessionWorkingCopy.tsx +++ b/web-client/src/views/TrialSessionWorkingCopy/TrialSessionWorkingCopy.tsx @@ -104,11 +104,11 @@ export const TrialSessionWorkingCopy = connect( -
-
+
+
-
+
diff --git a/web-client/src/views/TrialSessionWorkingCopy/WorkingCopySessionList.tsx b/web-client/src/views/TrialSessionWorkingCopy/WorkingCopySessionList.tsx index 512f5ab0ff5..da727118900 100644 --- a/web-client/src/views/TrialSessionWorkingCopy/WorkingCopySessionList.tsx +++ b/web-client/src/views/TrialSessionWorkingCopy/WorkingCopySessionList.tsx @@ -49,7 +49,7 @@ export const WorkingCopySessionList = connect( onClickSequence={toggleWorkingCopySortSequence} /> - + Case Title (optional) { + onSelect={(tabName: 'calendared' | 'new') => { if (tabName === trialSessionsPageFilters.currentTab) { return; } diff --git a/web-client/src/views/TrialSessions/TrialSessionsTable.tsx b/web-client/src/views/TrialSessions/TrialSessionsTable.tsx index a4ba3e0011c..07cf8c16827 100644 --- a/web-client/src/views/TrialSessions/TrialSessionsTable.tsx +++ b/web-client/src/views/TrialSessions/TrialSessionsTable.tsx @@ -1,5 +1,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Paginator } from '@web-client/ustc-ui/Pagination/Paginator'; +import { SESSION_TYPES } from '@shared/business/entities/EntityConstants'; import { connect } from '@web-client/presenter/shared.cerebral'; import { focusPaginatorTop } from '@web-client/presenter/utilities/focusPaginatorTop'; import { @@ -85,57 +86,81 @@ export const TrialSessionsTable = connect( ); } if (isTrialSessionRow(row)) { + const additionalRow = row.sessionType === + SESSION_TYPES.special && ( + + + + + +
+ + Special Session Notes:{' '} + + { + trialSessionsPage + .specialTrialSessionCopyNotesObject[ + row.trialSessionId + ] + } +
+ + + ); return ( - - - {row.showAlertForNOTTReminder && ( - - )} - {row.formattedStartDate} - - {row.formattedEstimatedEndDate} - - {row.swingSession && ( - - )} - - + - + {row.showAlertForNOTTReminder && ( + + )} + {row.formattedStartDate} + + {row.formattedEstimatedEndDate} + + {row.swingSession && ( + + )} + + - {row.trialLocation} - - - {row.proceedingType} - {row.sessionType} - {row.judge.name} - {trialSessionsHelper.showNoticeIssued && ( - {row.formattedNoticeIssuedDate} - )} - {trialSessionsHelper.showSessionStatus && ( - {row.sessionStatus} - )} - + + {row.trialLocation} + + + {row.proceedingType} + {row.sessionType} + {row.judge.name} + {trialSessionsHelper.showNoticeIssued && ( + {row.formattedNoticeIssuedDate} + )} + {trialSessionsHelper.showSessionStatus && ( + {row.sessionStatus} + )} + + {additionalRow} + ); } diff --git a/web-client/src/views/WorkQueue/IndividualWorkQueueInbox.tsx b/web-client/src/views/WorkQueue/IndividualWorkQueueInbox.tsx index f4d16ef8066..f9b04def155 100644 --- a/web-client/src/views/WorkQueue/IndividualWorkQueueInbox.tsx +++ b/web-client/src/views/WorkQueue/IndividualWorkQueueInbox.tsx @@ -72,7 +72,7 @@ export const IndividualWorkQueueInbox = connect( {item.showUnreadStatusIcon && ( {!item.isRead && ( {item.showUnassignedIcon && (