451 lines
No EOL
14 KiB
JavaScript
451 lines
No EOL
14 KiB
JavaScript
import { List, Map } from 'immutable';
|
|
import Funnel from 'Types/funnel';
|
|
import FunnelIssue from 'Types/funnelIssue';
|
|
import Session from 'Types/session';
|
|
import { fetchListType, fetchType, saveType, editType, initType, removeType } from './funcTools/crud/types';
|
|
import { createItemInListUpdater, mergeReducers, success, array } from './funcTools/tools';
|
|
import { createRequestReducer } from './funcTools/request';
|
|
import { getDateRangeFromValue } from 'App/dateRange';
|
|
import { LAST_7_DAYS } from 'Types/app/period';
|
|
import { filterMap, checkFilterValue, hasFilterApplied } from './search';
|
|
|
|
const name = 'funnel';
|
|
const idKey = 'funnelId';
|
|
const itemInListUpdater = createItemInListUpdater(idKey);
|
|
|
|
const FETCH_LIST = fetchListType('funnel/FETCH_LIST');
|
|
const FETCH_ISSUES = fetchType('funnel/FETCH_ISSUES');
|
|
const FETCH_ISSUE = fetchType('funnel/FETCH_ISSUE');
|
|
const FETCH_ISSUE_TYPES = fetchType('funnel/FETCH_ISSUE_TYPES');
|
|
const FETCH_SESSIONS = fetchType('funnel/FETCH_SESSIONS');
|
|
const FETCH = fetchType('funnel/FETCH');
|
|
const FETCH_INSIGHTS = fetchType('funnel/FETCH_INSIGHTS');
|
|
const SAVE = saveType('funnel/SAVE');
|
|
const UPDATE = saveType('funnel/UPDATE');
|
|
const EDIT = editType('funnel/EDIT');
|
|
const EDIT_FILTER = `${name}/EDIT_FILTER`;
|
|
const EDIT_FUNNEL_FILTER = `${name}/EDIT_FUNNEL_FILTER`;
|
|
const REMOVE = removeType('funnel/REMOVE');
|
|
const INIT = initType('funnel/INIT');
|
|
const SET_NAV_REF = 'funnels/SET_NAV_REF'
|
|
|
|
const RESET_FUNNEL = 'funnels/RESET_FUNNEL'
|
|
const APPLY_FILTER = 'funnels/APPLY_FILTER'
|
|
const APPLY_ISSUE_FILTER = 'funnels/APPLY_ISSUE_FILTER'
|
|
const REMOVE_ISSUE_FILTER = 'funnels/REMOVE_ISSUE_FILTER'
|
|
const SET_ACTIVE_STAGES = 'funnels/SET_ACTIVE_STAGES'
|
|
const SET_SESSIONS_SORT = 'funnels/SET_SESSIONS_SORT'
|
|
const BLINK = 'funnels/BLINK'
|
|
|
|
const RESET_ISSUE = 'funnles/RESET_ISSUE'
|
|
|
|
const FETCH_LIST_SUCCESS = success(FETCH_LIST);
|
|
const FETCH_ISSUES_SUCCESS = success(FETCH_ISSUES);
|
|
const FETCH_ISSUE_SUCCESS = success(FETCH_ISSUE);
|
|
const FETCH_ISSUE_TYPES_SUCCESS = success(FETCH_ISSUE_TYPES);
|
|
const FETCH_SESSIONS_SUCCESS = success(FETCH_SESSIONS);
|
|
const FETCH_SUCCESS = success(FETCH);
|
|
const FETCH_INSIGHTS_SUCCESS = success(FETCH_INSIGHTS);
|
|
const SAVE_SUCCESS = success(SAVE);
|
|
const UPDATE_SUCCESS = success(UPDATE);
|
|
const REMOVE_SUCCESS = success(REMOVE);
|
|
|
|
const range = getDateRangeFromValue(LAST_7_DAYS);
|
|
const defaultDateFilters = {
|
|
rangeValue: LAST_7_DAYS,
|
|
startDate: range.start.unix() * 1000,
|
|
endDate: range.end.unix() * 1000
|
|
}
|
|
|
|
const initialState = Map({
|
|
list: List(),
|
|
instance: Funnel(),
|
|
insights: Funnel(),
|
|
issues: List(),
|
|
issue: FunnelIssue(),
|
|
issuesTotal: 0,
|
|
sessionsTotal: 0,
|
|
sessions: List(),
|
|
activeStages: List(),
|
|
funnelFilters: Map(defaultDateFilters),
|
|
sessionsSort: Map({ order: "desc", sort: "newest" }),
|
|
issueFilters: Map({
|
|
filters: List(),
|
|
sort: { order: "desc", sort: "lostConversions" }
|
|
}),
|
|
sessionFilters: defaultDateFilters,
|
|
navRef: null,
|
|
issueTypes: List(),
|
|
blink: true
|
|
});
|
|
|
|
const reducer = (state = initialState, action = {}) => {
|
|
switch(action.type) {
|
|
case BLINK:
|
|
return state.set('blink', action.state);
|
|
case EDIT:
|
|
return state.mergeIn([ 'instance' ], action.instance);
|
|
case EDIT_FILTER:
|
|
return state.mergeIn([ 'instance', 'filter' ], action.instance);
|
|
case EDIT_FUNNEL_FILTER:
|
|
return state.mergeIn([ 'funnelFilters' ], action.instance);
|
|
case INIT:
|
|
return state.set('instance', Funnel(action.instance))
|
|
case FETCH_LIST_SUCCESS:
|
|
return state.set('list', List(action.data).map(Funnel))
|
|
case FETCH_ISSUES_SUCCESS:
|
|
return state
|
|
.set('issues', List(action.data.issues.significant).map(FunnelIssue))
|
|
.set('criticalIssuesCount', action.data.issues.criticalIssuesCount)
|
|
case FETCH_SESSIONS_SUCCESS:
|
|
return state
|
|
.set('sessions', List(action.data.sessions).map(s => new Session(s)))
|
|
.set('total', action.data.total)
|
|
case FETCH_ISSUE_SUCCESS:
|
|
return state
|
|
.set('issue', FunnelIssue(action.data.issue))
|
|
.set('sessions', List(action.data.sessions.sessions).map(s => new Session(s)))
|
|
.set('sessionsTotal', action.data.sessions.total)
|
|
case RESET_ISSUE:
|
|
return state.set('isses', FunnelIssue())
|
|
.set('sections', List())
|
|
.set('sessionsTotal', 0);
|
|
case FETCH_SUCCESS:
|
|
const funnel = Funnel(action.data);
|
|
return state.set('instance', funnel)
|
|
case FETCH_ISSUE_TYPES_SUCCESS:
|
|
const tmpMap = {};
|
|
action.data.forEach(element => {
|
|
tmpMap[element.type] = element.title
|
|
});
|
|
return state
|
|
.set('issueTypes', List(action.data.map(({ type, title }) => ({ text: title, value: type }))))
|
|
.set('issueTypesMap', tmpMap);
|
|
case FETCH_INSIGHTS_SUCCESS:
|
|
let stages = [];
|
|
if (action.isRefresh) {
|
|
const activeStages = state.get('activeStages');
|
|
const oldInsights = state.get('insights');
|
|
const lastStage = action.data.stages[action.data.stages.length - 1]
|
|
const lastStageIndex = activeStages.toJS()[1];
|
|
stages = oldInsights.stages.map((stage, i) => {
|
|
stage.dropDueToIssues = lastStageIndex === i ? lastStage.dropDueToIssues : 0;
|
|
return stage;
|
|
});
|
|
return state.set('insights', Funnel({ totalDropDueToIssues: action.data.totalDropDueToIssues, stages, activeStages: activeStages.toJS() }));
|
|
} else {
|
|
stages = action.data.stages.map((stage, i) => {
|
|
stage.dropDueToIssues = 0;
|
|
return stage;
|
|
});
|
|
return state.set('insights', Funnel({ ...action.data, stages }))
|
|
}
|
|
case SAVE_SUCCESS:
|
|
case UPDATE_SUCCESS:
|
|
return state.update('list', itemInListUpdater(CustomField(action.data)))
|
|
case REMOVE_SUCCESS:
|
|
return state.update('list', list => list.filter(item => item.index !== action.index));
|
|
case APPLY_FILTER:
|
|
return state.mergeIn([ action.filterType ], Array.isArray(action.filter) ? action.filter : Map(action.filter));
|
|
case APPLY_ISSUE_FILTER:
|
|
return state.mergeIn(['issueFilters'], action.filter)
|
|
case REMOVE_ISSUE_FILTER:
|
|
return state.updateIn(['issueFilters', 'filters'], list => list.filter(item => item !== action.errorType))
|
|
case SET_ACTIVE_STAGES:
|
|
return state.set('activeStages', List(action.stages))
|
|
case SET_NAV_REF:
|
|
return state.set('navRef', action.navRef);
|
|
case SET_SESSIONS_SORT:
|
|
const comparator = (s1, s2) => {
|
|
let diff = s1[ action.sortKey ] - s2[ action.sortKey ];
|
|
diff = diff === 0 ? s1.startedAt - s2.startedAt : diff;
|
|
return action.sign * diff;
|
|
};
|
|
return state
|
|
.update('sessions', list => list.sort(comparator))
|
|
.set('sessionsSort', { sort: action.sort, sign: action.sign });
|
|
case RESET_FUNNEL:
|
|
return state
|
|
.set('instance', Funnel())
|
|
.set('activeStages', List())
|
|
.set('issuesSort', Map({}))
|
|
// .set('funnelFilters', Map(defaultDateFilters))
|
|
.set('insights', Funnel())
|
|
.set('issues', List())
|
|
.set('sessions', List());
|
|
default:
|
|
return state;
|
|
}
|
|
}
|
|
|
|
export const fetchList = (range) => {
|
|
return {
|
|
types: array(FETCH_LIST),
|
|
call: client => client.get(`/funnels`),
|
|
}
|
|
}
|
|
|
|
export const fetch = (funnelId, params) => (dispatch, getState) => {
|
|
return dispatch({
|
|
types: array(FETCH),
|
|
call: client => client.get(`/funnels/${funnelId}`, params)
|
|
});
|
|
}
|
|
|
|
// const eventMap = ({value, type, key, operator, source, custom}) => ({value, type, key, operator, source, custom});
|
|
// const filterMap = ({value, type, key, operator, source, custom }) => ({value: Array.isArray(value) ? value: [value], custom, type, key, operator, source});
|
|
|
|
function getParams(params, state) {
|
|
const filter = state.getIn([ 'funnels', 'instance', 'filter']).toData();
|
|
filter.filters = filter.filters.map(filterMap);
|
|
const funnelFilters = state.getIn([ 'funnels', 'funnelFilters']).toJS();
|
|
|
|
// const appliedFilter = state.getIn([ 'funnels', 'instance', 'filter' ]);
|
|
// const filter = appliedFilter
|
|
// .update('events', list => list.map(event => event.set('value', event.value || '*')).map(eventMap))
|
|
// .toJS();
|
|
|
|
// filter.filters = state.getIn([ 'funnelFilters', 'appliedFilter', 'filters' ])
|
|
// .map(filterMap).toJS();
|
|
|
|
return { ...filter, ...funnelFilters };
|
|
}
|
|
|
|
export const fetchInsights = (funnelId, params = {}, isRefresh = false) => (dispatch, getState) => {
|
|
return dispatch({
|
|
types: array(FETCH_INSIGHTS),
|
|
call: client => client.post(`/funnels/${funnelId}/insights`, getParams(params, getState())),
|
|
isRefresh
|
|
})
|
|
}
|
|
|
|
|
|
export const fetchFiltered = (funnelId, params) => (dispatch, getState) => {
|
|
return dispatch({
|
|
types: array(FETCH),
|
|
call: client => client.post(`/funnels/${funnelId}`, params),
|
|
})
|
|
}
|
|
|
|
export const fetchIssuesFiltered = (funnelId, params) => (dispatch, getState) => {
|
|
return dispatch({
|
|
types: array(FETCH_ISSUES),
|
|
call: client => client.post(`/funnels/${funnelId}/issues`, getParams(params, getState())),
|
|
})
|
|
}
|
|
|
|
export const fetchSessionsFiltered = (funnelId, params) => (dispatch, getState) => {
|
|
return dispatch({
|
|
types: array(FETCH_SESSIONS),
|
|
call: client => client.post(`/funnels/${funnelId}/sessions`, getParams(params, getState())),
|
|
})
|
|
}
|
|
|
|
export const fetchIssue = (funnelId, issueId, params) => (dispatch, getState) => {
|
|
const filters = getState().getIn([ 'funnelFilters', 'appliedFilter' ]);
|
|
const _params = { ...filters.toData(), ...params };
|
|
return dispatch({
|
|
types: array(FETCH_ISSUE),
|
|
call: client => client.post(`/funnels/${funnelId}/issues/${issueId}/sessions`, _params),
|
|
})
|
|
}
|
|
|
|
export const fetchIssues = (funnelId, params) => {
|
|
return {
|
|
types: array(FETCH_ISSUES),
|
|
call: client => client.get(`/funnels/${funnelId}/issues`, params),
|
|
}
|
|
}
|
|
|
|
export const fetchSessions = (funnelId, params) => {
|
|
return {
|
|
types: array(FETCH_SESSIONS),
|
|
call: client => client.get(`/funnels/${funnelId}/sessions`, params),
|
|
}
|
|
}
|
|
|
|
export const fetchIssueTypes = () => {
|
|
return {
|
|
types: array(FETCH_ISSUE_TYPES),
|
|
call: client => client.get(`/funnels/issue_types`),
|
|
}
|
|
}
|
|
|
|
export const save = () => (dispatch, getState) => {
|
|
const instance = getState().getIn([ 'funnels', 'instance'])
|
|
const filter = instance.get('filter').toData();
|
|
filter.filters = filter.filters.map(filterMap);
|
|
const isExist = instance.exists();
|
|
|
|
const _instance = instance instanceof Funnel ? instance : Funnel(instance);
|
|
const url = isExist ? `/funnels/${ _instance[idKey] }` : `/funnels`;
|
|
|
|
return dispatch({
|
|
types: array(isExist ? SAVE : UPDATE),
|
|
call: client => client.post(url, { ..._instance.toData(), filter }),
|
|
});
|
|
}
|
|
|
|
export const updateFunnelFilters = (funnelId, filter) => {
|
|
return {
|
|
types: array(UPDATE),
|
|
call: client => client.post(`/funnels/${funnelId}`, { filter }),
|
|
}
|
|
}
|
|
|
|
export const remove = (index) => {
|
|
return {
|
|
types: array(REMOVE),
|
|
call: client => client.delete(`/funnels/${index}`),
|
|
index,
|
|
}
|
|
}
|
|
|
|
export const applyFilter = (filterType='funnelFilters', filter) => {
|
|
return {
|
|
type: APPLY_FILTER,
|
|
filter,
|
|
filterType,
|
|
}
|
|
};
|
|
|
|
export const applyIssueFilter = (filter) => {
|
|
return {
|
|
type: APPLY_ISSUE_FILTER,
|
|
filter
|
|
}
|
|
};
|
|
|
|
export const removeIssueFilter = errorType => {
|
|
return {
|
|
type: REMOVE_ISSUE_FILTER,
|
|
errorType,
|
|
}
|
|
};
|
|
|
|
export const setActiveStages = (stages, filters, funnelId, forceRrefresh = false) => (dispatch, getState) => {
|
|
dispatch({
|
|
type: SET_ACTIVE_STAGES,
|
|
stages,
|
|
})
|
|
|
|
if (stages.length === 2) {
|
|
const filter = {...filters.toData(), firstStage: stages[0] + 1, lastStage: stages[1] + 1 };
|
|
dispatch(fetchIssuesFiltered(funnelId, filter))
|
|
dispatch(fetchInsights(funnelId, filter, true));
|
|
dispatch(fetchSessionsFiltered(funnelId, filter));
|
|
} else if (forceRrefresh) {
|
|
const filter = {...filters.toData()};
|
|
dispatch(fetchIssuesFiltered(funnelId, filter))
|
|
dispatch(fetchInsights(funnelId, filter));
|
|
dispatch(fetchSessionsFiltered(funnelId, filter));
|
|
}
|
|
};
|
|
|
|
export const edit = instance => {
|
|
return {
|
|
type: EDIT,
|
|
instance,
|
|
}
|
|
};
|
|
|
|
export const init = instance => {
|
|
return {
|
|
type: INIT,
|
|
instance,
|
|
}
|
|
};
|
|
|
|
export const setNavRef = ref => {
|
|
return {
|
|
type: SET_NAV_REF,
|
|
navRef: ref
|
|
}
|
|
};
|
|
|
|
export const resetIssue = () => {
|
|
return {
|
|
type: RESET_ISSUE,
|
|
}
|
|
}
|
|
|
|
export const resetFunnel = () => {
|
|
return {
|
|
type: RESET_FUNNEL,
|
|
}
|
|
}
|
|
|
|
export const setSessionsSort = (sortKey, sign = 1) => {
|
|
return {
|
|
type: SET_SESSIONS_SORT,
|
|
sortKey,
|
|
sign
|
|
}
|
|
}
|
|
|
|
export const blink = (state = true) => {
|
|
return {
|
|
type: BLINK,
|
|
state
|
|
}
|
|
}
|
|
|
|
export const refresh = (funnelId) => (dispatch, getState) => {
|
|
// dispatch(fetch(funnelId))
|
|
dispatch(fetchInsights(funnelId))
|
|
dispatch(fetchIssuesFiltered(funnelId, {}))
|
|
dispatch(fetchSessionsFiltered(funnelId, {}))
|
|
}
|
|
|
|
export default mergeReducers(
|
|
reducer,
|
|
createRequestReducer({
|
|
fetchRequest: FETCH,
|
|
fetchListRequest: FETCH_LIST,
|
|
fetchInsights: FETCH_INSIGHTS,
|
|
fetchIssueRequest: FETCH_ISSUE,
|
|
saveRequest: SAVE,
|
|
updateRequest: UPDATE,
|
|
fetchIssuesRequest: FETCH_ISSUES,
|
|
fetchSessionsRequest: FETCH_SESSIONS,
|
|
}),
|
|
)
|
|
|
|
const reduceThenFetchList = actionCreator => (...args) => (dispatch, getState) => {
|
|
dispatch(actionCreator(...args));
|
|
dispatch(refresh(getState().getIn([ 'funnels', 'instance', idKey ])));
|
|
|
|
// const filter = getState().getIn([ 'funnels', 'instance', 'filter']).toData();
|
|
// filter.filters = filter.filters.map(filterMap);
|
|
|
|
// return dispatch(fetchSessionList(filter));
|
|
};
|
|
|
|
|
|
export const editFilter = reduceThenFetchList((instance) => ({
|
|
type: EDIT_FILTER,
|
|
instance,
|
|
}));
|
|
|
|
export const editFunnelFilter = reduceThenFetchList((instance) => ({
|
|
type: EDIT_FUNNEL_FILTER,
|
|
instance,
|
|
}));
|
|
|
|
export const addFilter = (filter) => (dispatch, getState) => {
|
|
filter.value = checkFilterValue(filter.value);
|
|
const instance = getState().getIn([ 'funnels', 'instance', 'filter']);
|
|
|
|
if (hasFilterApplied(instance.filters, filter)) {
|
|
|
|
} else {
|
|
const filters = instance.filters.push(filter);
|
|
return dispatch(editFilter(instance.set('filters', filters)));
|
|
}
|
|
}
|
|
|
|
export const addFilterByKeyAndValue = (key, value) => (dispatch, getState) => {
|
|
let defaultFilter = filtersMap[key];
|
|
defaultFilter.value = value;
|
|
dispatch(addFilter(defaultFilter));
|
|
} |