Skip to content
This repository has been archived by the owner on Jan 9, 2023. It is now read-only.

feat(incidents): filter incidents #2087

Merged
merged 9 commits into from
May 20, 2020
17 changes: 15 additions & 2 deletions src/__tests__/incidents/incidents-slice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import thunk from 'redux-thunk'

import IncidentRepository from '../../clients/db/IncidentRepository'
import incidents, {
fetchIncidents,
fetchIncidentsStart,
fetchIncidentsSuccess,
searchIncidents,
filter,
} from '../../incidents/incidents-slice'
import Incident from '../../model/Incident'
import { RootState } from '../../store'
Expand Down Expand Up @@ -39,12 +40,24 @@ describe('Incidents Slice', () => {
jest.spyOn(IncidentRepository, 'findAll').mockResolvedValue(expectedIncidents)
const store = mockStore()

await store.dispatch(fetchIncidents())
await store.dispatch(searchIncidents(filter.all))

expect(store.getActions()[0]).toEqual(fetchIncidentsStart())
expect(IncidentRepository.findAll).toHaveBeenCalledTimes(1)
expect(store.getActions()[1]).toEqual(fetchIncidentsSuccess(expectedIncidents))
})

it('should fetch incidents filtering by status', async () => {
const expectedIncidents = [{ id: '123' }] as Incident[]
jest.spyOn(IncidentRepository, 'search').mockResolvedValue(expectedIncidents)
const store = mockStore()

await store.dispatch(searchIncidents(filter.reported))

expect(store.getActions()[0]).toEqual(fetchIncidentsStart())
expect(IncidentRepository.search).toHaveBeenCalledTimes(1)
expect(store.getActions()[1]).toEqual(fetchIncidentsSuccess(expectedIncidents))
})
})
})
})
20 changes: 19 additions & 1 deletion src/__tests__/incidents/list/ViewIncidents.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import thunk from 'redux-thunk'

