Commit bf1f878a authored by Shani's avatar Shani

Merge branch 'fraud_detection' into dev

parents 7f61ced4 d852e363
......@@ -68,8 +68,10 @@ fieldset[disabled] .btn {
white-space:nowrap;
}
.pl0{ padding-left: 0!important; }
.pt0{ padding-top: 0!important; }
.pr0{ padding-right: 0!important; }
.pb0{ padding-bottom: 0!important; }
.pl0{ padding-left: 0!important; }
.mt10{ margin-top: 10px!important; }
.mr10{ margin-right: 10px!important; }
.mb10{ margin-bottom: 10px!important; }
......
import { updateSetting } from './settingsActions';
import { getEventConvertedConditions } from '../components/Events/EventsUtil';
import Immutable from 'immutable';
import uuid from 'uuid';
import isNumber from 'is-number';
import {
saveSettings,
updateSetting,
removeSettingField,
pushToSetting,
getSettings,
} from './settingsActions';
import { pushToList } from './listActions';
import {
usageTypesDataSelector,
propertyTypeSelector,
eventsSelector,
} from '../selectors/settingsSelector';
import {
effectOnEventUsagetFieldsSelector,
} from '../selectors/eventSelectors';
import { apiBillRun, apiBillRunErrorHandler, apiBillRunSuccessHandler } from '../common/Api';
import {
getProductsByKeysQuery,
saveSettingsQuery,
} from '../common/ApiQueries';
import { getValueByUnit } from '../common/Util';
export const saveEvent = (entityType, index, event) => (dispatch, getState) => { // eslint-disable-line import/prefer-default-export
const getEventConvertedConditions = (propertyTypes, usageTypes, item, toBaseUnit = true) => {
const convertedConditions = item.get('conditions', Immutable.List()).withMutations((conditionsWithMutations) => {
conditionsWithMutations.forEach((cond, index) => {
const unit = cond.get('unit', '');
const usaget = cond.get('usaget', '');
if (unit !== '' && usaget !== '') {
const value = cond.get('value', 0);
const newValue = getValueByUnit(propertyTypes, usageTypes, usaget, unit, value, toBaseUnit);
conditionsWithMutations.setIn([index, 'value'], newValue);
}
});
});
return !convertedConditions.isEmpty()
? convertedConditions
: Immutable.List();
};
const getEventConvertedThreshold = (propertyTypes, usageTypes, item, toBaseUnit = true) => {
const convertedThreshold = item.getIn(['threshold_conditions', 0], Immutable.List()).withMutations((thresholdWithMutations) => {
thresholdWithMutations.forEach((threshold, index) => {
const unit = threshold.get('unit', '');
const usaget = threshold.get('usaget', '');
const value = threshold.get('value', 0);
if (unit !== '' && usaget !== '') {
if (Immutable.List.isList(value) || Array.isArray(value)) {
const arrayVal = value.map((val) => {
const newValue = getValueByUnit(propertyTypes, usageTypes, usaget, unit, val, toBaseUnit);
return isNumber(newValue) ? parseFloat(newValue) : newValue;
});
thresholdWithMutations.setIn([index, 'value'], arrayVal);
} else {
const newValue = getValueByUnit(propertyTypes, usageTypes, usaget, unit, value, toBaseUnit);
const val = isNumber(newValue) ? parseFloat(newValue) : newValue;
thresholdWithMutations.setIn([index, 'value'], val);
}
}
if (!toBaseUnit && Immutable.Iterable.isIterable(value)) {
thresholdWithMutations.setIn([index, 'value'], value.toList());
}
if (!toBaseUnit && !Immutable.Iterable.isIterable(value) && ['in', 'nin'].includes(threshold.get('op', ''))) {
thresholdWithMutations.setIn([index, 'value'], Immutable.List([value]));
}
});
});
return !convertedThreshold.isEmpty()
? convertedThreshold
: Immutable.List();
};
const convertFromApiToUi = (event, eventType, params) => event.withMutations((eventWithMutations) => {
const { propertyTypes, usageTypesData, effectOnUsagetFields } = params;
const eventUsageTypeFromEvent = Immutable.Map().withMutations((eventUsageTypeWithMutations) => {
if (eventType === 'fraud') {
event.getIn(['conditions', 0]).forEach((cond) => {
if (effectOnUsagetFields.includes(cond.get('field', ''))) {
eventUsageTypeWithMutations.set(cond.get('field', ''), cond.get('value', Immutable.List()));
}
});
}
});
const uiFlags = Immutable.Map({
id: uuid.v4(),
eventUsageType: eventUsageTypeFromEvent,
});
eventWithMutations.set('conditions', getEventConvertedConditions(propertyTypes, usageTypesData, event, false));
eventWithMutations.setIn(['threshold_conditions', 0], getEventConvertedThreshold(propertyTypes, usageTypesData, event, false));
eventWithMutations.set('ui_flags', uiFlags);
});
const convertFromUiToApi = (event, eventType, params) =>
event.withMutations((eventWithMutations) => {
const { propertyTypes, usageTypesData } = params;
eventWithMutations.delete('ui_flags');
if (eventType === 'fraud') {
const withoutEmptyConditions = eventWithMutations.getIn(['conditions', 0], Immutable.List())
.filter(cond => (cond.get('field', '') !== '' && cond.get('op', '')));
eventWithMutations.setIn(['conditions', 0], withoutEmptyConditions);
}
eventWithMutations.set('conditions', getEventConvertedConditions(propertyTypes, usageTypesData, eventWithMutations, true));
eventWithMutations.setIn(['threshold_conditions', 0], getEventConvertedThreshold(propertyTypes, usageTypesData, eventWithMutations, true));
});
export const getEvents = (eventCategory = '') => (dispatch, getState) => {
const settingsPath = (eventCategory === '') ? 'events' : `events.${eventCategory}`;
return dispatch(getSettings(settingsPath)).then(() => {
// Add local ID to events
const state = getState();
const usageTypesData = usageTypesDataSelector(state);
const propertyTypes = propertyTypeSelector(state);
const effectOnUsagetFields = effectOnEventUsagetFieldsSelector(state, { eventType: 'fraud' });
const params = ({ usageTypesData, propertyTypes, effectOnUsagetFields });
const settingsEvents = state.settings.get('events', Immutable.List());
settingsEvents.forEach((events, eventType) => {
if (!['settings'].includes(eventType) && (eventCategory === '' || eventCategory === eventType)) {
const eventsWithId = events.map(event => convertFromApiToUi(event, eventType, params));
dispatch(updateSetting('events', eventType, eventsWithId));
}
});
});
};
export const saveEvents = (eventCategory = '') => (dispatch, getState) => {
// remove local ID to events
const state = getState();
const usageTypesData = usageTypesDataSelector(state);
const propertyTypes = propertyTypeSelector(state);
const convertedEvent = event.withMutations((eventWithMutations) => {
eventWithMutations.set('conditions', getEventConvertedConditions(propertyTypes, usageTypesData, event, true));
const settingsEvents = state.settings.get('events', Immutable.List());
const params = ({ usageTypesData, propertyTypes });
settingsEvents.forEach((events, eventType) => {
if (!['settings'].includes(eventType) && (eventCategory === '' || eventCategory === eventType)) {
const eventsWithId = events.map(event => convertFromUiToApi(event, eventType, params));
dispatch(updateSetting('events', eventType, eventsWithId));
}
});
return dispatch(updateSetting('events', [entityType, index], convertedEvent));
const settingsPath = (eventCategory === '') ? 'events' : `events.${eventCategory}`;
return dispatch(saveSettings([settingsPath]));
};
export const saveEvent = (eventCategory, event) => (dispatch, getState) => {
const state = getState();
const usageTypesData = usageTypesDataSelector(state);
const propertyTypes = propertyTypeSelector(state);
const params = ({ usageTypesData, propertyTypes });
const convertedEvent = convertFromUiToApi(event, eventCategory, params);
const category = `event.${eventCategory}`;
const queries = saveSettingsQuery(convertedEvent, category);
return apiBillRun(queries)
.then(success => dispatch(apiBillRunSuccessHandler(success)))
.catch(error => dispatch(apiBillRunErrorHandler(error)));
};
export const addEvent = (eventType, event) => (dispatch) => {
const newEvent = event.setIn(['ui_flags', 'id'], uuid.v4());
return dispatch(pushToSetting('events', newEvent, eventType));
};
export const updateEvent = (eventType, event) => (dispatch, getState) => {
const events = eventsSelector(getState(), { eventType });
const index = events.findIndex(e => e.getIn(['ui_flags', 'id'], '') === event.getIn(['ui_flags', 'id'], ''));
if (index !== -1) {
return dispatch(updateSetting('events', [eventType, index], event));
}
return Promise.reject();
};
export const removeEvent = (eventType, event) => (dispatch, getState) => {
const events = eventsSelector(getState(), { eventType });
const index = events.findIndex(e => e.getIn(['ui_flags', 'id'], '') === event.getIn(['ui_flags', 'id'], ''));
if (index !== -1) {
return dispatch(removeSettingField('events', [eventType, index]));
}
return false;
};
export const saveEventSettings = () => dispatch => dispatch(saveSettings(['events.settings']));
export const updateEventSettings = (path, value) => dispatch => dispatch(updateSetting('events', ['settings', ...path], value));
export const getEventSettings = () => dispatch => dispatch(getSettings(['events.settings']));
export const getEventRates = eventRatesKeys => dispatch =>
dispatch(pushToList('event_products', getProductsByKeysQuery(eventRatesKeys.toArray(), { key: 1, rates: 1 })));
......@@ -8,6 +8,11 @@ export const ONBOARDING_SET_STATE = 'SET_ON_BOARDING_STATE';
export const CONFIRM_SHOW = 'CONFIRM_SHOW';
export const CONFIRM_HIDE = 'CONFIRM_HIDE';
export const EDIT_FORM_SHOW = 'EDIT_FORM_SHOW';
export const EDIT_FORM_HIDE = 'EDIT_FORM_HIDE';
export const EDIT_FORM_SET_ITEM = 'EDIT_FORM_SET_ITEM';
export const EDIT_FORM_UPDATE_ITEM_FIELD = 'EDIT_FORM_UPDATE_ITEM_FIELD';
export const EDIT_FORM_DELETE_ITEM_FIELD = 'EDIT_FORM_DELETE_ITEM_FIELD';
export const onBoardingStates = {
READY: 'READY',
......@@ -80,3 +85,29 @@ export const showConfirmModal = confirm => ({
export const hideConfirmModal = () => ({
type: CONFIRM_HIDE,
});
export const showFormModal = (item, component, config) => ({
type: EDIT_FORM_SHOW,
item,
component,
config,
});
export const hideFormModal = () => ({
type: EDIT_FORM_HIDE,
});
export const setFormModalItem = item => ({
type: EDIT_FORM_SET_ITEM,
item,
});
export const updateFormModalItemField = (path, value) => ({
type: EDIT_FORM_UPDATE_ITEM_FIELD,
path,
value,
});
export const removeFormModalItemField = path => ({
type: EDIT_FORM_DELETE_ITEM_FIELD,
path,
});
......@@ -106,7 +106,7 @@ const convert = (settings) => {
filters = []
} = settings;
const connections = receiver ? (receiver.connections ? receiver.connections: {}) : {};
const connections = receiver ? (receiver.connections ? receiver.connections: []) : [];
const field_widths = (parser.type === "fixed" && parser.structure) ? parser.structure.map(struct => struct.width) : [];
const usaget_type = (!_.result(processor, 'usaget_mapping') || processor.usaget_mapping.length < 1) ?
"static" :
......@@ -469,10 +469,9 @@ export function removeRatingField(rateCategory, usaget, priority, index) {
};
}
export function removeReceiver(receiver, index) {
export function removeReceiver(index) {
return {
type: REMOVE_RECEIVER,
receiver,
index,
};
}
......
......@@ -61,6 +61,7 @@ export const getFieldNameType = (type) => {
case 'subscriber':
return 'subscription';
case 'lines':
case 'line':
case 'usage':
return 'lines';
case 'service':
......@@ -497,9 +498,15 @@ export const escapeRegExp = text =>
export const createRateListNameByArgs = (query = Immutable.Map()) => query.reduce((acc, value, key) => `${acc}.${key}.${value}`, 'rates');
export const setFieldTitle = (field, entity) => (field.has('title')
? field
: field.set('title', getFieldName(field.get('field_name', ''), getFieldNameType(entity), sentenceCase(field.get('field_name', '')))));
export const setFieldTitle = (field, entity, keyProperty = 'field_name') => {
if (field.has('title')) {
return field;
}
const entityName = getFieldNameType(!entity && field.has('entity') ? field.get('entity') : entity);
const key = field.get(keyProperty, '');
const defaultLable = sentenceCase(field.get(keyProperty, ''));
return field.set('title', getFieldName(key, entityName, defaultLable));
};
export const toImmutableList = (value) => {
if ([undefined, null].includes(value)) {
......@@ -513,3 +520,19 @@ export const toImmutableList = (value) => {
}
return Immutable.List([value]);
};
export const sortFieldOption = (optionsA, optionB) => {
const a = optionsA.get('title', '').toUpperCase(); // ignore upper and lowercase
const b = optionB.get('title', '').toUpperCase(); // ignore upper and lowercase
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
};
export const onlyLineForeignFields = lineField => lineField.has('foreign');
export const foreignFieldWithoutDates = foreignField => foreignField.getIn(['foreign', 'translate', 'type'], '') !== 'unixTimeToString';
......@@ -18,16 +18,17 @@ const Action = (props) => {
'fa-pencil': type === 'edit',
'fa-files-o': type === 'clone',
'fa-file-excel-o': type === 'export_csv',
'danger-red': type === 'remove' || type === 'enable',
'danger-red': ['enable', 'remove'].includes(type),
'fa-trash-o': type === 'remove',
'fa-toggle-off': type === 'enable',
'fa-toggle-on': type === 'disable',
'fa-plus': type === 'add',
'fa-plus': ['add', 'expand'].includes(type),
'fa-calendar': type === 'move',
'fa-repeat': type === 'reopen',
'fa-cloud-upload': type === 'import',
'fa-refresh': type === 'refresh',
'fa-arrow-left': type === 'back',
'fa-minus': type === 'collapse',
});
const onClick = () => {
......
import React, { PropTypes, Component } from 'react';
import Immutable from 'immutable';
import { connect } from 'react-redux';
import { Form, FormGroup, Col, ControlLabel, Button, Panel } from 'react-bootstrap';
import { getConditionDescription } from './EventsUtil';
import Field from '../Field';
import { ModalWrapper } from '../Elements';
import ConditionBalance from './ConditionsTypes/ConditionBalance';
import { usageTypesDataSelector, propertyTypeSelector, currencySelector } from '../../selectors/settingsSelector';
import { Form, FormGroup, Col, ControlLabel, Panel } from 'react-bootstrap';
import { getConditionDescription } from './../EventsUtil';
import Field from '../../Field';
import { Actions, CreateButton } from '../../Elements';
import BalanceEventCondition from './BalanceEventCondition';
import { usageTypesDataSelector, propertyTypeSelector, currencySelector } from '../../../selectors/settingsSelector';
class EventForm extends Component {
class BalanceEvent extends Component {
static propTypes = {
item: PropTypes.instanceOf(Immutable.Map),
onCancel: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired,
onUpdateField: PropTypes.func.isRequired,
updateField: PropTypes.func.isRequired,
conditionType: PropTypes.string,
propertyTypes: PropTypes.instanceOf(Immutable.List),
usageTypesData: PropTypes.instanceOf(Immutable.List),
......@@ -35,13 +33,18 @@ class EventForm extends Component {
onChangeField = path => (e) => {
const { value } = e.target;
this.props.onUpdateField(path, value);
this.props.updateField(path, value);
};
onChangeActive = (e) => {
const { value } = e.target;
this.props.updateField(['active'], value === 'yes');
};
addCondition = () => {
const { item } = this.props;
const conditions = item.get('conditions', Immutable.List()).push(Immutable.Map());
this.props.onUpdateField(['conditions'], conditions);
this.props.updateField(['conditions'], conditions);
this.setState({
editedConditionIndex: conditions.size - 1,
});
......@@ -62,7 +65,7 @@ class EventForm extends Component {
removeCondition = index => () => {
const { item } = this.props;
const conditions = item.get('conditions', Immutable.List()).delete(index);
this.props.onUpdateField(['conditions'], conditions);
this.props.updateField(['conditions'], conditions);
};
renderConditionEditForm = (condition, index) => {
......@@ -71,10 +74,10 @@ class EventForm extends Component {
case 'balance':
default:
return (
<ConditionBalance
<BalanceEventCondition
item={condition}
index={index}
onChangeField={this.props.onUpdateField}
onChangeField={this.props.updateField}
propertyTypes={propertyTypes}
usageTypesData={usageTypesData}
/>
......@@ -82,90 +85,105 @@ class EventForm extends Component {
}
}
renderCondition = (condition, index) => (
<FormGroup className="form-inner-edit-row" key={index}>
<Col sm={10}>
{
getConditionDescription(this.props.conditionType, condition, {
propertyTypes: this.props.propertyTypes,
usageTypesData: this.props.usageTypesData,
currency: this.props.currency,
activityType: 'counter',
})
}
</Col>
<Col sm={1} hidden={this.state.editedConditionIndex === index}>
<Button onClick={this.editCondition(index)} bsStyle="link">
<i className="fa fa-fw fa-pencil" />
</Button>
</Col>
<Col sm={1} hidden={this.state.editedConditionIndex !== index}>
<Button onClick={this.hideEditCondition} bsStyle="link">
<i className="fa fa-fw fa-minus" />
</Button>
</Col>
<Col sm={1}>
<Button onClick={this.removeCondition(index)} bsStyle="link">
<i className="fa fa-fw danger-red fa-trash-o" />
</Button>
</Col>
<Col sm={12}>
<Panel collapsible expanded={this.state.editedConditionIndex === index}>
{ this.renderConditionEditForm(condition, index) }
</Panel>
</Col>
</FormGroup>
);
showConditionDetails = (index) => {
const { editedConditionIndex } = this.state;
return editedConditionIndex === index;
}
renderAddConditionButton = () => (
<Button className="btn-primary" onClick={this.addCondition}><i className="fa fa-plus" />&nbsp;Add New Condition</Button>
);
getConditionActions = index => [
{ type: 'edit', onClick: this.editCondition(index), show: !this.showConditionDetails(index) },
{ type: 'collapse', onClick: this.hideEditCondition, show: this.showConditionDetails(index) },
{ type: 'remove', onClick: this.removeCondition(index) },
];
renderCondition = (condition, index) => {
const { conditionType, propertyTypes, usageTypesData, currency } = this.props;
const activityType = 'counter';
const params = ({ propertyTypes, usageTypesData, currency, activityType });
return (
<FormGroup key={index} className="mb0">
<Col sm={12}>
<div style={{ paddingRight: 100, display: 'inline-block' }}>
{ getConditionDescription(conditionType, condition, params) }
</div>
<span style={{ marginLeft: -100, paddingRight: 15 }} className="pull-right List row">
<Actions actions={this.getConditionActions(index)} />
</span>
</Col>
<Col sm={12}>
<Panel collapsible expanded={this.state.editedConditionIndex === index}>
{ this.renderConditionEditForm(condition, index) }
</Panel>
</Col>
</FormGroup>
);
}
renderConditions = () => (
this.props.item.get('conditions', Immutable.List()).map(this.renderCondition).toArray()
);
renderConditionsHeader = () => (
<FormGroup className="form-inner-edit-row">
<Col sm={12}>
<strong>Conditions</strong>
</Col>
</FormGroup>
);
onSaveEvent = () => {
};
render() {
const { item, onSave, onCancel } = this.props;
const { item } = this.props;
return (
<ModalWrapper title={`Event ${item.get('event_code', '')}`} show={true} onOk={onSave} onCancel={onCancel} labelOk="Save" >
<Form horizontal>
<Form horizontal>
<Panel header={<span>Details</span>}>
<FormGroup>
<Col componentClass={ControlLabel} md={4}>
<Col componentClass={ControlLabel} sm={3}>
Event Code
</Col>
<Col sm={5}>
<Col sm={7}>
<Field id="label" onChange={this.onChangeField(['event_code'])} value={item.get('event_code', '')} />
</Col>
</FormGroup>
<FormGroup>
<Col sm={12}>
{ this.renderConditionsHeader() }
<Col componentClass={ControlLabel} sm={3}>
Description
</Col>
<Col sm={7}>
<Field id="description" onChange={this.onChangeField(['event_description'])} value={item.get('event_description', '')} />
</Col>
</FormGroup>
<FormGroup>
<Col componentClass={ControlLabel} sm={3}>Status</Col>
<Col sm={7}>
<span>
<span style={{ display: 'inline-block', marginRight: 20 }}>
<Field
fieldType="radio"
onChange={this.onChangeActive}
name="step-active-status"
value="yes"
label="Active"
checked={item.get('active', true)}
/>
</span>
<span style={{ display: 'inline-block' }}>
<Field
fieldType="radio"
onChange={this.onChangeActive}
name="step-active-status"
value="no"
label="Not Active"
checked={!item.get('active', true)}
/>
</span>
</span>
</Col>
</FormGroup>
</Panel>
<Panel header={<span>Conditions</span>}>
<FormGroup>
<Col sm={12}>
{ this.renderConditions() }
</Col>
<Col sm={12}>
{ this.renderAddConditionButton() }
<CreateButton onClick={this.addCondition} label="Add New Condition" />
</Col>
</FormGroup>
</Form>
</ModalWrapper>
</Panel>
</Form>
);
}
}
......@@ -176,4 +194,4 @@ const mapStateToProps = (state, props) => ({
currency: currencySelector(state, props),
});
export default connect(mapStateToProps)(EventForm);
export default connect(mapStateToProps)(BalanceEvent);
import React, { PropTypes, Component } from 'react';
import { connect } from 'react-redux';
import Immutable from 'immutable';
import { Form, FormGroup, Row, Col, Panel, Label } from 'react-bootstrap';
import { CreateButton, LoadingItemPlaceholder } from '../../Elements';
import {
eventPropertyTypesSelector,
eventThresholdOperatorsSelectOptionsSelector,
eventThresholdFieldsSelectOptionsSelector,
} from '../../../selectors/eventSelectors';
import { eventsSettingsSelector } from '../../../selectors/settingsSelector';
import FraudEventDetails from './FraudEventDetails';
import FraudEventCondition from './FraudEventCondition';
import FraudEventThreshold from './FraudEventThreshold';
import { getSettings } from '../../../actions/settingsActions';
import { getEventRates } from '../../../actions/eventActions';
class FraudEvent extends Component {
static propTypes = {
item: PropTypes.instanceOf(Immutable.Map),
mode: PropTypes.string,
eventsSettings: PropTypes.instanceOf(Immutable.Map),
eventPropertyType: PropTypes.instanceOf(Immutable.Set),
thresholdFieldsSelectOptions: PropTypes.array,
thresholdOperatorsSelectOptions: PropTypes.array,
updateField: PropTypes.func.isRequired,
dispatch: PropTypes.func.isRequired,
};
static defaultProps = {
item: Immutable.Map(),
mode: 'create',
eventsSettings: Immutable.Map(),
eventPropertyType: Immutable.Set(),
thresholdFieldsSelectOptions: [],
thresholdOperatorsSelectOptions: [],
};
state = {
status: 'loading', // one of [done, loadin, error]
};
componentDidMount() {
this.initEvent();
}
initEvent = () => {
this.props.dispatch(getSettings(['lines.fields']))
.then(this.initEventRates)
.then(this.setStatusDone)
.catch(this.setStatusError);
}
initEventRates = () => {
const { item } = this.props;
const ratesToFetch = item.getIn(['ui_flags', 'eventUsageType', 'arate_key'], Immutable.List());
return (!ratesToFetch.isEmpty())
? this.props.dispatch(getEventRates(ratesToFetch))
: Promise.resolve();
}
setStatusDone = () => {
this.setState(() => ({ status: 'done' }));
}
setStatusError = () => {
this.setState(() => ({ status: 'error' }));
}
getEventRates = (eventRates) => {
if (!eventRates.isEmpty()) {
this.props.dispatch(getEventRates(eventRates));
}
}
onChangeText = path => (e) => {
const { value } = e.target;
this.props.updateField(path, value);
};
<