From e5963fbeef8410adccd202899b1f7cdcd18ef04d Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Wed, 15 Jun 2022 14:14:48 +0200 Subject: [PATCH] feat(ui) - assist filters wip --- frontend/app/components/Assist/Assist.tsx | 7 + .../AssistSearchField/AssistSearchField.tsx | 32 ++++ .../Assist/AssistSearchField/index.ts | 1 + .../BugFinder/Filters/SortDropdown.js | 2 +- .../BugFinder/SessionsMenu/SessionsMenu.js | 27 +-- .../Client/CustomFields/CustomFields.js | 2 +- frontend/app/components/Header/Header.js | 35 +--- .../FilterSelection/FilterSelection.tsx | 3 +- .../LiveSessionList/LiveSessionList.tsx | 4 +- .../LiveSessionReloadButton.tsx | 8 +- .../LiveSessionSearch/LiveSessionSearch.tsx | 58 ++++--- .../shared/MainSearchBar/MainSearchBar.tsx | 24 ++- .../shared/SessionItem/SessionItem.tsx | 6 +- .../SessionSearchField/SessionSearchField.tsx | 16 +- .../components/ui/CountryFlag/CountryFlag.js | 2 +- .../components/ui/SavedSearchList/ListItem.js | 20 --- .../ui/SavedSearchList/SavedSearchList.js | 157 ------------------ .../components/ui/SavedSearchList/index.js | 1 - .../ui/SavedSearchList/listItem.module.css | 24 --- .../savedSearchList.module.css | 20 --- frontend/app/components/ui/index.js | 1 - frontend/app/duck/liveSearch.js | 67 ++++++-- frontend/app/duck/search.js | 1 - frontend/app/duck/sessions.js | 50 +----- frontend/app/mstore/types/filter.ts | 2 +- .../MessageDistributor/MessageDistributor.ts | 2 +- 26 files changed, 190 insertions(+), 382 deletions(-) create mode 100644 frontend/app/components/Assist/AssistSearchField/AssistSearchField.tsx create mode 100644 frontend/app/components/Assist/AssistSearchField/index.ts delete mode 100644 frontend/app/components/ui/SavedSearchList/ListItem.js delete mode 100644 frontend/app/components/ui/SavedSearchList/SavedSearchList.js delete mode 100644 frontend/app/components/ui/SavedSearchList/index.js delete mode 100644 frontend/app/components/ui/SavedSearchList/listItem.module.css delete mode 100644 frontend/app/components/ui/SavedSearchList/savedSearchList.module.css diff --git a/frontend/app/components/Assist/Assist.tsx b/frontend/app/components/Assist/Assist.tsx index 0d901337b..f6bcb85c1 100644 --- a/frontend/app/components/Assist/Assist.tsx +++ b/frontend/app/components/Assist/Assist.tsx @@ -4,13 +4,20 @@ import LiveSessionSearch from 'Shared/LiveSessionSearch'; import cn from 'classnames' import withPageTitle from 'HOCs/withPageTitle'; import withPermissions from 'HOCs/withPermissions' +import SessionSearch from '../shared/SessionSearch'; +import MainSearchBar from '../shared/MainSearchBar'; +import AssistSearchField from './AssistSearchField'; function Assist() { return (
+ + {/* */} + + {/* */}
diff --git a/frontend/app/components/Assist/AssistSearchField/AssistSearchField.tsx b/frontend/app/components/Assist/AssistSearchField/AssistSearchField.tsx new file mode 100644 index 000000000..5df3a7d78 --- /dev/null +++ b/frontend/app/components/Assist/AssistSearchField/AssistSearchField.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { Button } from 'UI'; +import SessionSearchField from 'Shared/SessionSearchField'; +// import { fetchFilterSearch } from 'Duck/search'; +import { connect } from 'react-redux'; +import { edit as editFilter, addFilterByKeyAndValue, clearSearch, fetchFilterSearch } from 'Duck/liveSearch'; +// import { clearSearch } from 'Duck/search'; + +function AssistSearchField(props: any) { + return ( +
+
+ +
+ +
+ ); +} + +export default connect(null, { + fetchFilterSearch, editFilter, addFilterByKeyAndValue, clearSearch +})(AssistSearchField); \ No newline at end of file diff --git a/frontend/app/components/Assist/AssistSearchField/index.ts b/frontend/app/components/Assist/AssistSearchField/index.ts new file mode 100644 index 000000000..8ecf4e244 --- /dev/null +++ b/frontend/app/components/Assist/AssistSearchField/index.ts @@ -0,0 +1 @@ +export { default } from './AssistSearchField' \ No newline at end of file diff --git a/frontend/app/components/BugFinder/Filters/SortDropdown.js b/frontend/app/components/BugFinder/Filters/SortDropdown.js index d75b4222f..398902ec5 100644 --- a/frontend/app/components/BugFinder/Filters/SortDropdown.js +++ b/frontend/app/components/BugFinder/Filters/SortDropdown.js @@ -10,6 +10,7 @@ import stl from './sortDropdown.module.css'; export default class SortDropdown extends React.PureComponent { state = { value: null } sort = ({ value }) => { + value = value.value this.setState({ value: value }) const [ sort, order ] = value.split('-'); const sign = order === 'desc' ? -1 : 1; @@ -25,7 +26,6 @@ export default class SortDropdown extends React.PureComponent { setShowModal(true) } onBlur={ () => setTimeout(setShowModal, 200, false) } onChange={ onSearchChange } - // icon="search" - // iconPosition="left" placeholder={ 'Search sessions using any captured event (click, input, page, error...)'} - // fluid id="search" type="search" autoComplete="off" @@ -57,4 +51,4 @@ function SessionSearchField(props: Props) { ); } -export default connect(null, { fetchFilterSearch, editFilter, addFilterByKeyAndValue })(SessionSearchField); \ No newline at end of file +export default connect(null, { })(SessionSearchField); \ No newline at end of file diff --git a/frontend/app/components/ui/CountryFlag/CountryFlag.js b/frontend/app/components/ui/CountryFlag/CountryFlag.js index 700304dde..5edf8b275 100644 --- a/frontend/app/components/ui/CountryFlag/CountryFlag.js +++ b/frontend/app/components/ui/CountryFlag/CountryFlag.js @@ -12,7 +12,7 @@ const CountryFlag = React.memo(({ country, className, style = {}, label = false return (
{knownCountry - ?
+ ?
: (
diff --git a/frontend/app/components/ui/SavedSearchList/ListItem.js b/frontend/app/components/ui/SavedSearchList/ListItem.js deleted file mode 100644 index d38762c63..000000000 --- a/frontend/app/components/ui/SavedSearchList/ListItem.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import { Icon } from 'UI'; -import cn from "classnames"; -import stl from './listItem.module.css'; - -const ListItem = ({icon, label, onClick, onRemove }) => { - return ( -
-
- - { label } -
-
- -
-
- ); -}; - -export default ListItem; diff --git a/frontend/app/components/ui/SavedSearchList/SavedSearchList.js b/frontend/app/components/ui/SavedSearchList/SavedSearchList.js deleted file mode 100644 index 98c4d5208..000000000 --- a/frontend/app/components/ui/SavedSearchList/SavedSearchList.js +++ /dev/null @@ -1,157 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import stl from './savedSearchList.module.css'; -import cn from 'classnames'; -import { Icon, IconButton, Loader, Button } from 'UI'; -import { confirm } from 'UI'; -import { withRouter } from 'react-router-dom'; -import { addFilterByKeyAndValue } from 'Duck/search'; -import { - fetchList as fetchFunnelsList, - remove as deleteSearch, - // clearEvents, - init -} from 'Duck/funnels'; -import { setActiveFlow, clearEvents } from 'Duck/filters'; -import { setActiveTab } from 'Duck/sessions'; -import { funnel as funnelRoute, withSiteId } from 'App/routes'; -import Event, { TYPES } from 'Types/filter/event'; -import FunnelMenuItem from 'Components/Funnels/FunnelMenuItem'; -import FunnelSaveModal from 'Components/Funnels/FunnelSaveModal'; -import { blink as setBlink } from 'Duck/funnels'; -import { FilterKey } from 'Types/filter/filterType'; - -const DEFAULT_VISIBLE = 3; -@withRouter -class SavedSearchList extends React.Component { - state = { showMore: false, showSaveModal: false } - - setFlow = flow => { - this.props.setActiveTab({ name: 'All', type: 'all' }); - this.props.setActiveFlow(flow) - if (flow && flow.type === 'flows') { - this.props.clearEvents() - } - } - - renameHandler = funnel => { - this.props.init(funnel); - this.setState({ showSaveModal: true }) - } - - deleteSearch = async (e, funnel) => { - e.preventDefault(); - e.stopPropagation(); - - if (await confirm({ - header: 'Delete Funnel', - confirmButton: 'Delete', - confirmation: `Are you sure you want to permanently delete "${funnel.name}"?` - })) { - this.props.deleteSearch(funnel.funnelId).then(function() { - this.props.fetchFunnelsList(); - this.setState({ showSaveModal: false }) - }.bind(this)); - } else {} - } - - createHandler = () => { - const { filters } = this.props; - if (filters.size === 0) { - this.props.addFilterByKeyAndValue(FilterKey.LOCATION, ''); - this.props.addFilterByKeyAndValue(FilterKey.LOCATION, ''); - this.props.addFilterByKeyAndValue(FilterKey.CLICK, '') - } else { - this.props.setBlink() - } - } - - onFlowClick = ({ funnelId }) => { - const { siteId, history } = this.props; - history.push(withSiteId(funnelRoute(funnelId), siteId)); - } - - render() { - const { funnels, activeFlow, activeTab, loading } = this.props; - const { showMore, showSaveModal } = this.state; - const shouldLimit = funnels.size > DEFAULT_VISIBLE; - - return ( -
- this.setState({ showSaveModal: false })} - /> - -
-
- Funnels - - { funnels.size > 0 && ( - - )} -
-
- { funnels.size === 0 && -
-
- Funnels makes it easy to uncover the most significant issues that impacted conversions. -
- -
- } - { funnels.size > 0 && - - { funnels.take(showMore ? funnels.size : DEFAULT_VISIBLE).map(filter => ( -
- this.onFlowClick(filter)} - deleteHandler={ (e) => this.deleteSearch(e, filter) } - renameHandler={() => this.renameHandler(filter)} - /> -
- ))} - { shouldLimit && -
this.setState({ showMore: !showMore})} - className={cn(stl.showMore, 'cursor-pointer py-2 flex items-center')} - > - {/* */} - { showMore ? 'SHOW LESS' : 'SHOW MORE' } -
- } -
- } -
-
- ); - } -} - -export default connect(state => ({ - funnels: state.getIn([ 'funnels', 'list' ]), - loading: state.getIn(['funnels', 'fetchListRequest', 'loading']), - activeFlow: state.getIn([ 'filters', 'activeFlow' ]), - activeTab: state.getIn([ 'sessions', 'activeTab' ]), - siteId: state.getIn([ 'site', 'siteId' ]), - events: state.getIn([ 'filters', 'appliedFilter', 'events' ]), - filters: state.getIn([ 'search', 'instance', 'filters' ]), -}), { - deleteSearch, setActiveTab, - setActiveFlow, clearEvents, - addFilterByKeyAndValue, - init, - fetchFunnelsList, - setBlink -})(SavedSearchList) diff --git a/frontend/app/components/ui/SavedSearchList/index.js b/frontend/app/components/ui/SavedSearchList/index.js deleted file mode 100644 index 009c383d7..000000000 --- a/frontend/app/components/ui/SavedSearchList/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SavedSearchList' \ No newline at end of file diff --git a/frontend/app/components/ui/SavedSearchList/listItem.module.css b/frontend/app/components/ui/SavedSearchList/listItem.module.css deleted file mode 100644 index dcf9fdddd..000000000 --- a/frontend/app/components/ui/SavedSearchList/listItem.module.css +++ /dev/null @@ -1,24 +0,0 @@ -.wrapper { - padding: 4px 5px; - cursor: pointer; - border: solid thin transparent; - border-radius: 3px; - margin-left: -5px; - - &.active, - &:hover { - background-color: $active-blue; - border-color: $active-blue-border; - & .actionWrapper { - opacity: 1; - } - } - - & span { - color: $teal - } - - & .actionWrapper { - opacity: 0; - } -} \ No newline at end of file diff --git a/frontend/app/components/ui/SavedSearchList/savedSearchList.module.css b/frontend/app/components/ui/SavedSearchList/savedSearchList.module.css deleted file mode 100644 index 1b6a2aeb4..000000000 --- a/frontend/app/components/ui/SavedSearchList/savedSearchList.module.css +++ /dev/null @@ -1,20 +0,0 @@ -.header { - margin-bottom: 15px; - & .label { - text-transform: uppercase; - color: gray; - letter-spacing: 0.2em; - } -} - -.showMore { - &:hover { - color: $teal; - & svg { - fill: $teal; - } - & .actions { - opacity: 1; - } - } -} \ No newline at end of file diff --git a/frontend/app/components/ui/index.js b/frontend/app/components/ui/index.js index c6b43f5b1..8ac4e8c05 100644 --- a/frontend/app/components/ui/index.js +++ b/frontend/app/components/ui/index.js @@ -30,7 +30,6 @@ export { default as JSONTree } from './JSONTree'; export { default as Tooltip } from './Tooltip'; export { default as CountryFlag } from './CountryFlag'; export { default as RandomElement } from './RandomElement'; -export { default as SavedSearchList } from './SavedSearchList'; export { default as SplitButton } from './SplitButton'; export { default as confirm } from './Confirmation'; export { default as SideMenuitem } from './SideMenuitem'; diff --git a/frontend/app/duck/liveSearch.js b/frontend/app/duck/liveSearch.js index 5a9ffd85b..fce73539a 100644 --- a/frontend/app/duck/liveSearch.js +++ b/frontend/app/duck/liveSearch.js @@ -1,21 +1,24 @@ import { List, Map } from 'immutable'; -import { fetchType, editType } from './funcTools/crud'; +import { fetchListType, fetchType, editType } from './funcTools/crud'; import { createRequestReducer } from './funcTools/request'; -import { mergeReducers } from './funcTools/tools'; +import { mergeReducers, success } from './funcTools/tools'; import Filter from 'Types/filter'; -import { fetchList as fetchSessionList } from './sessions'; -import { liveFiltersMap } from 'Types/filter/newFilter'; +// import { fetchList as fetchSessionList } from './sessions'; +import { liveFiltersMap, filtersMap } from 'Types/filter/newFilter'; import { filterMap, checkFilterValue, hasFilterApplied } from './search'; +import Session from 'Types/session'; const name = "liveSearch"; const idKey = "searchId"; +const FETCH_FILTER_SEARCH = fetchListType(`${name}/FILTER_SEARCH`); const FETCH = fetchType(name); const EDIT = editType(name); const CLEAR_SEARCH = `${name}/CLEAR_SEARCH`; const APPLY = `${name}/APPLY`; const UPDATE_CURRENT_PAGE = `${name}/UPDATE_CURRENT_PAGE`; const UPDATE_SORT = `${name}/UPDATE_SORT`; +const FETCH_SESSION_LIST = fetchListType(`${name}/FETCH_SESSION_LIST`); const initialState = Map({ list: List(), @@ -36,6 +39,23 @@ function reducer(state = initialState, action = {}) { return state.set('currentPage', action.page); case UPDATE_SORT: return state.mergeIn(['sort'], action.sort); + case FETCH_SESSION_LIST: + const { sessions, total } = action.data; + const list = List(sessions).map(Session); + return state + .set('list', list) + .set('total', total); + case success(FETCH_FILTER_SEARCH): + const groupedList = action.data.reduce((acc, item) => { + const { projectId, type, value } = item; + const key = type; + if (!acc[key]) { + acc[key] = []; + } + acc[key].push({ projectId, value }); + return acc; + }, {}); + return state.set('filterSearchList', groupedList); } return state; } @@ -44,21 +64,32 @@ export default mergeReducers( reducer, createRequestReducer({ fetch: FETCH, + fetchList: FETCH_SESSION_LIST, }), ); const reduceThenFetchResource = actionCreator => (...args) => (dispatch, getState) => { dispatch(actionCreator(...args)); - const filter = getState().getIn([ 'search', 'instance']).toData(); + const filter = getState().getIn([ 'liveSearch', 'instance']).toData(); filter.filters = filter.filters.map(filterMap); + filter.limit = 10; + filter.page = getState().getIn([ 'liveSearch', 'currentPage']); + return dispatch(fetchSessionList(filter)); }; -export const edit = (instance) => ({ - type: EDIT, - instance, -}); +export const fetchSessionList = (filter) => { + return { + types: FETCH_SESSION_LIST.array, + call: client => client.post('/assist/sessions', filter), + } +} + +export const edit = reduceThenFetchResource((instance) => ({ + type: EDIT, + instance, +})); export const applyFilter = reduceThenFetchResource((filter, fromUrl=false) => ({ type: APPLY, @@ -67,7 +98,7 @@ export const applyFilter = reduceThenFetchResource((filter, fromUrl=false) => ({ })); export const fetchSessions = (filter) => (dispatch, getState) => { - const _filter = filter ? filter : getState().getIn([ 'search', 'instance']); + const _filter = filter ? filter : getState().getIn([ 'liveSearch', 'instance']); return dispatch(applyFilter(_filter)); }; @@ -93,9 +124,12 @@ export const addFilter = (filter) => (dispatch, getState) => { } } -export const addFilterByKeyAndValue = (key, value) => (dispatch, getState) => { - let defaultFilter = liveFiltersMap[key]; +export const addFilterByKeyAndValue = (key, value, operator = undefined) => (dispatch, getState) => { + let defaultFilter = filtersMap[key]; defaultFilter.value = value; + if (operator) { + defaultFilter.operator = operator; + } dispatch(addFilter(defaultFilter)); } @@ -111,4 +145,13 @@ export function updateSort(sort) { type: UPDATE_SORT, sort, }; +} + +export function fetchFilterSearch(params) { + params.live = true + return { + types: FETCH_FILTER_SEARCH.array, + call: client => client.get('/events/search', params), + params, + }; } \ No newline at end of file diff --git a/frontend/app/duck/search.js b/frontend/app/duck/search.js index a5e1d89c0..9a4498439 100644 --- a/frontend/app/duck/search.js +++ b/frontend/app/duck/search.js @@ -171,7 +171,6 @@ export const reduceThenFetchResource = actionCreator => (...args) => (dispatch, } } - return isRoute(ERRORS_ROUTE, window.location.pathname) ? dispatch(fetchErrorsList(filter)) : dispatch(fetchSessionList(filter)); diff --git a/frontend/app/duck/sessions.js b/frontend/app/duck/sessions.js index 47b960bc2..8887b03f2 100644 --- a/frontend/app/duck/sessions.js +++ b/frontend/app/duck/sessions.js @@ -1,7 +1,7 @@ import { List, Map } from 'immutable'; import Session from 'Types/session'; import ErrorStack from 'Types/session/errorStack'; -import Watchdog, { getSessionWatchdogTypes } from 'Types/watchdog'; +import Watchdog from 'Types/watchdog'; import { clean as cleanParams } from 'App/api_client'; import withRequestState, { RequestTypes } from './requestStateCreator'; import { getRE } from 'App/utils'; @@ -83,55 +83,11 @@ const reducer = (state = initialState, action = {}) => { const { sessions, total } = action.data; const list = List(sessions).map(Session); - const { params } = action; - const eventProperties = { - eventCount: 0, - eventTypes: [], - dateFilter: params.rangeValue, - filterKeys: Object.keys(params) - .filter(key => ![ 'custom', 'startDate', 'endDate', 'strict', 'key', 'events', 'rangeValue' ].includes(key)), - returnedCount: list.size, - totalSearchCount: total, - }; - if (Array.isArray(params.events)) { - eventProperties.eventCount = params.events.length; - params.events.forEach(({ type }) => { - if (!eventProperties.eventTypes.includes(type)) { - eventProperties.eventTypes.push(type); - } - }) - } - - const keyMap = {} - list.forEach(s => { - s.issueTypes.forEach(k => { - if(keyMap[k]) - keyMap[k] += 1 - else - keyMap[k] = 1; - }) - }) - - const wdTypeCount = {} - try{ - list.forEach(s => { - getSessionWatchdogTypes(s).forEach(wdtp => { - wdTypeCount[wdtp] = wdTypeCount[wdtp] ? wdTypeCount[wdtp] + 1 : 1; - }) - }) - } catch(e) { - - } - - const sessionIds = list.map(({ sessionId }) => sessionId ).toJS(); - return state .set('list', list) - .set('sessionIds', sessionIds) + .set('sessionIds', list.map(({ sessionId }) => sessionId ).toJS()) .set('favoriteList', list.filter(({ favorite }) => favorite)) - .set('total', total) - .set('keyMap', keyMap) - .set('wdTypeCount', wdTypeCount); + .set('total', total); case SET_AUTOPLAY_VALUES: { const sessionIds = state.get('sessionIds') const currentSessionId = state.get('current').sessionId diff --git a/frontend/app/mstore/types/filter.ts b/frontend/app/mstore/types/filter.ts index 11434e660..3635993f7 100644 --- a/frontend/app/mstore/types/filter.ts +++ b/frontend/app/mstore/types/filter.ts @@ -70,7 +70,7 @@ export default class Filter implements IFilter { this.filters.splice(index, 1) } - fromJson(json) { + fromJson(json: any) { this.name = json.name this.filters = json.filters.map(i => new FilterItem().fromJson(i)) this.eventsOrder = json.eventsOrder diff --git a/frontend/app/player/MessageDistributor/MessageDistributor.ts b/frontend/app/player/MessageDistributor/MessageDistributor.ts index 59e567a1b..582cd7211 100644 --- a/frontend/app/player/MessageDistributor/MessageDistributor.ts +++ b/frontend/app/player/MessageDistributor/MessageDistributor.ts @@ -133,7 +133,7 @@ export default class MessageDistributor extends StatedScreen { const r = new MFileReader(new Uint8Array(), this.sessionStart) const msgs: Array = [] - loadFiles([this.session.mobsUrl], + loadFiles(this.session.mobsUrl, b => { r.append(b) let next: ReturnType