Skip to content

Commit

Permalink
adds synonyms moderation front end (#2538)
Browse files Browse the repository at this point in the history
Resolves #2116.
  • Loading branch information
Courey authored Oct 16, 2024
1 parent 6f9816f commit 14f8268
Show file tree
Hide file tree
Showing 9 changed files with 810 additions and 176 deletions.
162 changes: 150 additions & 12 deletions __tests__/synonyms.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,65 @@ import type { AWSError, DynamoDB } from 'aws-sdk'
import * as awsSDKMock from 'aws-sdk-mock'
import crypto from 'crypto'

import type { Circular } from '~/routes/circulars/circulars.lib'
import { createSynonyms, putSynonyms } from '~/routes/synonyms/synonyms.server'

jest.mock('@architect/functions')
const synonymId = 'abcde-abcde-abcde-abcde-abcde'
const exampleCirculars = [
{
Items: [
{
circularId: 1234556,
subject: 'subject 1',
body: 'very intelligent things',
eventId: 'eventId1',
createdOn: 12345567,
submitter: 'steve',
} as Circular,
],
},
{
Items: [
{
circularId: 1230000,
subject: 'subject 2',
body: 'more intelligent things',
eventId: 'eventId2',
createdOn: 12345560,
submitter: 'steve',
} as Circular,
],
},
{ Items: [] },
]

describe('createSynonyms', () => {
beforeAll(() => {
beforeEach(() => {
const mockBatchWrite = jest.fn()
const mockQuery = jest.fn()

const mockClient = {
batchWrite: mockBatchWrite,
query: mockQuery,
}
;(tables as unknown as jest.Mock).mockResolvedValue({

;(tables as unknown as jest.Mock).mockReturnValue({
_doc: mockClient,
name: () => {
return 'synonyms'
},
circulars: {
query: mockQuery
.mockReturnValueOnce(exampleCirculars[0])
.mockReturnValueOnce(exampleCirculars[1]),
},
})

jest.spyOn(crypto, 'randomUUID').mockReturnValue(synonymId)
})

afterAll(() => {
afterEach(() => {
jest.restoreAllMocks()
})

Expand All @@ -48,12 +85,51 @@ describe('createSynonyms', () => {

expect(result).toBe(synonymId)
})

test('createSynonyms with nonexistent eventId throws Response 400', async () => {
const mockBatchWriteItem = jest.fn(
(
params: DynamoDB.DocumentClient.BatchWriteItemInput,
callback: (
err: AWSError | null,
data?: DynamoDB.DocumentClient.BatchWriteItemOutput
) => void
) => {
expect(params.RequestItems.synonyms).toBeDefined()
callback(null, {})
}
)
awsSDKMock.mock('DynamoDB', 'batchWriteItem', mockBatchWriteItem)

const synonymousEventIds = ['eventId1', 'nope']
try {
await createSynonyms(synonymousEventIds)
} catch (error) {
// eslint-disable-next-line jest/no-conditional-expect
expect(error).toBeInstanceOf(Response)
const convertedError = error as Response
// eslint-disable-next-line jest/no-conditional-expect
expect(convertedError.status).toBe(400)
const errorMessage = await convertedError.text()
// eslint-disable-next-line jest/no-conditional-expect
expect(errorMessage).toBe('eventId does not exist')
}
})
})

describe('putSynonyms', () => {
const mockBatchWrite = jest.fn()
const mockQuery = jest.fn()

beforeAll(() => {
jest.spyOn(crypto, 'randomUUID').mockReturnValue(synonymId)
})

afterAll(() => {
jest.restoreAllMocks()
awsSDKMock.restore('DynamoDB')
})
test('putSynonyms should not write to DynamoDB if no additions or subtractions', async () => {
const mockClient = {
batchWrite: mockBatchWrite,
}
Expand All @@ -63,23 +139,60 @@ describe('putSynonyms', () => {
return 'synonyms'
},
})

jest.spyOn(crypto, 'randomUUID').mockReturnValue(synonymId)
})

afterAll(() => {
jest.restoreAllMocks()
awsSDKMock.restore('DynamoDB')
})
test('putSynonyms should not write to DynamoDB if no additions or subtractions', async () => {
awsSDKMock.mock('DynamoDB.DocumentClient', 'batchWrite', mockBatchWrite)

await putSynonyms({ synonymId })

expect(mockBatchWrite).not.toHaveBeenCalled()
})

test('putSynonyms should throw 400 response if there are invalid additions', async () => {
const mockClient = {
batchWrite: mockBatchWrite,
query: mockQuery,
}

;(tables as unknown as jest.Mock).mockReturnValue({
_doc: mockClient,
name: () => {
return 'synonyms'
},
circulars: {
query: mockQuery.mockReturnValueOnce(exampleCirculars[2]),
},
})
awsSDKMock.mock('DynamoDB.DocumentClient', 'batchWrite', mockBatchWrite)
try {
await putSynonyms({ synonymId, additions: ["doesn't exist"] })
} catch (error) {
// eslint-disable-next-line jest/no-conditional-expect
expect(error).toBeInstanceOf(Response)
const convertedError = error as Response
// eslint-disable-next-line jest/no-conditional-expect
expect(convertedError.status).toBe(400)
const errorMessage = await convertedError.text()
// eslint-disable-next-line jest/no-conditional-expect
expect(errorMessage).toBe('eventId does not exist')
}
})

test('putSynonyms should write to DynamoDB if there are additions', async () => {
const mockClient = {
batchWrite: mockBatchWrite,
query: mockQuery,
}

;(tables as unknown as jest.Mock).mockReturnValue({
_doc: mockClient,
name: () => {
return 'synonyms'
},
circulars: {
query: mockQuery
.mockReturnValueOnce(exampleCirculars[0])
.mockReturnValueOnce(exampleCirculars[1]),
},
})
awsSDKMock.mock('DynamoDB.DocumentClient', 'batchWrite', mockBatchWrite)
const additions = ['eventId1', 'eventId2']
await putSynonyms({ synonymId, additions })
Expand Down Expand Up @@ -109,6 +222,15 @@ describe('putSynonyms', () => {
})

test('putSynonyms should write to DynamoDB if there are subtractions', async () => {
const mockClient = {
batchWrite: mockBatchWrite,
}
;(tables as unknown as jest.Mock).mockResolvedValue({
_doc: mockClient,
name: () => {
return 'synonyms'
},
})
awsSDKMock.mock('DynamoDB.DocumentClient', 'batchWrite', mockBatchWrite)

const subtractions = ['eventId3', 'eventId4']
Expand All @@ -126,6 +248,22 @@ describe('putSynonyms', () => {
})

test('putSynonyms should write to DynamoDB if there are additions and subtractions', async () => {
const mockClient = {
batchWrite: mockBatchWrite,
query: mockQuery,
}

;(tables as unknown as jest.Mock).mockReturnValue({
_doc: mockClient,
name: () => {
return 'synonyms'
},
circulars: {
query: mockQuery
.mockReturnValueOnce(exampleCirculars[0])
.mockReturnValueOnce(exampleCirculars[1]),
},
})
awsSDKMock.mock('DynamoDB.DocumentClient', 'batchWrite', mockBatchWrite)

const additions = ['eventId1', 'eventId2']
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ function getPageLink({
return searchString && `?${searchString}`
}

export default function ({
export default function Pagination({
page,
totalPages,
...queryStringProps
Expand Down
61 changes: 61 additions & 0 deletions app/components/pagination/PaginationSelectionFooter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*!
* Copyright © 2023 United States Government as represented by the
* Administrator of the National Aeronautics and Space Administration.
* All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import { useSubmit } from '@remix-run/react'
import { Select } from '@trussworks/react-uswds'

import Pagination from './Pagination'

export default function PaginationSelectionFooter({
page,
totalPages,
limit,
query,
form,
}: {
page: number
totalPages: number
limit?: number
query?: string
form: string
}) {
const submit = useSubmit()
return (
<div className="display-flex flex-row flex-wrap">
<div className="display-flex flex-align-self-center margin-right-2 width-auto">
<div>
<Select
id="limit"
title="Number of results per page"
className="width-auto height-5 padding-y-0 margin-y-0"
name="limit"
defaultValue="100"
form={form}
onChange={({ target: { form } }) => {
submit(form)
}}
>
<option value="10">10 / page</option>
<option value="20">20 / page</option>
<option value="50">50 / page</option>
<option value="100">100 / page</option>
</Select>
</div>
</div>
<div className="display-flex flex-fill">
{totalPages > 1 && (
<Pagination
query={query}
page={page}
limit={limit}
totalPages={totalPages}
/>
)}
</div>
</div>
)
}
52 changes: 9 additions & 43 deletions app/routes/circulars._archive._index/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,7 @@ import {
useSearchParams,
useSubmit,
} from '@remix-run/react'
import {
Alert,
Button,
Icon,
Label,
Select,
TextInput,
} from '@trussworks/react-uswds'
import { Alert, Button, Icon, Label, TextInput } from '@trussworks/react-uswds'
import clamp from 'lodash/clamp'
import { useId, useState } from 'react'

Expand All @@ -41,13 +34,13 @@ import {
putVersion,
search,
} from '../circulars/circulars.server'
import CircularPagination from './CircularPagination'
import CircularsHeader from './CircularsHeader'
import CircularsIndex from './CircularsIndex'
import { DateSelector } from './DateSelectorMenu'
import { SortSelector } from './SortSelectorButton'
import Hint from '~/components/Hint'
import { ToolbarButtonGroup } from '~/components/ToolbarButtonGroup'
import PaginationSelectionFooter from '~/components/pagination/PaginationSelectionFooter'
import { origin } from '~/lib/env.server'
import { getFormDataString } from '~/lib/utils'
import { postZendeskRequest } from '~/lib/zendesk.server'
Expand Down Expand Up @@ -281,40 +274,13 @@ export default function () {
totalItems={totalItems}
query={query}
/>
<div className="display-flex flex-row flex-wrap">
<div className="display-flex flex-align-self-center margin-right-2 width-auto">
<div>
<Select
id="limit"
title="Number of results per page"
className="width-auto height-5 padding-y-0 margin-y-0"
name="limit"
defaultValue="100"
form={formId}
onChange={({ target: { form } }) => {
submit(form)
}}
>
<option value="10">10 / page</option>
<option value="20">20 / page</option>
<option value="50">50 / page</option>
<option value="100">100 / page</option>
</Select>
</div>
</div>
<div className="display-flex flex-fill">
{totalPages > 1 && (
<CircularPagination
query={query}
page={page}
limit={parseInt(limit)}
totalPages={totalPages}
startDate={startDate}
endDate={endDate}
/>
)}
</div>
</div>
<PaginationSelectionFooter
query={query}
page={page}
limit={parseInt(limit)}
totalPages={totalPages}
form={formId}
/>
</>
)}
</>
Expand Down
Loading

0 comments on commit 14f8268

Please sign in to comment.