feat(ui) - filters - review fixes and other changes
This commit is contained in:
parent
3a90bddaab
commit
6ce4b4e4a7
18 changed files with 94 additions and 43 deletions
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
|
|
@ -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"
|
||||
/>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ function FilterValue(props: Props) {
|
|||
case FilterType.DROPDOWN:
|
||||
return (
|
||||
<FilterValueDropdown
|
||||
search={true}
|
||||
value={value}
|
||||
filter={filter}
|
||||
options={filter.options}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
1
frontend/app/components/shared/SaveFunnelButton/index.ts
Normal file
1
frontend/app/components/shared/SaveFunnelButton/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from './SaveFunnelButton';
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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'];
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -313,4 +313,8 @@ a:hover {
|
|||
|
||||
.ui.toggle.checkbox {
|
||||
min-height: 20px !important;
|
||||
}
|
||||
|
||||
.ui.search.dropdown>input.search {
|
||||
bottom: 0 !important;
|
||||
}
|
||||
|
|
@ -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 },
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue