diff --git a/frontend/app/components/Funnels/FunnelSaveModal/FunnelSaveModal.js b/frontend/app/components/Funnels/FunnelSaveModal/FunnelSaveModal.js
index 067be7c52..53fec2048 100644
--- a/frontend/app/components/Funnels/FunnelSaveModal/FunnelSaveModal.js
+++ b/frontend/app/components/Funnels/FunnelSaveModal/FunnelSaveModal.js
@@ -28,9 +28,9 @@ export default class FunnelSaveModal extends React.PureComponent {
onChangeOption = (e, { checked, name }) => this.props.edit({ [ name ]: checked })
onSave = () => {
- const { funnel, closeHandler } = this.props;
+ const { funnel, filter } = this.props;
if (funnel.name.trim() === '') return;
- this.props.save({ ...funnel, filter: filter }).then(function() {
+ this.props.save(funnel).then(function() {
this.props.fetchFunnelsList();
this.props.closeHandler();
}.bind(this));
diff --git a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx
index 3f7d432b0..de15e33a9 100644
--- a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx
+++ b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx
@@ -45,8 +45,7 @@ function FilterAutoComplete(props: Props) {
const [query, setQuery] = useState(value);
- const requestValues = (q) => {
- // const { params, method } = props;
+ const requestValues = (q) => {
setLoading(true);
return new APIClient()[method?.toLowerCase()](endpoint, { ...params, q })
@@ -55,13 +54,7 @@ function FilterAutoComplete(props: Props) {
if (errors) {
// this.setError();
} else {
- setOptions(data);
- // this.setState({
- // ddOpen: true,
- // values: data,
- // loading: false,
- // noResultsMessage: NO_RESULTS_MSG,
- // });
+ setOptions(data);
}
}).finally(() => setLoading(false));
// .catch(this.setError);
@@ -81,6 +74,17 @@ function FilterAutoComplete(props: Props) {
debouncedRequestValues(query)
}, [query])
+ useEffect(() => {
+ if(value === '') {
+ setQuery(value);
+ }
+ }, [value])
+
+ const onBlur = (e) => {
+ setTimeout(() => { setShowModal(false) }, 200)
+ props.onSelect(e, { value: query })
+ }
+
const onItemClick = (e, item) => {
e.stopPropagation();
e.preventDefault();
@@ -103,7 +107,7 @@ function FilterAutoComplete(props: Props) {
setTimeout(() => { setShowModal(false) }, 200) }
+ onBlur={ onBlur }
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 a5300dc44..a8760428b 100644
--- a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx
+++ b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx
@@ -14,6 +14,7 @@ interface Props {
}
function FilterItem(props: Props) {
const { isFilter = false, filterIndex, filter } = props;
+ const canShowValues = !(filter.operator === "isAny" || filter.operator === "onAny");
const replaceFilter = (filter) => {
props.onUpdate({ ...filter, value: [""]});
@@ -53,9 +54,7 @@ function FilterItem(props: Props) {
className="mx-2 flex-shrink-0"
value={filter.operator}
/>
- { !(filter.operator === "isAny" || filter.operator === "onAny") && (
-
- )}
+ { canShowValues && () }
diff --git a/frontend/app/components/shared/Filters/FilterSource/FilterSource.tsx b/frontend/app/components/shared/Filters/FilterSource/FilterSource.tsx
index 79e4d4aa1..180f0f2a4 100644
--- a/frontend/app/components/shared/Filters/FilterSource/FilterSource.tsx
+++ b/frontend/app/components/shared/Filters/FilterSource/FilterSource.tsx
@@ -1,5 +1,5 @@
import { FilterType } from 'App/types/filter/filterType';
-import React from 'react';
+import React, { useState, useEffect } from 'react';
import stl from './FilterSource.css';
interface Props {
@@ -8,11 +8,22 @@ interface Props {
}
function FilterSource(props: Props) {
const { filter } = props;
+ const [value, setValue] = useState(filter.source[0] || '');
const onChange = ({ target: { value, name } }) => {
props.onUpdate({ ...filter, [name]: [value] })
}
+ useEffect(() => {
+ setValue(filter.source[0] || '');
+ }, [filter])
+
+ useEffect(() => {
+ props.onUpdate({ ...filter, source: [value] })
+ }, [value])
+
+ const write = ({ target: { value, name } }) => setValue(value)
+
const renderFiled = () => {
switch(filter.sourceType) {
case FilterType.NUMBER:
@@ -20,9 +31,9 @@ function FilterSource(props: Props) {
)
diff --git a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx
index e5357d01a..4292a834b 100644
--- a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx
+++ b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx
@@ -11,6 +11,8 @@ interface Props {
function FilterValue(props: Props) {
const { filter } = props;
const [durationValues, setDurationValues] = useState({ minDuration: filter.value[0], maxDuration: filter.value[1] });
+ const showCloseButton = filter.value.length > 1;
+ const lastIndex = filter.value.length - 1;
const onAddValue = () => {
const newValues = filter.value.concat("")
@@ -65,8 +67,7 @@ function FilterValue(props: Props) {
}
const renderValueFiled = (value, valueIndex) => {
- const showCloseButton = filter.value.length > 1;
- const showOrButton = valueIndex === filter.value.length - 1;
+ const showOrButton = valueIndex === lastIndex;
switch(filter.type) {
case FilterType.DROPDOWN:
return (
diff --git a/frontend/app/constants/filterOptions.js b/frontend/app/constants/filterOptions.js
index b88e9b808..b59f4744d 100644
--- a/frontend/app/constants/filterOptions.js
+++ b/frontend/app/constants/filterOptions.js
@@ -122,6 +122,10 @@ const targetFilterKeys = ['on', 'notOn', 'onAny'];
const signUpStatusFilterKeys = ['isSignedUp', 'notSignedUp'];
const rangeFilterKeys = ['before', 'after', 'on', 'inRange', 'notInRange', 'withInLast', 'notWithInLast'];
+const getOperatorsByKeys = (keys) => {
+ return options.filter(option => keys.includes(option.key));
+};
+
export const baseOperators = options.filter(({key}) => filterKeys.includes(key));
export const stringOperators = options.filter(({key}) => stringFilterKeys.includes(key));
export const targetOperators = options.filter(({key}) => targetFilterKeys.includes(key));
@@ -145,4 +149,5 @@ export default {
targetOperators,
booleanOperators,
customOperators,
+ getOperatorsByKeys,
}
\ No newline at end of file
diff --git a/frontend/app/duck/funnels.js b/frontend/app/duck/funnels.js
index cad648839..a30916777 100644
--- a/frontend/app/duck/funnels.js
+++ b/frontend/app/duck/funnels.js
@@ -7,6 +7,7 @@ import { createItemInListUpdater, mergeReducers, success, array } from './funcTo
import { createRequestReducer } from './funcTools/request';
import { getDateRangeFromValue } from 'App/dateRange';
import { LAST_7_DAYS } from 'Types/app/period';
+import { filterMap as searchFilterMap } from './search';
const name = 'funnel';
const idKey = 'funnelId';
@@ -265,16 +266,20 @@ export const fetchIssueTypes = () => {
}
}
-export const save = (instance) => {
+export const save = (instance) => (dispatch, getState) => {
+// export const save = (instance) => {
+ const filter = getState().getIn([ 'search', 'instance']).toData();
+ filter.filters = filter.filters.map(searchFilterMap);
+
const _instance = instance instanceof Funnel ? instance : Funnel(instance);
const url = _instance.exists()
? `/funnels/${ _instance[idKey] }`
: `/funnels`;
- return {
+ return dispatch({
types: array(_instance.exists() ? SAVE : UPDATE),
- call: client => client.post(url, _instance.toData()),
- }
+ call: client => client.post(url, { ..._instance.toData(), filter }),
+ });
}
export const updateFunnelFilters = (funnelId, filter) => {
diff --git a/frontend/app/duck/search.js b/frontend/app/duck/search.js
index 86cc19e6a..027a03b88 100644
--- a/frontend/app/duck/search.js
+++ b/frontend/app/duck/search.js
@@ -7,6 +7,7 @@ import SavedFilter from 'Types/filter/savedFilter';
import { errors as errorsRoute, isRoute } from "App/routes";
import { fetchList as fetchSessionList } from './sessions';
import { fetchList as fetchErrorsList } from './errors';
+import { FilterCategory, FilterKey } from '../types/filter/filterType';
const ERRORS_ROUTE = errorsRoute();
@@ -88,13 +89,12 @@ export default mergeReducers(
}),
);
-const filterMap = ({value, key, operator, sourceOperator, source, custom, isEvent }) => ({
+export const filterMap = ({category, value, key, operator, sourceOperator, source, custom, isEvent }) => ({
value: value.filter(i => i !== '' && i !== null),
custom,
- type: key,
- // key,
+ type: category === FilterCategory.METADATA ? FilterKey.METADATA : key,
operator,
- source,
+ source: category === FilterCategory.METADATA ? key : source,
sourceOperator,
isEvent
});
diff --git a/frontend/app/types/filter/filter.js b/frontend/app/types/filter/filter.js
index 74991b53a..b8093d5cb 100644
--- a/frontend/app/types/filter/filter.js
+++ b/frontend/app/types/filter/filter.js
@@ -45,7 +45,7 @@ export default Record({
suspicious: undefined,
consoleLevel: undefined,
strict: false,
- eventsOrder: 'and',
+ eventsOrder: 'then',
}, {
idKey: 'searchId',
methods: {
diff --git a/frontend/app/types/filter/newFilter.js b/frontend/app/types/filter/newFilter.js
index 5c9b5a942..74b739918 100644
--- a/frontend/app/types/filter/newFilter.js
+++ b/frontend/app/types/filter/newFilter.js
@@ -40,8 +40,8 @@ export const filtersMap = {
[FilterKey.PLATFORM]: { key: FilterKey.PLATFORM, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.GEAR, label: 'Platform', operator: 'is', operatorOptions: filterOptions.baseOperators, icon: 'filters/platform', options: platformOptions },
[FilterKey.REVID]: { key: FilterKey.REVID, type: FilterType.MULTIPLE, category: FilterCategory.GEAR, label: 'RevId', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/rev-id' },
[FilterKey.REFERRER]: { key: FilterKey.REFERRER, type: FilterType.MULTIPLE, category: FilterCategory.RECORDING_ATTRIBUTES, label: 'Referrer', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/referrer' },
- [FilterKey.DURATION]: { key: FilterKey.DURATION, type: FilterType.DURATION, category: FilterCategory.RECORDING_ATTRIBUTES, label: 'Duration', operator: 'is', operatorOptions: filterOptions.baseOperators, icon: 'filters/duration' },
- [FilterKey.USER_COUNTRY]: { key: FilterKey.USER_COUNTRY, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.RECORDING_ATTRIBUTES, label: 'User Country', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/country', options: countryOptions },
+ [FilterKey.DURATION]: { key: FilterKey.DURATION, type: FilterType.DURATION, category: FilterCategory.RECORDING_ATTRIBUTES, label: 'Duration', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is']), icon: 'filters/duration' },
+ [FilterKey.USER_COUNTRY]: { key: FilterKey.USER_COUNTRY, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.RECORDING_ATTRIBUTES, label: 'User Country', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is', 'isAny', 'isNot']), icon: 'filters/country', options: countryOptions },
// [FilterKey.CONSOLE]: { key: FilterKey.CONSOLE, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Console', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/console' },
[FilterKey.USERID]: { key: FilterKey.USERID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User Id', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid' },
[FilterKey.USERANONYMOUSID]: { key: FilterKey.USERANONYMOUSID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User AnonymousId', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid' },