From 06ba41b938905c33a2677127dbd3eb28287783eb Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Mon, 14 Feb 2022 16:09:30 +0100 Subject: [PATCH] feat(ui) - funnel filters replaced with new filter (#322) * feat(ui) - funnel filters replaced with new filter * feat(ui) - funnel filters - removed logs --- .../Funnels/FunnelDetails/FunnelDetails.js | 38 ++++---- .../Funnels/FunnelHeader/FunnelHeader.js | 4 +- .../shared/Filters/FilterList/FilterList.tsx | 48 +++++----- .../shared/FunnelSearch/FunnelSearch.tsx | 91 +++++++++++++++++++ .../components/shared/FunnelSearch/index.ts | 1 + .../UpdateFunnelButton/UpdateFunnelButton.tsx | 27 ++++++ .../shared/UpdateFunnelButton/index.ts | 1 + frontend/app/duck/funnels.js | 81 ++++++++++++----- frontend/app/types/filter/filter.js | 2 +- frontend/app/types/funnel.js | 2 +- 10 files changed, 232 insertions(+), 63 deletions(-) create mode 100644 frontend/app/components/shared/FunnelSearch/FunnelSearch.tsx create mode 100644 frontend/app/components/shared/FunnelSearch/index.ts create mode 100644 frontend/app/components/shared/UpdateFunnelButton/UpdateFunnelButton.tsx create mode 100644 frontend/app/components/shared/UpdateFunnelButton/index.ts 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
-
-
- Events Order
} - content={ `Events Order` } - size="tiny" - inverted - position="top center" + { !hideEventsOrder && ( +
+
+ Events Order
} + 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