feat(ui) - filters - review fixes and other changes

This commit is contained in:
Shekar Siri 2022-02-03 16:13:12 +01:00
parent 3a90bddaab
commit 6ce4b4e4a7
18 changed files with 94 additions and 43 deletions

View file

@ -68,7 +68,7 @@ export default class FilterModal extends React.PureComponent {
this.props.addAttribute(filter, _in >= 0 ? _in : _index);
} else {
logger.log('Adding Event', filter)
const _index = filterType === 'event' ? index : undefined; // should add new one if coming from fitlers
const _index = filterType === 'event' ? index : undefined; // should add new one if coming from filters
this.props.addEvent(filter, false, _index);
}

View file

@ -1,6 +1,7 @@
import { connect } from 'react-redux';
import { Loader, NoContent, Message, Icon, Button, LoadMoreButton } from 'UI';
import { applyFilter, addAttribute, addEvent } from 'Duck/filters';
import { fetchSessions } from 'Duck/search';
import SessionItem from 'Shared/SessionItem';
import SessionListHeader from './SessionListHeader';
import { KEYS } from 'Types/filter/customFilter';
@ -21,7 +22,8 @@ var timeoutId;
}), {
applyFilter,
addAttribute,
addEvent
addEvent,
fetchSessions,
})
export default class SessionList extends React.PureComponent {
state = {
@ -53,7 +55,8 @@ export default class SessionList extends React.PureComponent {
timeout = () => {
timeoutId = setTimeout(function () {
if (this.props.shouldAutorefresh) {
this.props.applyFilter();
// this.props.applyFilter();
this.props.fetchSessions();
}
this.timeout();
}.bind(this), AUTOREFRESH_INTERVAL);

View file

@ -4,6 +4,7 @@ import styles from './funnelSaveModal.css';
import { edit, save, fetchList as fetchFunnelsList } from 'Duck/funnels';
@connect(state => ({
filter: state.getIn(['search', 'instance']),
funnel: state.getIn(['funnels', 'instance']),
loading: state.getIn([ 'funnels', 'saveRequest', 'loading' ]) ||
state.getIn([ 'funnels', 'updateRequest', 'loading' ]),
@ -29,7 +30,7 @@ export default class FunnelSaveModal extends React.PureComponent {
onSave = () => {
const { funnel, closeHandler } = this.props;
if (funnel.name.trim() === '') return;
this.props.save(funnel).then(function() {
this.props.save({ ...funnel, filter: filter }).then(function() {
this.props.fetchFunnelsList();
this.props.closeHandler();
}.bind(this));
@ -38,7 +39,6 @@ export default class FunnelSaveModal extends React.PureComponent {
render() {
const {
show,
appliedFilter,
closeHandler,
loading,
funnel

View file

@ -67,7 +67,7 @@ export default class FilterModal extends React.PureComponent {
this.props.addAttribute(filter, _in >= 0 ? _in : _index);
} else {
logger.log('Adding Event', filter)
const _index = filterType === 'event' ? index : undefined; // should add new one if coming from fitlers
const _index = filterType === 'event' ? index : undefined; // should add new one if coming from filters
this.props.addEvent(filter, false, _index);
}

View file

@ -101,7 +101,7 @@ function FilterAutoComplete(props: Props) {
<input
name="query"
onChange={ onInputChange }
onBlur={ () => setTimeout(() => { setShowModal(false) }, 150) }
onBlur={ () => setTimeout(() => { setShowModal(false) }, 200) }
onFocus={ () => setShowModal(true)}
value={ query }
autoFocus={ true }

View file

@ -12,7 +12,7 @@ interface Props {
onRemoveFilter: () => void;
isFilter?: boolean;
}
function FitlerItem(props: Props) {
function FilterItem(props: Props) {
const { isFilter = false, filterIndex, filter } = props;
const replaceFilter = (filter) => {
@ -53,7 +53,10 @@ function FitlerItem(props: Props) {
className="mx-2 flex-shrink-0"
value={filter.operator}
/>
<FilterValue filter={filter} onUpdate={props.onUpdate} />
{ !(filter.operator === "isAny" || filter.operator === "onAny") && (
<FilterValue filter={filter} onUpdate={props.onUpdate} />
)}
</div>
<div className="flex flex-shrink-0 self-start mt-1 ml-auto px-2">
<div
@ -67,4 +70,4 @@ function FitlerItem(props: Props) {
);
}
export default FitlerItem;
export default FilterItem;

View file

@ -3,16 +3,33 @@ import { Icon } from 'UI';
import { connect } from 'react-redux';
import cn from 'classnames';
import stl from './FilterModal.css';
import { filtersMap } from 'Types/filter/newFilter'
import { filtersMap } from 'Types/filter/newFilter';
import { FilterKey, FilterType } from 'Types/filter/filterType';
interface Props {
filters: any,
onFilterClick?: (filter) => void,
filterSearchList: any,
metaOptions: any,
}
function FilterModal(props: Props) {
const { filters, onFilterClick = () => null, filterSearchList } = props;
const { filters, metaOptions, onFilterClick = () => null, filterSearchList } = props;
const hasFilerSearchList = filterSearchList && Object.keys(filterSearchList).length > 0;
const allFilters = Object.assign({}, filtersMap);
if (metaOptions.size > 0) {
metaOptions.forEach((option) => {
if (option.key) {
allFilters[option.key] = {
category: FilterKey.METADATA,
key: option.key,
name: option.key,
label: option.key,
};
}
});
}
console.log('allFilters', allFilters);
const onFilterSearchClick = (filter) => {
const _filter = filtersMap[filter.type];
@ -71,5 +88,6 @@ function FilterModal(props: Props) {
export default connect(state => ({
filters: state.getIn([ 'filters', 'filterList' ]),
filterSearchList: state.getIn([ 'search', 'filterSearchList' ])
filterSearchList: state.getIn([ 'search', 'filterSearchList' ]),
metaOptions: state.getIn([ 'customFields', 'list' ]),
}))(FilterModal);

View file

@ -9,8 +9,6 @@ interface Props {
function FilterSource(props: Props) {
const { filter } = props;
console.log('FilterSource', filter.source);
const onChange = ({ target: { value, name } }) => {
props.onUpdate({ ...filter, [name]: [value] })
}
@ -24,6 +22,7 @@ function FilterSource(props: Props) {
className={stl.inputField}
value={filter.source[0]}
onBlur={onChange}
onChange={onChange}
type="number"
/>
)

View file

@ -58,6 +58,7 @@ function FilterValue(props: Props) {
case FilterType.DROPDOWN:
return (
<FilterValueDropdown
search={true}
value={value}
filter={filter}
options={filter.options}

View file

@ -0,0 +1,20 @@
import React, { useState } from 'react';
import { IconButton } from 'UI';
import FunnelSaveModal from 'Components/Funnels/FunnelSaveModal';
export default function SaveFunnelButton() {
const [showModal, setshowModal] = useState(false)
return (
<div>
<IconButton
className="mr-2"
onClick={() => setshowModal(true)} primaryText label="SAVE FUNNEL" icon="zoom-in"
/>
<FunnelSaveModal
show={showModal}
closeHandler={() => setshowModal(false)}
/>
</div>
)
}

View file

@ -0,0 +1 @@
export { default } from './SaveFunnelButton';

View file

@ -32,7 +32,7 @@ function Row ({ name, onClick, onClickEdit, onDelete }) {
function SavedSearchDropdown(props: Props) {
const onClick = (item) => {
props.applySavedSearch(item)
props.edit(item.filter)
// props.edit(item.filter)
props.onClose()
}

View file

@ -6,6 +6,7 @@ import SaveFilterButton from 'Shared/SaveFilterButton';
import { connect } from 'react-redux';
import { IconButton, Button } from 'UI';
import { edit } from 'Duck/search';
import SaveFunnelButton from '../SaveFunnelButton';
interface Props {
appliedFilter: any;
@ -84,7 +85,8 @@ function SessionSearch(props) {
</div>
<div className="ml-auto flex items-center">
<SaveFilterButton />
<IconButton primaryText label="SAVE FUNNEL" icon="filter" />
<SaveFunnelButton />
{/* <IconButton primaryText label="SAVE FUNNEL" icon="filter" /> */}
</div>
</div>
</div>

View file

@ -3,6 +3,11 @@ export const options = [
key: 'is',
text: 'is',
value: 'is'
},
{
key: 'isAny',
text: 'is any',
value: 'isAny'
}, {
key: 'isNot',
text: 'is not',
@ -112,7 +117,7 @@ export const options = [
];
const filterKeys = ['is', 'isNot'];
const stringFilterKeys = ['is', 'isNot', 'contains', 'startsWith', 'endsWith', 'notContains'];
const stringFilterKeys = ['is', 'isAny', 'isNot', 'contains', 'startsWith', 'endsWith', 'notContains'];
const targetFilterKeys = ['on', 'notOn', 'onAny'];
const signUpStatusFilterKeys = ['isSignedUp', 'notSignedUp'];
const rangeFilterKeys = ['before', 'after', 'on', 'inRange', 'notInRange', 'withInLast', 'notWithInLast'];

View file

@ -266,13 +266,14 @@ export const fetchIssueTypes = () => {
}
export const save = (instance) => {
const url = instance.exists()
? `/funnels/${ instance[idKey] }`
const _instance = instance instanceof Funnel ? instance : Funnel(instance);
const url = _instance.exists()
? `/funnels/${ _instance[idKey] }`
: `/funnels`;
return {
types: array(instance.exists() ? SAVE : UPDATE),
call: client => client.post(url, instance.toData()),
types: array(_instance.exists() ? SAVE : UPDATE),
call: client => client.post(url, _instance.toData()),
}
}

View file

@ -1,11 +1,8 @@
import { List, Map } from 'immutable';
import ErrorInfo, { RESOLVED, UNRESOLVED, IGNORED } from 'Types/errorInfo';
import CustomMetric, { FilterSeries } from 'Types/customMetric'
import { createFetch, fetchListType, fetchType, saveType, removeType, editType, createRemove, createEdit } from './funcTools/crud';
import { fetchListType, fetchType, saveType, removeType, editType, createRemove } from './funcTools/crud';
import { createRequestReducer, ROOT_KEY } from './funcTools/request';
import { array, request, success, failure, createListUpdater, mergeReducers } from './funcTools/tools';
import Filter from 'Types/filter';
import NewFilter from 'Types/filter/newFilter';
import SavedFilter from 'Types/filter/savedFilter';
import { errors as errorsRoute, isRoute } from "App/routes";
import { fetchList as fetchSessionList } from './sessions';
@ -53,18 +50,14 @@ function reducer(state = initialState, action = {}) {
return state.mergeIn(['instance'], action.instance);
case APPLY:
return action.fromUrl
? state.set('instance',
Filter(action.filter)
// .set('events', state.getIn([ 'instance', 'events' ]))
)
? state.set('instance', Filter(action.filter))
: state.mergeIn(['instance'], action.filter);
case success(SAVE):
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.searchId !== action.id));
case success(FETCH):
return state.set("instance", ErrorInfo(action.data));
return state.set("instance", action.data);
case success(FETCH_LIST):
const { data } = action;
return state.set("list", List(data.map(SavedFilter)));
@ -106,7 +99,6 @@ const filterMap = ({value, key, operator, sourceOperator, source, custom, isEven
});
const reduceThenFetchResource = actionCreator => (...args) => (dispatch, getState) => {
console.log('reduceThenFetchResource', args);
dispatch(actionCreator(...args));
const filter = getState().getIn([ 'search', 'instance']).toData();
filter.filters = filter.filters.map(filterMap);
@ -131,15 +123,18 @@ export const applyFilter = reduceThenFetchResource((filter, fromUrl=false) => ({
}));
export const applySavedSearch = (filter) => (dispatch, getState) => {
// console.log('applySavedSearch', filter);
// export const applySavedSearch = (filter) => ({
dispatch(edit(filter ? filter.filter : new Filter({ fitlers: []})));
dispatch(edit(filter ? filter.filter : new Filter({ filters: []})));
return dispatch({
type: APPLY_SAVED_SEARCH,
filter,
})
};
export const fetchSessions = (filter) => (dispatch, getState) => {
const _filter = filter ? filter : getState().getIn([ 'search', 'instance']);
return dispatch(applyFilter(_filter));
};
export const updateSeries = (index, series) => ({
type: UPDATE,
index,

View file

@ -313,4 +313,8 @@ a:hover {
.ui.toggle.checkbox {
min-height: 20px !important;
}
.ui.search.dropdown>input.search {
bottom: 0 !important;
}

View file

@ -46,13 +46,12 @@ export const filtersMap = {
[FilterKey.USERANONYMOUSID]: { key: FilterKey.USERANONYMOUSID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'UserAnonymousId', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid' },
// PERFORMANCE
[FilterKey.DOM_COMPLETE]: { key: FilterKey.DOM_COMPLETE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'DOM Complete', operator: 'is', operatorOptions: filterOptions.stringOperators, sourceOperatorOptions: filterOptions.customOperators, source: [], icon: 'filters/dom-complete', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER },
[FilterKey.LARGEST_CONTENTFUL_PAINT_TIME]: { key: FilterKey.LARGEST_CONTENTFUL_PAINT_TIME, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Largest Contentful Paint Time', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/lcpt', isEvent: true },
// [FilterKey.TIME_BETWEEN_EVENTS]: { key: FilterKey.TIME_BETWEEN_EVENTS, type: FilterType.NUMBER, category: FilterCategory.PERFORMANCE, label: 'Time Between Events', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/click' },
[FilterKey.TTFB]: { key: FilterKey.TTFB, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Time to First Byte', operator: 'is', operatorOptions: filterOptions.stringOperators, sourceOperatorOptions: filterOptions.customOperators, source: [], icon: 'filters/ttfb', isEvent: true },
[FilterKey.AVG_CPU_LOAD]: { key: FilterKey.AVG_CPU_LOAD, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg CPU Load', operator: 'is', operatorOptions: filterOptions.stringOperators, sourceOperatorOptions: filterOptions.customOperators, source: [], icon: 'filters/cpu-load', isEvent: true },
[FilterKey.AVG_MEMORY_USAGE]: { key: FilterKey.AVG_MEMORY_USAGE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg Memory Usage', operator: 'is', operatorOptions: filterOptions.stringOperators, sourceOperatorOptions: filterOptions.customOperators, source: [], icon: 'filters/memory-load', isEvent: true },
[FilterKey.FETCH_FAILED]: { key: FilterKey.AVG_MEMORY_USAGE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Fetch Failed', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch-failed', isEvent: true },
[FilterKey.DOM_COMPLETE]: { key: FilterKey.DOM_COMPLETE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'DOM Complete', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/dom-complete', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
[FilterKey.LARGEST_CONTENTFUL_PAINT_TIME]: { key: FilterKey.LARGEST_CONTENTFUL_PAINT_TIME, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Largest Contentful Paint', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/lcpt', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
[FilterKey.TTFB]: { key: FilterKey.TTFB, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Time to First Byte', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/ttfb', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
[FilterKey.AVG_CPU_LOAD]: { key: FilterKey.AVG_CPU_LOAD, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg CPU Load', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/cpu-load', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
[FilterKey.AVG_MEMORY_USAGE]: { key: FilterKey.AVG_MEMORY_USAGE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg Memory Usage', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/memory-load', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
[FilterKey.FETCH_FAILED]: { key: FilterKey.FETCH_FAILED, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Fetch Failed', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch-failed', isEvent: true },
[FilterKey.ISSUE]: { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', operator: 'is', operatorOptions: filterOptions.baseOperators, icon: 'filters/click', options: ISSUE_OPTIONS },
}