From 1d1ebe7d96c3e806f580306512b3830feca3f1f1 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 28 Jan 2022 21:35:17 +0530 Subject: [PATCH] feat(ui) - filters - saved search and other fixes --- .../app/components/BugFinder/BugFinder.js | 6 +- .../SaveFilterButton/SaveFilterButton.tsx | 13 ++++- .../SaveSearchModal/SaveSearchModal.tsx | 58 +++++++++++++------ .../shared/SavedSearch/SavedSearch.css | 4 ++ .../shared/SavedSearch/SavedSearch.tsx | 26 ++++++--- .../SavedSearchDropdown.css | 8 +++ .../SavedSearchDropdown.tsx | 17 +++--- .../shared/SessionSearch/SessionSearch.tsx | 1 - frontend/app/duck/search.js | 51 +++++++++++----- frontend/app/types/filter/savedFilter.js | 6 +- 10 files changed, 132 insertions(+), 58 deletions(-) create mode 100644 frontend/app/components/shared/SavedSearch/SavedSearch.css diff --git a/frontend/app/components/BugFinder/BugFinder.js b/frontend/app/components/BugFinder/BugFinder.js index dee0470ce..5bbefc725 100644 --- a/frontend/app/components/BugFinder/BugFinder.js +++ b/frontend/app/components/BugFinder/BugFinder.js @@ -31,7 +31,7 @@ import SessionSearchField from 'Shared/SessionSearchField' import SavedSearch from 'Shared/SavedSearch' import LiveSessionList from './LiveSessionList' import SessionSearch from 'Shared/SessionSearch'; -import { edit as editSearch } from 'Duck/search'; +import { clearSearch } from 'Duck/search'; import { Button } from 'UI'; const weakEqual = (val1, val2) => { @@ -82,7 +82,7 @@ const allowedQueryKeys = [ resetFunnel, resetFunnelFilters, setFunnelPage, - editSearch, + clearSearch, }) @withPageTitle("Sessions - OpenReplay") export default class BugFinder extends React.PureComponent { @@ -181,7 +181,7 @@ export default class BugFinder extends React.PureComponent {
-
diff --git a/frontend/app/components/shared/SaveFilterButton/SaveFilterButton.tsx b/frontend/app/components/shared/SaveFilterButton/SaveFilterButton.tsx index 9b5230f24..625b05047 100644 --- a/frontend/app/components/shared/SaveFilterButton/SaveFilterButton.tsx +++ b/frontend/app/components/shared/SaveFilterButton/SaveFilterButton.tsx @@ -6,14 +6,20 @@ import SaveSearchModal from 'Shared/SaveSearchModal' interface Props { filter: any; + savedSearch: any; } function SaveFilterButton(props: Props) { + const { savedSearch } = props; const [showModal, setshowModal] = useState(false) return (
- {/* */} - setshowModal(true)} primaryText label="SAVE SEARCH" icon="zoom-in" /> + { savedSearch ? ( + setshowModal(true)} primaryText label="UPDATE SEARCH" icon="zoom-in" /> + ) : ( + setshowModal(true)} primaryText label="SAVE SEARCH" icon="zoom-in" /> + )} + setshowModal(false)} @@ -23,5 +29,6 @@ function SaveFilterButton(props: Props) { } export default connect(state => ({ - filter: state.getIn([ 'filters', 'appliedFilter' ]), + filter: state.getIn([ 'search', 'instance' ]), + savedSearch: state.getIn([ 'search', 'savedSearch' ]), }), { save })(SaveFilterButton); \ No newline at end of file diff --git a/frontend/app/components/shared/SaveSearchModal/SaveSearchModal.tsx b/frontend/app/components/shared/SaveSearchModal/SaveSearchModal.tsx index 6ce0f52bc..59155acda 100644 --- a/frontend/app/components/shared/SaveSearchModal/SaveSearchModal.tsx +++ b/frontend/app/components/shared/SaveSearchModal/SaveSearchModal.tsx @@ -1,34 +1,50 @@ -import React from 'react'; +import React, { useState } from 'react'; import { connect } from 'react-redux'; -import { edit, save } from 'Duck/search'; +import { edit, save, remove } from 'Duck/search'; import { Button, Modal, Form, Icon, Checkbox } from 'UI'; +import { confirm } from 'UI/Confirmation'; import stl from './SaveSearchModal.css'; interface Props { filter: any; loading: boolean; edit: (filter: any) => void; - save: (filter: any) => Promise; + save: (searchId, name, filter: any) => Promise; show: boolean; closeHandler: () => void; + savedSearch: any; + remove: (filterId: number) => Promise; } function SaveSearchModal(props: Props) { - const { filter, loading, show, closeHandler } = props; + const [name, setName] = useState(props.savedSearch ? props.savedSearch.name : ''); + const { savedSearch, filter, loading, show, closeHandler } = props; const onNameChange = ({ target: { value } }) => { - props.edit({ name: value }); + // props.edit({ name: value }); + setName(value); }; const onSave = () => { const { filter, closeHandler } = props; - if (filter.name.trim() === '') return; - props.save(filter).then(function() { + if (name.trim() === '') return; + props.save(savedSearch ? savedSearch.searchId : null, name, filter).then(function() { // this.props.fetchFunnelsList(); closeHandler(); }); } - console.log('filter', filter) + const onDelete = async () => { + if (await confirm({ + header: 'Confirm', + confirmButton: 'Yes, Delete', + confirmation: `Are you sure you want to permanently delete this alert?` + })) { + props.remove(savedSearch.searchId).then(() => { + closeHandler(); + }); + } + } + return ( @@ -52,29 +68,33 @@ function SaveSearchModal(props: Props) { autoFocus={ true } // className={ stl.name } name="name" - value={ filter.name } + value={ name } onChange={ onNameChange } placeholder="Title" /> - - - + +
+ + +
+ { savedSearch && }
); } export default connect(state => ({ + savedSearch: state.getIn([ 'search', 'savedSearch' ]), filter: state.getIn(['search', 'instance']), loading: state.getIn([ 'search', 'saveRequest', 'loading' ]) || state.getIn([ 'search', 'updateRequest', 'loading' ]), -}), { edit, save })(SaveSearchModal); \ No newline at end of file +}), { edit, save, remove })(SaveSearchModal); \ No newline at end of file diff --git a/frontend/app/components/shared/SavedSearch/SavedSearch.css b/frontend/app/components/shared/SavedSearch/SavedSearch.css new file mode 100644 index 000000000..995afd92f --- /dev/null +++ b/frontend/app/components/shared/SavedSearch/SavedSearch.css @@ -0,0 +1,4 @@ +.disabled { + opacity: 0.5 !important; + pointer-events: none; +} \ No newline at end of file diff --git a/frontend/app/components/shared/SavedSearch/SavedSearch.tsx b/frontend/app/components/shared/SavedSearch/SavedSearch.tsx index cd08d2137..e704570b0 100644 --- a/frontend/app/components/shared/SavedSearch/SavedSearch.tsx +++ b/frontend/app/components/shared/SavedSearch/SavedSearch.tsx @@ -2,14 +2,20 @@ import React, { useState, useEffect } from 'react'; import { Button, Icon } from 'UI'; import SavedSearchDropdown from './components/SavedSearchDropdown'; import { connect } from 'react-redux'; -import { fetchList as fetchListSavedSearch } from 'Duck/filters'; +import { fetchList as fetchListSavedSearch } from 'Duck/search'; import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; +import cn from 'classnames'; +import { list } from 'App/components/BugFinder/CustomFilters/filterModal.css'; +import stl from './SavedSearch.css'; interface Props { fetchListSavedSearch: () => void; list: any; + savedSearch: any; } function SavedSearch(props) { + const { list } = props; + const { savedSearch } = props; const [showMenu, setShowMenu] = useState(false) useEffect(() => { @@ -22,7 +28,7 @@ function SavedSearch(props) { onClickOutside={() => setShowMenu(false)} >
-
+
- -
- - Viewing: - Login ... -
+ { savedSearch && ( +
+ + Viewing: + {savedSearch.name} +
+ )}
{ showMenu && ( @@ -52,5 +59,6 @@ function SavedSearch(props) { } export default connect(state => ({ - list: state.getIn([ 'filters', 'list' ]), + list: state.getIn([ 'search', 'list' ]), + savedSearch: state.getIn([ 'search', 'savedSearch' ]) }), { fetchListSavedSearch })(SavedSearch); \ No newline at end of file diff --git a/frontend/app/components/shared/SavedSearch/components/SavedSearchDropdown/SavedSearchDropdown.css b/frontend/app/components/shared/SavedSearch/components/SavedSearchDropdown/SavedSearchDropdown.css index 96609ccae..d4451d0bf 100644 --- a/frontend/app/components/shared/SavedSearch/components/SavedSearchDropdown/SavedSearchDropdown.css +++ b/frontend/app/components/shared/SavedSearch/components/SavedSearchDropdown/SavedSearchDropdown.css @@ -4,4 +4,12 @@ z-index: 999; display: flex; flex-direction: column; + max-height: 250px; + overflow-y: auto; +} + +.rowItem { + &:hover { + color: $teal; + } } \ No newline at end of file diff --git a/frontend/app/components/shared/SavedSearch/components/SavedSearchDropdown/SavedSearchDropdown.tsx b/frontend/app/components/shared/SavedSearch/components/SavedSearchDropdown/SavedSearchDropdown.tsx index b25bbb8e8..9b28451c7 100644 --- a/frontend/app/components/shared/SavedSearch/components/SavedSearchDropdown/SavedSearchDropdown.tsx +++ b/frontend/app/components/shared/SavedSearch/components/SavedSearchDropdown/SavedSearchDropdown.tsx @@ -1,27 +1,29 @@ import React from 'react'; import stl from './SavedSearchDropdown.css'; +import cn from 'classnames'; import { Icon } from 'UI'; -import { applyFilter, remove } from 'Duck/search' +import { applySavedSearch, remove, edit } from 'Duck/search' import { connect } from 'react-redux'; import { confirm } from 'UI/Confirmation'; interface Props { list: Array - applyFilter: (filter: any) => void + applySavedSearch: (filter: any) => void remove: (id: string) => Promise - onClose: () => void + onClose: () => void, + edit: (filter: any) => void, } function Row ({ name, onClick, onClickEdit, onDelete }) { return (
{name}
-
+ {/*
*/}
) @@ -29,7 +31,8 @@ function Row ({ name, onClick, onClickEdit, onDelete }) { function SavedSearchDropdown(props: Props) { const onClick = (item) => { - props.applyFilter(item.filter) + props.applySavedSearch(item) + props.edit(item.filter) props.onClose() } @@ -64,4 +67,4 @@ function SavedSearchDropdown(props: Props) { ); } -export default connect(null, { applyFilter, remove })(SavedSearchDropdown); \ No newline at end of file +export default connect(null, { applySavedSearch, remove, edit })(SavedSearchDropdown); \ No newline at end of file diff --git a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx index 8a3aaa599..9b9dafd51 100644 --- a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx +++ b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx @@ -84,7 +84,6 @@ function SessionSearch(props) {
- {/* */}
diff --git a/frontend/app/duck/search.js b/frontend/app/duck/search.js index 51de61d7c..39925d774 100644 --- a/frontend/app/duck/search.js +++ b/frontend/app/duck/search.js @@ -14,7 +14,7 @@ import { fetchList as fetchErrorsList } from './errors'; const ERRORS_ROUTE = errorsRoute(); const name = "search"; -const idKey = "metricId"; +const idKey = "searchId"; const FETCH_LIST = fetchListType(name); const FETCH_FILTER_SEARCH = fetchListType(`${name}/FILTER_SEARCH`); @@ -22,6 +22,8 @@ const FETCH = fetchType(name); const SAVE = saveType(name); const EDIT = editType(name); const REMOVE = removeType(name); +const APPLY_SAVED_SEARCH = `${name}/APPLY_SAVED_SEARCH`; +const CLEAR_SEARCH = `${name}/CLEAR_SEARCH`; const UPDATE = `${name}/UPDATE`; const APPLY = `${name}/APPLY`; const SET_ALERT_METRIC_ID = `${name}/SET_ALERT_METRIC_ID`; @@ -30,16 +32,17 @@ function chartWrapper(chart = []) { return chart.map(point => ({ ...point, count: Math.max(point.count, 0) })); } -// const updateItemInList = createListUpdater(idKey); -// const updateInstance = (state, instance) => state.getIn([ "instance", idKey ]) === instance[ idKey ] -// ? state.mergeIn([ "instance" ], instance) -// : state; +const savedSearchIdKey = 'searchId' +const updateItemInList = createListUpdater(savedSearchIdKey); +const updateInstance = (state, instance) => state.getIn([ "savedSearch", savedSearchIdKey ]) === instance[savedSearchIdKey] + ? state.mergeIn([ "savedSearch" ], instance) + : state; const initialState = Map({ list: List(), alertMetricId: null, instance: new Filter({ filters: [] }), - savedFilter: new SavedFilter({ filters: [] }), + savedSearch: null, filterSearchList: List(), }); @@ -56,16 +59,19 @@ function reducer(state = initialState, action = {}) { ) : state.mergeIn(['instance'], action.filter); case success(SAVE): - return state.mergeIn([ 'instance' ], action.data); + return updateItemInList(updateInstance(state, action.data), action.data); + // return state.mergeIn([ 'instance' ], action.data); case success(REMOVE): - return state.update('list', list => list.filter(item => item.metricId !== action.id)); + return state.update('list', list => list.filter(item => item.searchId !== action.id)); case success(FETCH): return state.set("instance", ErrorInfo(action.data)); case success(FETCH_LIST): const { data } = action; - return state.set("list", List(data.map(NewFilter))); + return state.set("list", List(data.map(SavedFilter))); case success(FETCH_FILTER_SEARCH): return state.set("filterSearchList", action.data.map(NewFilter)); + case APPLY_SAVED_SEARCH: + return state.set('savedSearch', action.filter); } return state; } @@ -106,7 +112,7 @@ export const edit = reduceThenFetchResource((instance) => ({ instance, })); -export const remove = createRemove(name); +export const remove = createRemove(name, (id) => `/saved_search/${id}`); export const applyFilter = reduceThenFetchResource((filter, fromUrl=false) => ({ type: APPLY, @@ -114,6 +120,16 @@ export const applyFilter = reduceThenFetchResource((filter, fromUrl=false) => ({ fromUrl, })); +export const applySavedSearch = (filter) => (dispatch, getState) => { + // console.log('applySavedSearch', filter); +// export const applySavedSearch = (filter) => ({ + dispatch(edit(filter ? filter.filter : new Filter({ fitlers: []}))); + return dispatch({ + type: APPLY_SAVED_SEARCH, + filter, + }) +}; + export const updateSeries = (index, series) => ({ type: UPDATE, index, @@ -128,11 +144,12 @@ export function fetch(id) { } } -export function save(instance) { +export function save(id, name, instance) { + instance = instance instanceof SavedFilter ? instance : new SavedFilter(instance); return { types: SAVE.array, - call: client => client.post('/saved_search', { - name: instance.name, + call: client => client.post(!id ? '/saved_search' : `/saved_search/${id}`, { + name: name, filter: instance.toSaveData(), }), instance, @@ -159,4 +176,12 @@ export function fetchFilterSearch(params) { call: client => client.get('/events/search', params), params, }; +} + +export const clearSearch = () => (dispatch, getState) => { + dispatch(applySavedSearch(null)); + dispatch(edit(new Filter({ filters: [] }))); + return dispatch({ + type: CLEAR_SEARCH, + }); } \ No newline at end of file diff --git a/frontend/app/types/filter/savedFilter.js b/frontend/app/types/filter/savedFilter.js index 00f7db7fe..83518f20d 100644 --- a/frontend/app/types/filter/savedFilter.js +++ b/frontend/app/types/filter/savedFilter.js @@ -3,16 +3,16 @@ import Filter from './filter'; import { List } from 'immutable'; export default Record({ - filterId: undefined, + searchId: undefined, projectId: undefined, userId: undefined, - name: undefined, + name: '', filter: Filter(), createdAt: undefined, count: 0, watchdogs: List() }, { - idKey: 'filterId', + idKey: 'searchId', methods: { toData() { const js = this.toJS();