diff --git a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx
index 66bd28a1b..8fc9f16c5 100644
--- a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx
+++ b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx
@@ -5,6 +5,7 @@ import SaveFilterButton from 'Shared/SaveFilterButton';
import { connect } from 'react-redux';
import { Button } from 'UI';
import { edit, addFilter } from 'Duck/search';
+import SessionSearchQueryParamHandler from 'Shared/SessionSearchQueryParamHandler';
interface Props {
appliedFilter: any;
@@ -19,7 +20,7 @@ function SessionSearch(props: Props) {
const onAddFilter = (filter: any) => {
props.addFilter(filter);
- }
+ };
const onUpdateFilter = (filterIndex: any, filter: any) => {
const newFilters = appliedFilter.filters.map((_filter: any, i: any) => {
@@ -31,10 +32,10 @@ function SessionSearch(props: Props) {
});
props.edit({
- ...appliedFilter,
- filters: newFilters,
+ ...appliedFilter,
+ filters: newFilters,
});
- }
+ };
const onRemoveFilter = (filterIndex: any) => {
const newFilters = appliedFilter.filters.filter((_filter: any, i: any) => {
@@ -44,51 +45,59 @@ function SessionSearch(props: Props) {
props.edit({
filters: newFilters,
});
- }
+ };
const onChangeEventsOrder = (e: any, { value }: any) => {
props.edit({
eventsOrder: value,
});
- }
+ };
- return (hasEvents || hasFilters) ? (
-
-
-
-
+ return (
+ <>
+
+ {hasEvents || hasFilters ? (
+
+
+
+
-
-
-
- {/* */}
-
-
+
+
+
+ {/* */}
+
+
+
+
+
+
+
-
-
-
-
-
- ) : <>>;
+ ) : (
+ <>>
+ )}
+ >
+ );
}
-export default connect((state: any) => ({
- saveRequestPayloads: state.getIn(['site', 'active', 'saveRequestPayloads']),
- appliedFilter: state.getIn([ 'search', 'instance' ]),
-}), { edit, addFilter })(SessionSearch);
+export default connect(
+ (state: any) => ({
+ saveRequestPayloads: state.getIn(['site', 'active', 'saveRequestPayloads']),
+ appliedFilter: state.getIn(['search', 'instance']),
+ }),
+ { edit, addFilter }
+)(SessionSearch);
diff --git a/frontend/app/components/shared/SessionSearchQueryParamHandler/SessionSearchQueryParamHandler.tsx b/frontend/app/components/shared/SessionSearchQueryParamHandler/SessionSearchQueryParamHandler.tsx
new file mode 100644
index 000000000..4cc1e3be6
--- /dev/null
+++ b/frontend/app/components/shared/SessionSearchQueryParamHandler/SessionSearchQueryParamHandler.tsx
@@ -0,0 +1,121 @@
+import React, { useEffect } from 'react';
+import { useHistory } from 'react-router';
+import { connect } from 'react-redux';
+import { addFilterByKeyAndValue, addFilter } from 'Duck/search';
+import { getFilterKeyTypeByKey, setQueryParamKeyFromFilterkey } from 'Types/filter/filterType';
+import { FilterCategory, FilterKey } from 'Types/filter/filterType';
+import { filtersMap } from 'Types/filter/newFilter';
+
+const allowedQueryKeys = [
+ 'userId',
+ 'userid',
+ 'uid',
+ 'usera',
+
+ 'clk',
+ 'inp',
+ 'loc',
+
+ 'os',
+ 'browser',
+ 'device',
+ 'platform',
+ 'revid',
+
+ 'country',
+ 'ref',
+ 'sort',
+ 'order',
+ 'ce',
+ 'sa',
+ 'err',
+ 'iss',
+
+ // PERFORMANCE
+ 'domc',
+ 'lcp',
+ 'ttfb',
+ 'acpu',
+ 'amem',
+ 'ff',
+];
+
+interface Props {
+ appliedFilter: any;
+ addFilterByKeyAndValue: typeof addFilterByKeyAndValue;
+ addFilter: typeof addFilter;
+}
+const SessionSearchQueryParamHandler = React.memo((props: Props) => {
+ const { appliedFilter } = props;
+ const history = useHistory();
+
+ const createUrlQuery = (filters: any) => {
+ const query: any = {};
+ filters.forEach((filter: any) => {
+ if (filter.value.length > 0) {
+ const _key = setQueryParamKeyFromFilterkey(filter.key);
+ if (_key) {
+ let str = `${filter.operator}|${filter.value.join('|')}`;
+ if (filter.hasSource) {
+ str = `${str}^${filter.sourceOperator}|${filter.source.join('|')}`;
+ }
+ query[_key] = str;
+ } else {
+ let str = `${filter.operator}|${filter.value.join('|')}`;
+ query[filter.key] = str;
+ }
+ }
+ });
+ return query;
+ };
+
+ const addFilter = ([key, value]: [any, any]): void => {
+ if (value !== '') {
+ const filterKey = getFilterKeyTypeByKey(key);
+ const tmp = value.split('^');
+ const valueArr = tmp[0].split('|');
+ const operator = valueArr.shift();
+
+ const sourceArr = tmp[1] ? tmp[1].split('|') : [];
+ const sourceOperator = sourceArr.shift();
+ // TODO validate operator
+ if (filterKey) {
+ props.addFilterByKeyAndValue(filterKey, valueArr, operator, sourceOperator, sourceArr);
+ } else {
+ console.warn(`Filter key ${key} not found`);
+ }
+ }
+ };
+
+ const applyFilterFromQuery = () => {
+ const entires = getQueryObject(history.location.search);
+ if (entires.length > 0) {
+ entires.forEach(addFilter);
+ }
+ };
+
+ const generateUrlQuery = () => {
+ const query: any = createUrlQuery(appliedFilter.filters);
+ // const queryString = Object.entries(query).map(([key, value]) => `${key}=${value}`).join('&');
+ const queryString = new URLSearchParams(query).toString();
+ history.replace({ search: queryString });
+ };
+
+ useEffect(applyFilterFromQuery, []);
+ useEffect(generateUrlQuery, [appliedFilter]);
+ return <>>;
+});
+
+export default connect(
+ (state: any) => ({
+ appliedFilter: state.getIn(['search', 'instance']),
+ }),
+ { addFilterByKeyAndValue, addFilter }
+)(SessionSearchQueryParamHandler);
+
+function getQueryObject(search: any) {
+ const queryParams = Object.fromEntries(
+ Object.entries(Object.fromEntries(new URLSearchParams(search)))
+ );
+ return Object.entries(queryParams);
+}
diff --git a/frontend/app/components/shared/SessionSearchQueryParamHandler/index.ts b/frontend/app/components/shared/SessionSearchQueryParamHandler/index.ts
new file mode 100644
index 000000000..c13bb493d
--- /dev/null
+++ b/frontend/app/components/shared/SessionSearchQueryParamHandler/index.ts
@@ -0,0 +1 @@
+export { default } from './SessionSearchQueryParamHandler';
\ No newline at end of file
diff --git a/frontend/app/duck/search.js b/frontend/app/duck/search.js
index 3abeb408c..ff1795b74 100644
--- a/frontend/app/duck/search.js
+++ b/frontend/app/duck/search.js
@@ -315,13 +315,17 @@ export const addFilter = (filter) => (dispatch, getState) => {
};
export const addFilterByKeyAndValue =
- (key, value, operator = undefined) =>
+ (key, value, operator = undefined, sourceOperator = undefined, source = undefined) =>
(dispatch, getState) => {
let defaultFilter = filtersMap[key];
defaultFilter.value = value;
if (operator) {
defaultFilter.operator = operator;
}
+ if (defaultFilter.hasSource && source && sourceOperator) {
+ defaultFilter.sourceOperator = sourceOperator;
+ defaultFilter.source = source;
+ }
dispatch(addFilter(defaultFilter));
};
diff --git a/frontend/app/types/filter/filterType.ts b/frontend/app/types/filter/filterType.ts
index 772bea55e..9915a3aa5 100644
--- a/frontend/app/types/filter/filterType.ts
+++ b/frontend/app/types/filter/filterType.ts
@@ -1,97 +1,201 @@
export enum FilterCategory {
- INTERACTIONS = "Interactions",
- GEAR = "Gear",
- RECORDING_ATTRIBUTES = "Recording Attributes",
- JAVASCRIPT = "Javascript",
- USER = "User Identification",
- METADATA = "Session & User Metadata",
- PERFORMANCE = "Performance",
+ INTERACTIONS = 'Interactions',
+ GEAR = 'Gear',
+ RECORDING_ATTRIBUTES = 'Recording Attributes',
+ JAVASCRIPT = 'Javascript',
+ USER = 'User Identification',
+ METADATA = 'Session & User Metadata',
+ PERFORMANCE = 'Performance',
+}
+
+export const setQueryParamKeyFromFilterkey = (filterKey: string) => {
+ switch (filterKey) {
+ case FilterKey.USERID:
+ return 'uid';
+ case FilterKey.USERANONYMOUSID:
+ return 'usera';
+ case FilterKey.CLICK:
+ return 'clk';
+ case FilterKey.INPUT:
+ return 'inp';
+ case FilterKey.LOCATION:
+ return 'loc';
+ case FilterKey.USER_OS:
+ return 'os';
+ case FilterKey.USER_BROWSER:
+ return 'browser';
+ case FilterKey.USER_DEVICE:
+ return 'device';
+ case FilterKey.PLATFORM:
+ return 'platform';
+ case FilterKey.REVID:
+ return 'revid';
+ case FilterKey.USER_COUNTRY:
+ return 'country';
+ case FilterKey.REFERRER:
+ return 'ref';
+ case FilterKey.CUSTOM:
+ return 'ce';
+ case FilterKey.STATEACTION:
+ return 'sa';
+ case FilterKey.ERROR:
+ return 'err';
+ case FilterKey.ISSUE:
+ return 'iss';
+
+ // PERFORMANCE
+ case FilterKey.DOM_COMPLETE:
+ return 'domc';
+ case FilterKey.LARGEST_CONTENTFUL_PAINT_TIME:
+ return 'lcp';
+ case FilterKey.TTFB:
+ return 'ttfb';
+ case FilterKey.AVG_CPU_LOAD:
+ return 'acpu';
+ case FilterKey.AVG_MEMORY_USAGE:
+ return 'amem';
+ case FilterKey.FETCH_FAILED:
+ return 'ff';
+ }
+};
+
+export const getFilterKeyTypeByKey = (key: string) => {
+ switch (key) {
+ case 'userId':
+ case 'uid':
+ case 'userid':
+ return FilterKey.USERID;
+ case 'usera':
+ return FilterKey.USERANONYMOUSID;
+ case 'clk':
+ return FilterKey.CLICK;
+ case 'inp':
+ return FilterKey.INPUT;
+ case 'loc':
+ return FilterKey.LOCATION;
+ case 'os':
+ return FilterKey.USER_OS;
+ case 'browser':
+ return FilterKey.USER_BROWSER;
+ case 'device':
+ return FilterKey.USER_DEVICE;
+ case 'platform':
+ return FilterKey.PLATFORM;
+ case 'revid':
+ return FilterKey.REVID;
+ case 'country':
+ return FilterKey.USER_COUNTRY;
+ case 'ref':
+ return FilterKey.REFERRER;
+ case 'ce':
+ return FilterKey.CUSTOM;
+ case 'sa':
+ return FilterKey.STATEACTION;
+ case 'err':
+ return FilterKey.ERROR;
+ case 'iss':
+ return FilterKey.ISSUE;
+
+ // PERFORMANCE
+ case 'domc':
+ return FilterKey.DOM_COMPLETE;
+ case 'lcp':
+ return FilterKey.LARGEST_CONTENTFUL_PAINT_TIME;
+ case 'ttfb':
+ return FilterKey.TTFB;
+ case 'acpu':
+ return FilterKey.AVG_CPU_LOAD;
+ case 'amem':
+ return FilterKey.AVG_MEMORY_USAGE;
+ case 'ff':
+ return FilterKey.FETCH_FAILED;
+ }
};
export enum IssueType {
- CLICK_RAGE = "click_rage",
- DEAD_CLICK = "dead_click",
- EXCESSIVE_SCROLLING = "excessive_scrolling",
- BAD_REQUEST = "bad_request",
- MISSING_RESOURCE = "missing_resource",
- MEMORY = "memory",
- CPU = "cpu",
- SLOW_RESOURCE = "slow_resource",
- SLOW_PAGE_LOAD = "slow_page_load",
- CRASH = "crash",
- CUSTOM = "custom",
- JS_EXCEPTION = "js_exception",
+ CLICK_RAGE = 'click_rage',
+ DEAD_CLICK = 'dead_click',
+ EXCESSIVE_SCROLLING = 'excessive_scrolling',
+ BAD_REQUEST = 'bad_request',
+ MISSING_RESOURCE = 'missing_resource',
+ MEMORY = 'memory',
+ CPU = 'cpu',
+ SLOW_RESOURCE = 'slow_resource',
+ SLOW_PAGE_LOAD = 'slow_page_load',
+ CRASH = 'crash',
+ CUSTOM = 'custom',
+ JS_EXCEPTION = 'js_exception',
}
export enum FilterType {
- STRING = "STRING",
- ISSUE = "ISSUE",
- BOOLEAN = "BOOLEAN",
- NUMBER = "NUMBER",
- NUMBER_MULTIPLE = "NUMBER_MULTIPLE",
- DURATION = "DURATION",
- MULTIPLE = "MULTIPLE",
- SUB_FILTERS = "SUB_FILTERS",
- COUNTRY = "COUNTRY",
- DROPDOWN = "DROPDOWN",
- MULTIPLE_DROPDOWN = "MULTIPLE_DROPDOWN",
- AUTOCOMPLETE_LOCAL = "AUTOCOMPLETE_LOCAL",
-};
+ STRING = 'STRING',
+ ISSUE = 'ISSUE',
+ BOOLEAN = 'BOOLEAN',
+ NUMBER = 'NUMBER',
+ NUMBER_MULTIPLE = 'NUMBER_MULTIPLE',
+ DURATION = 'DURATION',
+ MULTIPLE = 'MULTIPLE',
+ SUB_FILTERS = 'SUB_FILTERS',
+ COUNTRY = 'COUNTRY',
+ DROPDOWN = 'DROPDOWN',
+ MULTIPLE_DROPDOWN = 'MULTIPLE_DROPDOWN',
+ AUTOCOMPLETE_LOCAL = 'AUTOCOMPLETE_LOCAL',
+}
export enum FilterKey {
- ERROR = "ERROR",
- MISSING_RESOURCE = "MISSING_RESOURCE",
- SLOW_SESSION = "SLOW_SESSION",
- CLICK_RAGE = "CLICK_RAGE",
- CLICK = "CLICK",
- INPUT = "INPUT",
- LOCATION = "LOCATION",
- VIEW = "VIEW",
- CONSOLE = "CONSOLE",
- METADATA = "METADATA",
- CUSTOM = "CUSTOM",
- URL = "URL",
- USER_BROWSER = "USERBROWSER",
- USER_OS = "USEROS",
- USER_DEVICE = "USERDEVICE",
- PLATFORM = "PLATFORM",
- DURATION = "DURATION",
- REFERRER = "REFERRER",
- USER_COUNTRY = "USERCOUNTRY",
- JOURNEY = "JOURNEY",
- REQUEST = "REQUEST",
- GRAPHQL = "GRAPHQL",
- STATEACTION = "STATEACTION",
- REVID = "REVID",
- USERANONYMOUSID = "USERANONYMOUSID",
- USERID = "USERID",
- ISSUE = "ISSUE",
- EVENTS_COUNT = "EVENTS_COUNT",
- UTM_SOURCE = "UTM_SOURCE",
- UTM_MEDIUM = "UTM_MEDIUM",
- UTM_CAMPAIGN = "UTM_CAMPAIGN",
-
- DOM_COMPLETE = "DOM_COMPLETE",
- LARGEST_CONTENTFUL_PAINT_TIME = "LARGEST_CONTENTFUL_PAINT_TIME",
- TIME_BETWEEN_EVENTS = "TIME_BETWEEN_EVENTS",
- TTFB = "TTFB",
- AVG_CPU_LOAD = "AVG_CPU_LOAD",
- AVG_MEMORY_USAGE = "AVG_MEMORY_USAGE",
- FETCH_FAILED = "FETCH_FAILED",
-
- FETCH = "FETCH",
- FETCH_URL = "FETCH_URL",
- FETCH_STATUS_CODE = "FETCH_STATUS_CODE",
- FETCH_METHOD = "FETCH_METHOD",
- FETCH_DURATION = "FETCH_DURATION",
- FETCH_REQUEST_BODY = "FETCH_REQUEST_BODY",
- FETCH_RESPONSE_BODY = "FETCH_RESPONSE_BODY",
+ ERROR = 'ERROR',
+ MISSING_RESOURCE = 'MISSING_RESOURCE',
+ SLOW_SESSION = 'SLOW_SESSION',
+ CLICK_RAGE = 'CLICK_RAGE',
+ CLICK = 'CLICK',
+ INPUT = 'INPUT',
+ LOCATION = 'LOCATION',
+ VIEW = 'VIEW',
+ CONSOLE = 'CONSOLE',
+ METADATA = 'METADATA',
+ CUSTOM = 'CUSTOM',
+ URL = 'URL',
+ USER_BROWSER = 'USERBROWSER',
+ USER_OS = 'USEROS',
+ USER_DEVICE = 'USERDEVICE',
+ PLATFORM = 'PLATFORM',
+ DURATION = 'DURATION',
+ REFERRER = 'REFERRER',
+ USER_COUNTRY = 'USERCOUNTRY',
+ JOURNEY = 'JOURNEY',
+ REQUEST = 'REQUEST',
+ GRAPHQL = 'GRAPHQL',
+ STATEACTION = 'STATEACTION',
+ REVID = 'REVID',
+ USERANONYMOUSID = 'USERANONYMOUSID',
+ USERID = 'USERID',
+ ISSUE = 'ISSUE',
+ EVENTS_COUNT = 'EVENTS_COUNT',
+ UTM_SOURCE = 'UTM_SOURCE',
+ UTM_MEDIUM = 'UTM_MEDIUM',
+ UTM_CAMPAIGN = 'UTM_CAMPAIGN',
- GRAPHQL_NAME = "GRAPHQL_NAME",
- GRAPHQL_METHOD = "GRAPHQL_METHOD",
- GRAPHQL_REQUEST_BODY = "GRAPHQL_REQUEST_BODY",
- GRAPHQL_RESPONSE_BODY = "GRAPHQL_RESPONSE_BODY",
+ DOM_COMPLETE = 'DOM_COMPLETE',
+ LARGEST_CONTENTFUL_PAINT_TIME = 'LARGEST_CONTENTFUL_PAINT_TIME',
+ TIME_BETWEEN_EVENTS = 'TIME_BETWEEN_EVENTS',
+ TTFB = 'TTFB',
+ AVG_CPU_LOAD = 'AVG_CPU_LOAD',
+ AVG_MEMORY_USAGE = 'AVG_MEMORY_USAGE',
+ FETCH_FAILED = 'FETCH_FAILED',
+
+ FETCH = 'FETCH',
+ FETCH_URL = 'FETCH_URL',
+ FETCH_STATUS_CODE = 'FETCH_STATUS_CODE',
+ FETCH_METHOD = 'FETCH_METHOD',
+ FETCH_DURATION = 'FETCH_DURATION',
+ FETCH_REQUEST_BODY = 'FETCH_REQUEST_BODY',
+ FETCH_RESPONSE_BODY = 'FETCH_RESPONSE_BODY',
+
+ GRAPHQL_NAME = 'GRAPHQL_NAME',
+ GRAPHQL_METHOD = 'GRAPHQL_METHOD',
+ GRAPHQL_REQUEST_BODY = 'GRAPHQL_REQUEST_BODY',
+ GRAPHQL_RESPONSE_BODY = 'GRAPHQL_RESPONSE_BODY',
SESSIONS = 'SESSIONS',
- ERRORS = 'js_exception'
-}
\ No newline at end of file
+ ERRORS = 'js_exception',
+}
diff --git a/frontend/app/types/filter/newFilter.js b/frontend/app/types/filter/newFilter.js
index 9a87da2c6..452c4f1c9 100644
--- a/frontend/app/types/filter/newFilter.js
+++ b/frontend/app/types/filter/newFilter.js
@@ -11,7 +11,7 @@ export const filters = [
{ key: FilterKey.INPUT, type: FilterType.MULTIPLE, category: FilterCategory.INTERACTIONS, label: 'Input', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/input', isEvent: true },
{ key: FilterKey.LOCATION, type: FilterType.MULTIPLE, category: FilterCategory.INTERACTIONS, label: 'Path', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/location', isEvent: true },
{ key: FilterKey.CUSTOM, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Custom Events', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/custom', isEvent: true },
- { key: FilterKey.REQUEST, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Fetch', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch', isEvent: true },
+ // { key: FilterKey.REQUEST, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Fetch', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch', isEvent: true },
{ key: FilterKey.FETCH, type: FilterType.SUB_FILTERS, category: FilterCategory.JAVASCRIPT, operator: 'is', label: 'Network Request', filters: [
{ key: FilterKey.FETCH_URL, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with URL', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' },
{ key: FilterKey.FETCH_STATUS_CODE, type: FilterType.NUMBER_MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with status code', operator: '=', operatorOptions: filterOptions.customOperators, icon: 'filters/fetch' },