зеркало из https://github.com/Azure/Sia-EventUI.git
persist one reducer, add configs inline
This commit is contained in:
Коммит
b18b8564ff
|
@ -1,5 +1,6 @@
|
|||
import queryString from 'query-string'
|
||||
import deepEquals from 'deep-equal'
|
||||
import ByPath from 'object-path'
|
||||
|
||||
import * as eventActions from './eventActions'
|
||||
|
||||
|
@ -33,18 +34,17 @@ export const addFilter = (history) => (filter, eventType) => {
|
|||
return applyFilter(history)(oldFilter, newFilter)
|
||||
}
|
||||
|
||||
export const removeFilter = (history) => (oldFilter, eventTypeToDelete) => {
|
||||
if (!oldFilter.eventTypes.includes(eventTypeToDelete.id)) {
|
||||
export const removeFilter = (history, relativeFilterPath) => (oldFilter, filterToDelete) => {
|
||||
if (!ByPath.get(oldFilter, relativeFilterPath).includes(filterToDelete)) {
|
||||
return
|
||||
}
|
||||
const newFilter = {
|
||||
...oldFilter,
|
||||
eventTypes: oldFilter.eventTypes.filter(eventType => eventTypeToDelete.id !== eventType)
|
||||
}
|
||||
let newFilter = { ...oldFilter }
|
||||
const isFilterToKeep = existingFilter => filterToDelete !== existingFilter
|
||||
ByPath.set(newFilter, relativeFilterPath, ByPath.get(oldFilter, relativeFilterPath).filter(isFilterToKeep))
|
||||
return applyFilter(history)(oldFilter, newFilter)
|
||||
}
|
||||
|
||||
export const applyFilter = (history) => (oldFilter, newFilter) => (dispatch) => {
|
||||
const applyFilter = (history) => (oldFilter, newFilter) => (dispatch) => {
|
||||
if (!newFilter.incidentId) {
|
||||
throw new Error('Need to filter on incidentId!')
|
||||
}
|
||||
|
@ -106,6 +106,9 @@ export const generateUrl = (history, filter) => {
|
|||
return /tickets/ + filter.ticketId + '?' + serializeEventTypesForQuery(filter.eventTypes)
|
||||
}
|
||||
|
||||
export const findEventTypeInRef = (eventType, referenceData) => {
|
||||
return referenceData.hasOwnProperty(eventType) ? referenceData[eventType] : {id: eventType, name: 'unknown'}
|
||||
export const findEventTypeInRef = (referenceData) => (eventType) => {
|
||||
return referenceData.hasOwnProperty(eventType) ?
|
||||
referenceData[eventType]
|
||||
:
|
||||
{id: eventType, name: 'unknown'}
|
||||
}
|
|
@ -25,7 +25,7 @@ class Ticket extends Component {
|
|||
componentDidMount() {
|
||||
const { dispatch, incident, ticketId, ticket, ticketSystem, preferences, incidentIsFetching, incidentIsError, history } = this.props
|
||||
dispatch(incidentActions.fetchIncidentIfNeeded(incident, ticketId, ticket, ticketSystem, preferences, incidentIsFetching, incidentIsError))
|
||||
dispatch(fetchEventTypes(history))
|
||||
dispatch(fetchEventTypes())
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import IconButtonStyled from '../elements/IconButtonStyled'
|
||||
import FilterChips from '../elements/FilterChips'
|
||||
import ArrowDown from 'material-ui/svg-icons/navigation/arrow-downward'
|
||||
import ArrowUp from 'material-ui/svg-icons/navigation/arrow-upward'
|
||||
import AutoComplete from 'material-ui/AutoComplete'
|
||||
import Chip from 'material-ui/Chip'
|
||||
import * as formActions from '../../actions/formActions'
|
||||
import * as eventActions from '../../actions/eventActions'
|
||||
import * as filterActions from '../../actions/filterActions'
|
||||
|
||||
|
||||
export const dataSourceConfig = {
|
||||
text: 'name',
|
||||
value: 'id'
|
||||
|
@ -31,11 +31,15 @@ export const filterSearchForm = {
|
|||
}
|
||||
|
||||
|
||||
const EventFilter = ({pagination, filter, filterSearchField, eventTypes, filterTypes, dispatch, history}) => {
|
||||
const filterChips = filter && filter.eventTypes && eventTypes ? renderChips(history, filter, eventTypes, dispatch): null
|
||||
const EventFilter = ({pagination, filter, filterSearchField, filterTypes, dispatch, history}) => {
|
||||
return (
|
||||
<div className="incident-EventFilter">
|
||||
{filterChips}
|
||||
<FilterChips
|
||||
selectSpecificFilter={'eventTypes'}
|
||||
lookupFilterObject={'events.filter'}
|
||||
recordLookup={'eventTypes.records'}
|
||||
onRequestDelete={(filter, id) => () => dispatch(filterActions.removeFilter(history, 'eventTypes')(filter,id))}
|
||||
/>
|
||||
<AutoComplete
|
||||
floatingLabelText="Filter by event type"
|
||||
filter={AutoComplete.caseInsensitiveFilter}
|
||||
|
@ -64,38 +68,15 @@ const EventFilter = ({pagination, filter, filterSearchField, eventTypes, filterT
|
|||
)
|
||||
}
|
||||
|
||||
|
||||
const renderChips = (history, filter, eventTypes, dispatch) => {
|
||||
return (
|
||||
<div style={chipStyles.wrapper}>
|
||||
{filter.eventTypes.map((passedEventType) => renderChip(history, filter, eventTypes, passedEventType, dispatch))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderChip = (history, filter, eventTypes, passedEventType, dispatch) => {
|
||||
const eventType = filterActions.findEventTypeInRef(passedEventType, eventTypes)
|
||||
return (
|
||||
<Chip
|
||||
key={eventType.id}
|
||||
onRequestDelete={() => dispatch(filterActions.removeFilter(history)(filter, eventType))}
|
||||
style={chipStyles.chip}
|
||||
>
|
||||
{eventType.name}
|
||||
</Chip>
|
||||
)
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const { events } = state
|
||||
return {
|
||||
...ownProps,
|
||||
pagination: events.pages,
|
||||
filter: events.filter,
|
||||
filterSearchField: state.forms[filterSearchForm.name] ? state.forms[filterSearchForm.name][filterSearchForm.field] : '',
|
||||
eventTypes: ownProps.eventTypes ? ownProps.eventTypes : null,
|
||||
filterTypes: ownProps.eventTypes ? Object.values(ownProps.eventTypes) : []
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(EventFilter)
|
||||
export default withRouter(connect(mapStateToProps)(EventFilter))
|
||||
|
|
|
@ -12,11 +12,16 @@ import { resetSignalRConnection } from '../../services/signalRService'
|
|||
import { acknowledgeMessages } from '../../actions/signalRActions'
|
||||
|
||||
export const NavNotifs = ({signalR, dispatch}) => {
|
||||
return <IconButton onTouchTap={notifsAction(signalR, dispatch)}>
|
||||
const pendingMessages = signalR.pendingMessages ? signalR.pendingMessages : 0
|
||||
return <IconButton tooltip={showNotifsMessage(pendingMessages)}onTouchTap={notifsAction(signalR, dispatch)}>
|
||||
{displayButton(signalR)}
|
||||
</IconButton>
|
||||
}
|
||||
|
||||
const showNotifsMessage = (pendingMessages) => {
|
||||
return pendingMessages === 0 ? 'Check for new messages' : `View ${pendingMessages} messages`
|
||||
}
|
||||
|
||||
NavNotifs.propTypes = {
|
||||
signalR: PropTypes.object,
|
||||
dispatch: PropTypes.func
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import Chip from 'material-ui/Chip'
|
||||
import ByPath from 'object-path'
|
||||
|
||||
export const FilterChips = ({filter, selectSpecificFilter, records, onRequestDelete}) => {
|
||||
const selectedFilters = ByPath.get(filter, selectSpecificFilter)
|
||||
return selectedFilters
|
||||
? selectedFilters
|
||||
.map((selectedFilter) => hydrateChip(selectedFilter, records))
|
||||
.map(chip => renderChip(chip.id, chip.name, onRequestDelete(filter, chip.id)))
|
||||
: <div></div>
|
||||
}
|
||||
|
||||
export const mapStateToProps = (state, ownProps) => ({
|
||||
...ownProps,
|
||||
filter: ByPath.get(state, ownProps.lookupFilterObject),
|
||||
records: ByPath.get(state, ownProps.recordLookup)
|
||||
})
|
||||
|
||||
export const renderChip = (id, name, onRequestDelete) => (
|
||||
<Chip
|
||||
key={id}
|
||||
onRequestDelete={onRequestDelete}
|
||||
style={chipStyles.chip}
|
||||
>
|
||||
{name}
|
||||
</Chip>
|
||||
)
|
||||
|
||||
export const hydrateChip = (id, records) => ({
|
||||
id: id,
|
||||
name: records[id] ? records[id].name : 'unknown'
|
||||
|
||||
})
|
||||
|
||||
const chipStyles = {
|
||||
chip: {
|
||||
margin: 4
|
||||
},
|
||||
wrapper: {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap'
|
||||
}
|
||||
}
|
||||
|
||||
FilterChips.propTypes = {
|
||||
selectSpecificFilter: PropTypes.string,
|
||||
lookupFilterObject: PropTypes.string,
|
||||
recordLookup: PropTypes.string,
|
||||
onRequestDelete: PropTypes.func
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(FilterChips)
|
|
@ -6,11 +6,6 @@ import buildError from './reducerHelpers/error'
|
|||
import { persistReducer } from 'redux-persist'
|
||||
import storage from 'redux-persist/lib/storage' // default: localStorage if web, AsyncStorage if react-native
|
||||
|
||||
const persistConfig = {
|
||||
key: 'eventType',
|
||||
storage
|
||||
}
|
||||
|
||||
const defaultEventTypeCollection = {}
|
||||
|
||||
const actionSet = {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { expect } from 'chai'
|
||||
import queryString from 'query-string'
|
||||
import * as filterActions from '../../src/actions/filterActions'
|
||||
import AddMockDispatch from '../helpers/mockDispatch'
|
||||
|
||||
// Istanbul for test coverage, provides us with a new command nyc-mocha, which runs the tests and gives a reporter
|
||||
describe('FilterActions', function () {
|
||||
|
@ -9,7 +10,7 @@ describe('FilterActions', function () {
|
|||
describe('when the query string provides a single value for eventTypes', function() {
|
||||
it('should return an array with one value', function() {
|
||||
let testQuery = '?eventTypes=1'
|
||||
|
||||
|
||||
let result = filterActions.getFilterFromUrl(testQuery)
|
||||
|
||||
expect(result.eventTypes).to.eql([1])
|
||||
|
@ -21,7 +22,7 @@ describe('FilterActions', function () {
|
|||
let testQuery = '?eventTypes=16&eventTypes=11'
|
||||
|
||||
let result = filterActions.getFilterFromUrl(testQuery)
|
||||
|
||||
|
||||
expect(result.eventTypes).to.eql([16, 11])
|
||||
})
|
||||
})
|
||||
|
@ -30,7 +31,7 @@ describe('FilterActions', function () {
|
|||
describe('when an arbitrary key is given', function() {
|
||||
it('should return null if the arbitrary key is of unknown type', function() {
|
||||
let badTestQuery = '?foo=baz'
|
||||
|
||||
|
||||
let result = filterActions.getFilterFromUrl(badTestQuery)
|
||||
|
||||
expect(result).to.equal(null)
|
||||
|
@ -39,9 +40,9 @@ describe('FilterActions', function () {
|
|||
describe('when filter has no value in key-value pair', function() {
|
||||
it('should return null', function() {
|
||||
let badTestQuery = '?foo='
|
||||
|
||||
|
||||
let result = filterActions.getFilterFromUrl(badTestQuery)
|
||||
|
||||
|
||||
expect(result).to.equal(null)
|
||||
})
|
||||
})
|
||||
|
@ -49,28 +50,28 @@ describe('FilterActions', function () {
|
|||
describe('when filter has no key in key-value pair', function() {
|
||||
it('should return null', function () {
|
||||
let badTestQuery = '?=foo'
|
||||
|
||||
|
||||
let result = filterActions.getFilterFromUrl(badTestQuery)
|
||||
|
||||
|
||||
expect(result).to.equal(null)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe('when filter has no key-value pair', function() {
|
||||
it('should return null', function () {
|
||||
let badTestQuery = '?'
|
||||
|
||||
|
||||
let result = filterActions.getFilterFromUrl(badTestQuery)
|
||||
|
||||
|
||||
expect(result).to.equal(null)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getUrlFromFilter', function() {
|
||||
const history = []
|
||||
|
||||
|
||||
const filterNoEventTypes = {ticketId: 0, foo: 'bar'}
|
||||
const filterWithEventTypes = {ticketId: 0, eventTypes: [1,2]}
|
||||
|
||||
|
@ -111,11 +112,11 @@ describe('FilterActions', function () {
|
|||
const unknownEventType = {id: 10000, name: 'unknown'}
|
||||
|
||||
it('should return a matching object when given a known id', function() {
|
||||
const result = filterActions.findEventTypeInRef(testEventTypeId, referenceData)
|
||||
const result = filterActions.findEventTypeInRef(referenceData)(testEventTypeId)
|
||||
expect(result).to.deep.equal(expectedEventType)
|
||||
})
|
||||
it('should return a new object with name "unknown" when given an unknown id', function() {
|
||||
const badResult = filterActions.findEventTypeInRef(unknownEventTypeId, referenceData)
|
||||
const badResult = filterActions.findEventTypeInRef(referenceData)(unknownEventTypeId)
|
||||
expect(badResult).to.deep.equal(unknownEventType)
|
||||
})
|
||||
})
|
||||
|
@ -127,26 +128,18 @@ describe('FilterActions', function () {
|
|||
const result = filterActions.changeEventFilter(history)(filter)
|
||||
|
||||
const expectedValue = {type: 'CHANGE_EVENT_FILTER', filter: {ticketId: 0, eventTypes: [1, 2]}}
|
||||
|
||||
|
||||
it('should return an object with a type and a filter', function() {
|
||||
expect(result).to.deep.equal(expectedValue)
|
||||
})
|
||||
})
|
||||
|
||||
describe('applyFilter', function() {
|
||||
const history = []
|
||||
const oldFilter = {incidentId: 0}
|
||||
const goodNewFilter = {incidentId: 0}
|
||||
const badNewFilter = {foo: 'bar'}
|
||||
|
||||
const dispatch = function(arg) {
|
||||
return arg
|
||||
}
|
||||
const errorMessage = 'Need to filter on incidentId!'
|
||||
const expectedReturn = { type: 'CHANGE_EVENT_FILTER', filter: {incidentId: 0} }
|
||||
|
||||
it('should throw an error when new filter has no incident id', function() {
|
||||
expect(() => filterActions.applyFilter(history)(oldFilter, badNewFilter)(dispatch)).to.throw(errorMessage)
|
||||
describe('removeFilter', function() {
|
||||
beforeEach(() =>{
|
||||
this.mockDispatchRecorder = {
|
||||
action: null
|
||||
}
|
||||
this.singleState = setup(dummyState(this.mockDispatchRecorder), null)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
'use strict'
|
||||
import { expect } from 'chai'
|
||||
import React from 'react'
|
||||
import Chip from 'material-ui/Chip'
|
||||
import createComponent from '../../helpers/shallowRenderHelper'
|
||||
import { FilterChips, mapStateToProps, renderChip, hydrateChip } from '../../../src/components/elements/FilterChips'
|
||||
import AddMockDispatch from '../../helpers/mockDispatch'
|
||||
|
||||
describe('FilterChips', function() {
|
||||
describe('mapStateToProps', function() {
|
||||
let mockState = {
|
||||
records: 1,
|
||||
filter: 2
|
||||
}
|
||||
|
||||
it('should pass the props through', function() {
|
||||
expect(mapStateToProps(mockState, { foo: 'baz' })).to.include({foo: 'baz'})
|
||||
})
|
||||
|
||||
it('should retrieve the value of filter based on ownProps.lookupFilterObject', function() {
|
||||
expect(mapStateToProps(mockState, { lookupFilterObject: 'filter' }).filter).to.equal(2)
|
||||
})
|
||||
|
||||
it('should retrieve the value of records based on ownProps.recordLookup', function() {
|
||||
expect(mapStateToProps(mockState, { recordLookup: 'records' }).records).to.equal(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('renderChip', function() {
|
||||
it('should render a Chip with the correct id as an argument and the correct name as its only child', function() {
|
||||
let mockId = 42
|
||||
let mockName = 'Douglas'
|
||||
let mockFx = () => null
|
||||
|
||||
let result = renderChip(mockId, mockName, mockFx)
|
||||
|
||||
expect(result.type).to.equal(Chip)
|
||||
expect(result.props.children).to.equal('Douglas')
|
||||
expect(result.props.onRequestDelete).to.be.a('function')
|
||||
})
|
||||
})
|
||||
|
||||
describe('hydrateChip', function() {
|
||||
let mockRecords = {
|
||||
1: {name: 'Sue'}
|
||||
}
|
||||
|
||||
describe('when passed a valid record id', function() {
|
||||
it('should return the name of the record', function() {
|
||||
expect(hydrateChip(1, mockRecords).name).to.equal('Sue')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when passed an invalid record id', function() {
|
||||
it('should return unknown as the name of the record', function() {
|
||||
expect(hydrateChip(2, mockRecords).name).to.equal('unknown')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Component', function() {
|
||||
context('when state has all required information', function() {
|
||||
describe('when given a valid id', function () {
|
||||
it('should return an array of Chips with the correct name, id, and fx', function () {
|
||||
let mockProps = {
|
||||
records:{
|
||||
1: { name: 'Sue' }
|
||||
},
|
||||
filter:{ filterItems: [1] },
|
||||
onRequestDelete:(filter, id) => id,
|
||||
selectSpecificFilter:'filterItems'
|
||||
}
|
||||
|
||||
let result = FilterChips(mockProps)
|
||||
|
||||
expect(result[0].type).to.equal(Chip)
|
||||
expect(result[0].props.children).to.equal('Sue')
|
||||
expect(result[0].props.onRequestDelete).to.equal(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when given an invalid id', function () {
|
||||
it('should return an array of Chips with the correct name, id, and fx', function () {
|
||||
let mockProps = {
|
||||
records: { 1: { name: 'Sue' } },
|
||||
filter: { filterItems: [2] },
|
||||
onRequestDelete: (filter, id) => id,
|
||||
selectSpecificFilter: 'filterItems'
|
||||
}
|
||||
|
||||
let result = FilterChips(mockProps)
|
||||
|
||||
expect(result[0].type).to.equal(Chip)
|
||||
expect(result[0].props.children).to.equal('unknown')
|
||||
expect(result[0].props.onRequestDelete).to.equal(2)
|
||||
expect(result[0].onRequestDelete).to.not.equal(3)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('describe when state is missing required information', function() {
|
||||
it('should return an empty div', function() {
|
||||
let mockProps = {
|
||||
records: { 1: { name: 'Sue' } },
|
||||
filter: { filterItems: [1] },
|
||||
onRequestDelete: (filter, id) => id,
|
||||
selectSpecificFilter: 'NOT_filterItems'
|
||||
}
|
||||
|
||||
let result = FilterChips(mockProps)
|
||||
|
||||
expect(result.type).to.equal('div')
|
||||
expect(result.props.children).to.be.undefined
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
})
|
||||
})
|
Загрузка…
Ссылка в новой задаче