diff --git a/frontend/app/components/Funnels/FunnelDetails/FunnelDetails.js b/frontend/app/components/Funnels/FunnelDetails/FunnelDetails.js
index 0a8a997dd..a00ea34e5 100644
--- a/frontend/app/components/Funnels/FunnelDetails/FunnelDetails.js
+++ b/frontend/app/components/Funnels/FunnelDetails/FunnelDetails.js
@@ -7,12 +7,13 @@ import FunnelOverview from 'Components/Funnels/FunnelOverview'
import FunnelIssues from 'Components/Funnels/FunnelIssues'
import { connect } from 'react-redux';
import {
- fetch, fetchInsights, fetchList, fetchFiltered, fetchIssuesFiltered, fetchSessionsFiltered, fetchIssueTypes, resetFunnel
+ fetch, fetchInsights, fetchList, fetchFiltered, fetchIssuesFiltered, fetchSessionsFiltered, fetchIssueTypes, resetFunnel, refresh
} from 'Duck/funnels';
import { applyFilter, setFilterOptions, resetFunnelFilters, setInitialFilters } from 'Duck/funnelFilters';
import { withRouter } from 'react-router';
import { sessions as sessionsRoute, funnel as funnelRoute, withSiteId } from 'App/routes';
import EventFilter from 'Shared/EventFilter';
+import FunnelSearch from 'Shared/FunnelSearch';
import cn from 'classnames';
import IssuesEmptyMessage from 'Components/Funnels/IssuesEmptyMessage'
@@ -26,7 +27,7 @@ const TABS = [ TAB_ISSUES, TAB_SESSIONS ].map(tab => ({
}));
const FunnelDetails = (props) => {
- const { insights, funnels, funnel, funnelId, loading, liveFilters, issuesLoading, sessionsLoading } = props;
+ const { insights, funnels, funnel, funnelId, loading, liveFilters, issuesLoading, sessionsLoading, refresh } = props;
const [activeTab, setActiveTab] = useState(TAB_ISSUES)
const [showFilters, setShowFilters] = useState(false)
const [mounted, setMounted] = useState(false);
@@ -40,16 +41,17 @@ const FunnelDetails = (props) => {
props.fetch(funnelId).then(() => {
setMounted(true);
+ }).then(() => {
+ props.refresh(funnelId);
})
-
- props.fetchInsights(funnelId, {})
+
}, []);
- useEffect(() => {
- if (funnel && funnel.filter && liveFilters.events.size === 0) {
- props.setInitialFilters();
- }
- }, [funnel])
+ // useEffect(() => {
+ // if (funnel && funnel.filter && liveFilters.events.size === 0) {
+ // props.setInitialFilters();
+ // }
+ // }, [funnel])
const onBack = () => {
props.history.push(sessionsRoute());
@@ -83,16 +85,19 @@ const FunnelDetails = (props) => {
redirect={redirect}
funnels={funnels}
onBack={onBack}
- funnelId={funnelId}
+ funnelId={parseInt(funnelId)}
toggleFilters={() => setShowFilters(!showFilters)}
showFilters={showFilters}
/>
- {showFilters &&
- setShowFilters(!showFilters)}
- />}
+ {showFilters && (
+
+ // setShowFilters(!showFilters)}
+ // />
+ )
+ }
{
fetchIssueTypes,
resetFunnel,
resetFunnelFilters,
- setInitialFilters
+ setInitialFilters,
+ refresh,
})(withRouter((FunnelDetails)))
diff --git a/frontend/app/components/Funnels/FunnelHeader/FunnelHeader.js b/frontend/app/components/Funnels/FunnelHeader/FunnelHeader.js
index 9897734f6..75303b71d 100644
--- a/frontend/app/components/Funnels/FunnelHeader/FunnelHeader.js
+++ b/frontend/app/components/Funnels/FunnelHeader/FunnelHeader.js
@@ -80,7 +80,7 @@ const FunnelHeader = (props) => {
selectOnBlur={false}
icon={ }
/>
-
+
-
@@ -117,5 +117,5 @@ const FunnelHeader = (props) => {
}
export default connect(state => ({
- funnelFilters: state.getIn([ 'funnelFilters', 'appliedFilter']),
+ funnelFilters: state.getIn([ 'funnels', 'instance', 'filter' ]),
}), { applyFilter, deleteFunnel, fetch, fetchInsights, fetchIssuesFiltered, fetchSessionsFiltered })(FunnelHeader)
diff --git a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx
index e8472ca51..4355ac7c9 100644
--- a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx
+++ b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx
@@ -8,9 +8,10 @@ interface Props {
onUpdateFilter: (filterIndex, filter) => void;
onRemoveFilter: (filterIndex) => void;
onChangeEventsOrder: (e, { name, value }) => void;
+ hideEventsOrder?: boolean;
}
function FilterList(props: Props) {
- const { filter } = props;
+ const { filter, hideEventsOrder = false } = props;
const filters = filter.filters;
const hasEvents = filter.filters.filter(i => i.isEvent).size > 0;
const hasFilters = filter.filters.filter(i => !i.isEvent).size > 0;
@@ -30,29 +31,32 @@ function FilterList(props: Props) {
<>
EVENTS
-
-
}
- content={ `Events Order` }
- size="tiny"
- inverted
- position="top center"
+ { !hideEventsOrder && (
+
+
}
+ content={ `Events Order` }
+ size="tiny"
+ inverted
+ position="top center"
+ />
+
+
+
-
-
+ )}
{filters.map((filter, filterIndex) => filter.isEvent ? (
i.isEvent).size > 0;
+ const hasFilters = appliedFilter.filters.filter(i => !i.isEvent).size > 0;
+
+ const onAddFilter = (filter) => {
+ props.addFilter(filter);
+ // filter.value = [""]
+ // const newFilters = appliedFilter.filters.concat(filter);
+ // props.edit({
+ // ...appliedFilter.filter,
+ // filters: newFilters,
+ // });
+ }
+
+ const onUpdateFilter = (filterIndex, filter) => {
+ const newFilters = appliedFilter.filters.map((_filter, i) => {
+ if (i === filterIndex) {
+ return filter;
+ } else {
+ return _filter;
+ }
+ });
+
+ props.editFilter({
+ ...appliedFilter,
+ filters: newFilters,
+ });
+ }
+
+ const onRemoveFilter = (filterIndex) => {
+ const newFilters = appliedFilter.filters.filter((_filter, i) => {
+ return i !== filterIndex;
+ });
+
+ props.editFilter({
+ filters: newFilters,
+ });
+ }
+
+ const onChangeEventsOrder = (e, { name, value }) => {
+ props.editFilter({
+ eventsOrder: value,
+ });
+ }
+
+ return (
+
+ );
+}
+
+export default connect(state => ({
+ appliedFilter: state.getIn([ 'funnels', 'instance', 'filter' ]),
+}), { editFilter, addFilter })(FunnelSearch);
\ No newline at end of file
diff --git a/frontend/app/components/shared/FunnelSearch/index.ts b/frontend/app/components/shared/FunnelSearch/index.ts
new file mode 100644
index 000000000..2db683671
--- /dev/null
+++ b/frontend/app/components/shared/FunnelSearch/index.ts
@@ -0,0 +1 @@
+export { default } from './FunnelSearch';
\ No newline at end of file
diff --git a/frontend/app/components/shared/UpdateFunnelButton/UpdateFunnelButton.tsx b/frontend/app/components/shared/UpdateFunnelButton/UpdateFunnelButton.tsx
new file mode 100644
index 000000000..50c1215ed
--- /dev/null
+++ b/frontend/app/components/shared/UpdateFunnelButton/UpdateFunnelButton.tsx
@@ -0,0 +1,27 @@
+import React, { useState } from 'react';
+import { IconButton } from 'UI';
+import FunnelSaveModal from 'App/components/Funnels/FunnelSaveModal';
+import { connect } from 'react-redux';
+import { save } from 'Duck/funnels';
+
+interface Props {
+ save: typeof save;
+ loading: boolean;
+}
+function UpdateFunnelButton(props: Props) {
+ const { loading } = props;
+ return (
+
+ props.save()} primaryText label="UPDATE FUNNEL" icon="funnel"
+ />
+
+ )
+}
+
+export default connect(state => ({
+ loading: state.getIn(['funnels', 'saveRequest', 'loading']) ||
+ state.getIn(['funnels', 'updateRequest', 'loading']),
+}), { save })(UpdateFunnelButton);
\ No newline at end of file
diff --git a/frontend/app/components/shared/UpdateFunnelButton/index.ts b/frontend/app/components/shared/UpdateFunnelButton/index.ts
new file mode 100644
index 000000000..7638c11c0
--- /dev/null
+++ b/frontend/app/components/shared/UpdateFunnelButton/index.ts
@@ -0,0 +1 @@
+export { default } from './UpdateFunnelButton';
\ No newline at end of file
diff --git a/frontend/app/duck/funnels.js b/frontend/app/duck/funnels.js
index a30916777..1278614be 100644
--- a/frontend/app/duck/funnels.js
+++ b/frontend/app/duck/funnels.js
@@ -7,7 +7,7 @@ import { createItemInListUpdater, mergeReducers, success, array } from './funcTo
import { createRequestReducer } from './funcTools/request';
import { getDateRangeFromValue } from 'App/dateRange';
import { LAST_7_DAYS } from 'Types/app/period';
-import { filterMap as searchFilterMap } from './search';
+import { filterMap, checkFilterValue, hasFilterApplied } from './search';
const name = 'funnel';
const idKey = 'funnelId';
@@ -23,6 +23,7 @@ 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 REMOVE = removeType('funnel/REMOVE');
const INIT = initType('funnel/INIT');
const SET_NAV_REF = 'funnels/SET_NAV_REF'
@@ -84,6 +85,8 @@ const reducer = (state = initialState, action = {}) => {
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 INIT:
return state.set('instance', Funnel(action.instance))
case FETCH_LIST_SUCCESS:
@@ -191,19 +194,22 @@ export const fetch = (funnelId, params) => (dispatch, getState) => {
});
}
-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});
+// 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 appliedFilters = state.getIn([ 'funnelFilters', 'appliedFilter' ]);
- const filter = appliedFilters
- .update('events', list => list.map(event => event.set('value', event.value || '*')).map(eventMap))
- .toJS();
-
- filter.filters = state.getIn([ 'funnelFilters', 'appliedFilter', 'filters' ])
- .map(filterMap).toJS();
+ const filter = state.getIn([ 'funnels', 'instance', 'filter']).toData();
+ filter.filters = filter.filters.map(filterMap);
- return {...filter, ...params };
+ // 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;
}
export const fetchInsights = (funnelId, params = {}, isRefresh = false) => (dispatch, getState) => {
@@ -266,18 +272,17 @@ export const fetchIssueTypes = () => {
}
}
-export const save = (instance) => (dispatch, getState) => {
-// export const save = (instance) => {
- const filter = getState().getIn([ 'search', 'instance']).toData();
- filter.filters = filter.filters.map(searchFilterMap);
+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 = _instance.exists()
- ? `/funnels/${ _instance[idKey] }`
- : `/funnels`;
+ const url = isExist ? `/funnels/${ _instance[idKey] }` : `/funnels`;
return dispatch({
- types: array(_instance.exists() ? SAVE : UPDATE),
+ types: array(isExist ? SAVE : UPDATE),
call: client => client.post(url, { ..._instance.toData(), filter }),
});
}
@@ -387,7 +392,7 @@ export const blink = (state = true) => {
}
export const refresh = (funnelId) => (dispatch, getState) => {
- dispatch(fetch(funnelId))
+ // dispatch(fetch(funnelId))
dispatch(fetchInsights(funnelId))
dispatch(fetchIssuesFiltered(funnelId, {}))
dispatch(fetchSessionsFiltered(funnelId, {}))
@@ -405,4 +410,38 @@ export default mergeReducers(
fetchIssuesRequest: FETCH_ISSUES,
fetchSessionsRequest: FETCH_SESSIONS,
}),
-)
\ No newline at end of file
+)
+
+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 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));
+}
\ No newline at end of file
diff --git a/frontend/app/types/filter/filter.js b/frontend/app/types/filter/filter.js
index a98326314..be186e4f9 100644
--- a/frontend/app/types/filter/filter.js
+++ b/frontend/app/types/filter/filter.js
@@ -95,7 +95,7 @@ export default Record({
startDate,
endDate,
events: List(events).map(Event),
- filters: List(filters).map(i => NewFilter(i).toData()),
+ filters: List(filters).map(i => NewFilter(i).toData()).concat(List(events).map(i => NewFilter(i).toData())),
custom: Map(custom),
}
}
diff --git a/frontend/app/types/funnel.js b/frontend/app/types/funnel.js
index a76136460..0a97cf944 100644
--- a/frontend/app/types/funnel.js
+++ b/frontend/app/types/funnel.js
@@ -80,7 +80,7 @@ export default Record({
conversionImpact,
firstStage: firstStage && firstStage.label + ' ' + truncate(firstStage.value || '', 10) || '',
lastStage: lastStage && lastStage.label + ' ' + truncate(lastStage.value || '', 10) || '',
- filter: Filter(filter),
+ filter: Filter(filter),
sessionsCount: lastStage && lastStage.sessionsCount,
stepsCount: stages ? stages.length : 0,
conversions: 100 - conversionImpact