diff --git a/frontend/app/components/BugFinder/CustomFilters/FilterModal.js b/frontend/app/components/BugFinder/CustomFilters/FilterModal.js
index 5c0cfbf21..c5ab474be 100644
--- a/frontend/app/components/BugFinder/CustomFilters/FilterModal.js
+++ b/frontend/app/components/BugFinder/CustomFilters/FilterModal.js
@@ -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);
}
diff --git a/frontend/app/components/BugFinder/SessionList/SessionList.js b/frontend/app/components/BugFinder/SessionList/SessionList.js
index 0aa4b9afb..fa0d282be 100644
--- a/frontend/app/components/BugFinder/SessionList/SessionList.js
+++ b/frontend/app/components/BugFinder/SessionList/SessionList.js
@@ -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);
diff --git a/frontend/app/components/Funnels/FunnelSaveModal/FunnelSaveModal.js b/frontend/app/components/Funnels/FunnelSaveModal/FunnelSaveModal.js
index 2662c5a04..36e2bc35b 100644
--- a/frontend/app/components/Funnels/FunnelSaveModal/FunnelSaveModal.js
+++ b/frontend/app/components/Funnels/FunnelSaveModal/FunnelSaveModal.js
@@ -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
diff --git a/frontend/app/components/shared/EventFilter/FilterModal/FilterModal.js b/frontend/app/components/shared/EventFilter/FilterModal/FilterModal.js
index f76c28065..cbe8c9546 100644
--- a/frontend/app/components/shared/EventFilter/FilterModal/FilterModal.js
+++ b/frontend/app/components/shared/EventFilter/FilterModal/FilterModal.js
@@ -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);
}
diff --git a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx
index 0cd9f0513..16be3add8 100644
--- a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx
+++ b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx
@@ -101,7 +101,7 @@ function FilterAutoComplete(props: Props) {
setTimeout(() => { setShowModal(false) }, 150) }
+ onBlur={ () => setTimeout(() => { setShowModal(false) }, 200) }
onFocus={ () => setShowModal(true)}
value={ query }
autoFocus={ true }
diff --git a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx
index eb10c5553..a5300dc44 100644
--- a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx
+++ b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx
@@ -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}
/>
-
+ { !(filter.operator === "isAny" || filter.operator === "onAny") && (
+
+ )}
+
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);
\ No newline at end of file
diff --git a/frontend/app/components/shared/Filters/FilterSource/FilterSource.tsx b/frontend/app/components/shared/Filters/FilterSource/FilterSource.tsx
index cc622b82a..79e4d4aa1 100644
--- a/frontend/app/components/shared/Filters/FilterSource/FilterSource.tsx
+++ b/frontend/app/components/shared/Filters/FilterSource/FilterSource.tsx
@@ -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"
/>
)
diff --git a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx
index 6bda44668..8be5d257a 100644
--- a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx
+++ b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx
@@ -58,6 +58,7 @@ function FilterValue(props: Props) {
case FilterType.DROPDOWN:
return (
+ setshowModal(true)} primaryText label="SAVE FUNNEL" icon="zoom-in"
+ />
+
+ setshowModal(false)}
+ />
+
+ )
+}
diff --git a/frontend/app/components/shared/SaveFunnelButton/index.ts b/frontend/app/components/shared/SaveFunnelButton/index.ts
new file mode 100644
index 000000000..246df92ff
--- /dev/null
+++ b/frontend/app/components/shared/SaveFunnelButton/index.ts
@@ -0,0 +1 @@
+export { default } from './SaveFunnelButton';
\ 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 9b28451c7..58869a115 100644
--- a/frontend/app/components/shared/SavedSearch/components/SavedSearchDropdown/SavedSearchDropdown.tsx
+++ b/frontend/app/components/shared/SavedSearch/components/SavedSearchDropdown/SavedSearchDropdown.tsx
@@ -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()
}
diff --git a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx
index 9b9dafd51..ebe4926a7 100644
--- a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx
+++ b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx
@@ -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) {
-
+
+ {/* */}
diff --git a/frontend/app/constants/filterOptions.js b/frontend/app/constants/filterOptions.js
index 51fcca4a5..b88e9b808 100644
--- a/frontend/app/constants/filterOptions.js
+++ b/frontend/app/constants/filterOptions.js
@@ -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'];
diff --git a/frontend/app/duck/funnels.js b/frontend/app/duck/funnels.js
index 7a611dbca..cad648839 100644
--- a/frontend/app/duck/funnels.js
+++ b/frontend/app/duck/funnels.js
@@ -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()),
}
}
diff --git a/frontend/app/duck/search.js b/frontend/app/duck/search.js
index d2924f12d..42f9ca327 100644
--- a/frontend/app/duck/search.js
+++ b/frontend/app/duck/search.js
@@ -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,
diff --git a/frontend/app/styles/semantic.css b/frontend/app/styles/semantic.css
index cf6034861..06930c3e9 100644
--- a/frontend/app/styles/semantic.css
+++ b/frontend/app/styles/semantic.css
@@ -313,4 +313,8 @@ a:hover {
.ui.toggle.checkbox {
min-height: 20px !important;
+}
+
+.ui.search.dropdown>input.search {
+ bottom: 0 !important;
}
\ No newline at end of file
diff --git a/frontend/app/types/filter/newFilter.js b/frontend/app/types/filter/newFilter.js
index 3b24acc2d..b682b76d4 100644
--- a/frontend/app/types/filter/newFilter.js
+++ b/frontend/app/types/filter/newFilter.js
@@ -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 },
}