import * as breadcrumbUtil from '../../../breadcrumbs/useAddBreadcrumbs'
import IncidentRepository from '../../../clients/db/IncidentRepository'
import { filter } from '../../../incidents/incidents-slice'
import ViewIncidents from '../../../incidents/list/ViewIncidents'
import Incident from '../../../model/Incident'
import Permissions from '../../../model/Permissions'
Expand Down Expand Up @@ -41,6 +42,7 @@ describe('View Incidents', () => {
jest.spyOn(titleUtil, 'default')
jest.spyOn(ButtonBarProvider, 'useButtonToolbarSetter').mockReturnValue(setButtonToolBarSpy)
jest.spyOn(IncidentRepository, 'findAll').mockResolvedValue(expectedIncidents)
jest.spyOn(IncidentRepository, 'search').mockResolvedValue(expectedIncidents)

history = createMemoryHistory()
history.push(`/incidents`)
Expand Down Expand Up @@ -71,7 +73,23 @@ describe('View Incidents', () => {
wrapper.update()
return wrapper
}

it('should filter incidents by status=reported on first load ', async () => {
const wrapper = await setup([Permissions.ViewIncidents])
const filterSelect = wrapper.find('select')
expect(filterSelect.props().value).toBe(filter.reported)
expect(IncidentRepository.search).toHaveBeenCalled()
expect(IncidentRepository.search).toHaveBeenCalledWith({ status: filter.reported })
})
it('should call IncidentRepository after changing filter', async () => {
const wrapper = await setup([Permissions.ViewIncidents])
const filterSelect = wrapper.find('select')
expect(IncidentRepository.findAll).not.toHaveBeenCalled()
filterSelect.simulate('change', { target: { value: filter.all } })
expect(IncidentRepository.findAll).toHaveBeenCalled()
filterSelect.simulate('change', { target: { value: filter.reported } })
expect(IncidentRepository.search).toHaveBeenCalledTimes(2)
expect(IncidentRepository.search).toHaveBeenLastCalledWith({ status: filter.reported })
})
marcosvega91 marked this conversation as resolved.
Show resolved Hide resolved
describe('layout', () => {
it('should set the title', async () => {
await setup([Permissions.ViewIncidents])
Expand Down
16 changes: 16 additions & 0 deletions src/clients/db/IncidentRepository.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
import { incidents } from '../../config/pouchdb'
import { filter } from '../../incidents/incidents-slice'
import Incident from '../../model/Incident'
import Repository from './Repository'

interface SearchContainer {
marcosvega91 marked this conversation as resolved.
Show resolved Hide resolved
status: filter
}
class IncidentRepository extends Repository<Incident> {
constructor() {
super(incidents)
}

async search(container: SearchContainer): Promise<Incident[]> {
const selector = {
$and: [...(container.status !== 'all' ? [{ status: container.status }] : [undefined])].filter(
marcosvega91 marked this conversation as resolved.
Show resolved Hide resolved
(x) => x !== undefined,
),
}

return super.search({
selector,
})
}
}

export default new IncidentRepository()
17 changes: 14 additions & 3 deletions src/incidents/incidents-slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ interface IncidentsState {
status: 'loading' | 'completed'
}

export enum filter {
reported = 'reported',
marcosvega91 marked this conversation as resolved.
Show resolved Hide resolved
all = 'all',
}

marcosvega91 marked this conversation as resolved.
Show resolved Hide resolved
const initialState: IncidentsState = {
incidents: [],
status: 'loading',
Expand All @@ -34,10 +39,16 @@ const incidentSlice = createSlice({

export const { fetchIncidentsStart, fetchIncidentsSuccess } = incidentSlice.actions

export const fetchIncidents = (): AppThunk => async (dispatch) => {
export const searchIncidents = (status: filter): AppThunk => async (dispatch) => {
dispatch(fetchIncidentsStart())

const incidents = await IncidentRepository.findAll()
let incidents
if (status === filter.all) {
incidents = await IncidentRepository.findAll()
} else {
incidents = await IncidentRepository.search({
status,
})
}

dispatch(fetchIncidentsSuccess(incidents))
}
Expand Down
79 changes: 52 additions & 27 deletions src/incidents/list/ViewIncidents.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,78 @@
import format from 'date-fns/format'
import React, { useEffect } from 'react'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { useHistory } from 'react-router-dom'

import SelectWithLabelFormGroup from '../../components/input/SelectWithLableFormGroup'
import Incident from '../../model/Incident'
import useTitle from '../../page-header/useTitle'
import { RootState } from '../../store'
import { fetchIncidents } from '../incidents-slice'
import { searchIncidents, filter } from '../incidents-slice'

const ViewIncidents = () => {
const { t } = useTranslation()
const history = useHistory()
const dispatch = useDispatch()
useTitle(t('incidents.reports.label'))

const [searchFilter, setSearchFilter] = useState<filter>(filter.reported)
const { incidents } = useSelector((state: RootState) => state.incidents)

useEffect(() => {
dispatch(fetchIncidents())
}, [dispatch])
dispatch(searchIncidents(searchFilter))
}, [dispatch, searchFilter])

const onTableRowClick = (incident: Incident) => {
history.push(`incidents/${incident.id}`)
}
const setFilter = (value: string) => (value === 'reported' ? filter.reported : filter.all)

const onFilterChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
setSearchFilter(setFilter(event.target.value))
}

return (
<table className="table table-hover">
<thead className="thead-light">
<tr>
<th>{t('incidents.reports.code')}</th>
<th>{t('incidents.reports.dateOfIncident')}</th>
<th>{t('incidents.reports.reportedBy')}</th>
<th>{t('incidents.reports.reportedOn')}</th>
<th>{t('incidents.reports.status')}</th>
</tr>
</thead>
<tbody>
{incidents.map((incident: Incident) => (
<tr onClick={() => onTableRowClick(incident)} key={incident.id}>
<td>{incident.code}</td>
<td>{format(new Date(incident.date), 'yyyy-MM-dd hh:mm a')}</td>
<td>{incident.reportedBy}</td>
<td>{format(new Date(incident.reportedOn), 'yyyy-MM-dd hh:mm a')}</td>
<td>{incident.status}</td>
</tr>
))}
</tbody>
</table>
<>
<div className="row">
<div className="col-md-3 col-lg-2">
<SelectWithLabelFormGroup
name="type"
value={searchFilter}
label={t('incidents.filterTitle')}
isEditable
options={[
{ label: t('incidents.status.reported'), value: `${filter.reported}` },
{ label: t('incidents.status.all'), value: `${filter.all}` },
]}
marcosvega91 marked this conversation as resolved.
Show resolved Hide resolved
onChange={onFilterChange}
/>
</div>
</div>
<div className="row">
<table className="table table-hover">
<thead className="thead-light">
<tr>
<th>{t('incidents.reports.code')}</th>
<th>{t('incidents.reports.dateOfIncident')}</th>
<th>{t('incidents.reports.reportedBy')}</th>
<th>{t('incidents.reports.reportedOn')}</th>
<th>{t('incidents.reports.status')}</th>
</tr>
</thead>
<tbody>
{incidents.map((incident: Incident) => (
<tr onClick={() => onTableRowClick(incident)} key={incident.id}>
<td>{incident.code}</td>
<td>{format(new Date(incident.date), 'yyyy-MM-dd hh:mm a')}</td>
<td>{incident.reportedBy}</td>
<td>{format(new Date(incident.reportedOn), 'yyyy-MM-dd hh:mm a')}</td>
<td>{incident.status}</td>
</tr>
))}
</tbody>
</table>
</div>
</>
)
}

Expand Down
5 changes: 5 additions & 0 deletions src/locales/enUs/translations/incidents/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
export default {
incidents: {
filterTitle: ' Filter by status',
label: 'Incidents',
actions: {
report: 'Report',
},
status: {
reported: 'reported',
all: 'all',
},
reports: {
label: 'Reported Incidents',
new: 'Report Incident',
Expand Down