From 2008d3dc2ff339b66f83ef5bc7c6aaed4e2b5d67 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Wed, 26 Jan 2022 00:24:08 +0530 Subject: [PATCH] feat(ui) - custom metrics --- .../app/components/BugFinder/DateRange.js | 12 +- .../shared/Filters/FilterList/FilterList.tsx | 2 +- .../Filters/FilterModal/FilterModal.tsx | 4 +- .../Filters/FilterValue/FilterValue.tsx | 3 +- .../FilterValueDropdown.css | 41 ++++- .../FilterValueDropdown.tsx | 35 +++-- .../SaveSearchModal/SaveSearchModal.tsx | 4 +- .../shared/SessionSearch/SessionSearch.tsx | 30 +--- frontend/app/duck/search.js | 30 ++-- frontend/app/svg/icons/filters/custom.svg | 1 + frontend/app/types/customMetric.js | 5 - frontend/app/types/filter/filter.js | 22 ++- frontend/app/types/filter/filterType.ts | 11 ++ frontend/app/types/filter/newFilter.js | 140 ++++++++---------- 14 files changed, 193 insertions(+), 147 deletions(-) create mode 100644 frontend/app/svg/icons/filters/custom.svg diff --git a/frontend/app/components/BugFinder/DateRange.js b/frontend/app/components/BugFinder/DateRange.js index 8f16b5207..b9bbd745a 100644 --- a/frontend/app/components/BugFinder/DateRange.js +++ b/frontend/app/components/BugFinder/DateRange.js @@ -5,20 +5,24 @@ import { fetchList as fetchFunnelsList } from 'Duck/funnels'; import DateRangeDropdown from 'Shared/DateRangeDropdown'; @connect(state => ({ - rangeValue: state.getIn([ 'filters', 'appliedFilter', 'rangeValue' ]), - startDate: state.getIn([ 'filters', 'appliedFilter', 'startDate' ]), - endDate: state.getIn([ 'filters', 'appliedFilter', 'endDate' ]), + filter: state.getIn([ 'search', 'instance' ]), + // rangeValue: state.getIn([ 'search', 'instance', 'rangeValue' ]), + // startDate: state.getIn([ 'search', 'instance', 'startDate' ]), + // endDate: state.getIn([ 'search', 'instance', 'endDate' ]), }), { applyFilter, fetchFunnelsList }) export default class DateRange extends React.PureComponent { + onDateChange = (e) => { console.log('onDateChange', e); this.props.fetchFunnelsList(e.rangeValue) this.props.applyFilter(e) } render() { - const { startDate, endDate, rangeValue, className } = this.props; + const { filter: { rangeValue, startDate, endDate }, className } = this.props; + // const { startDate, endDate, rangeValue, className } = this.props; + return ( i.isEvent).size > 0; const hasFilters = filter.filters.filter(i => !i.isEvent).size > 0; diff --git a/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx b/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx index b2299b2e2..e282ab78f 100644 --- a/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx +++ b/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx @@ -10,9 +10,9 @@ function FilterModal(props: Props) { const { filters, onFilterClick = () => null } = props; return (
-
+
{filters && Object.keys(filters).map((key) => ( -
+
{key}
{filters[key].map((filter: any) => ( diff --git a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx index 2983febc8..ef327eb7f 100644 --- a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx +++ b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx @@ -53,7 +53,6 @@ function FilterValue(props: Props) { const renderValueFiled = (value, valueIndex) => { switch(filter.type) { - case FilterType.ISSUE: case FilterType.DROPDOWN: return ( onSelect(e, { value }, valueIndex)} /> ) + case FilterType.ISSUE: case FilterType.MULTIPLE_DROPDOWN: return ( diff --git a/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.css b/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.css index 8a2329dbd..eb2c457f7 100644 --- a/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.css +++ b/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.css @@ -1,3 +1,38 @@ +.wrapper { + border: solid thin $gray-light !important; + border-radius: 3px; + background-color: $gray-lightest !important; + display: flex; + align-items: center; + height: 30px; + + & .right { + height: 28px; + display: flex; + align-items: stretch; + padding: 0; + background-color: $gray-lightest; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + + & div { + /* background-color: red; */ + border-left: solid thin $gray-light !important; + width: 28px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + &:last-child { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + } + &:hover { + background-color: $gray-light; + } + } + } +} .operatorDropdown { font-weight: 400; height: 30px; @@ -8,9 +43,9 @@ padding: 0 8px !important; font-size: 13px; /* background-color: rgba(255, 255, 255, 0.8) !important; */ - background-color: $gray-lightest !important; - border: solid thin rgba(34, 36, 38, 0.15) !important; - border-radius: 4px !important; + /* background-color: $gray-lightest !important; */ + /* border: solid thin rgba(34, 36, 38, 0.15) !important; */ + /* border-radius: 4px !important; */ color: $gray-darkest !important; font-size: 14px !important; &.ui.basic.button { diff --git a/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.tsx b/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.tsx index 248fcabed..f2a54afc8 100644 --- a/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.tsx +++ b/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.tsx @@ -12,22 +12,35 @@ interface Props { options: any[]; search?: boolean; multiple?: boolean; + showCloseButton?: boolean; + showOrButton?: boolean; + onRemoveValue?: () => void; + onAddValue?: () => void; } function FilterValueDropdown(props: Props) { - const { multiple = false, search = false, options, onChange, value, className = '' } = props; + const { multiple = false, search = false, options, onChange, value, className = '', showCloseButton = true, showOrButton = true } = props; // const options = [] return ( - } - /> +
+ } + /> +
+ { showCloseButton &&
} + { showOrButton &&
or
} +
+
); } diff --git a/frontend/app/components/shared/SaveSearchModal/SaveSearchModal.tsx b/frontend/app/components/shared/SaveSearchModal/SaveSearchModal.tsx index e609440c2..29084ec42 100644 --- a/frontend/app/components/shared/SaveSearchModal/SaveSearchModal.tsx +++ b/frontend/app/components/shared/SaveSearchModal/SaveSearchModal.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; -import { edit, save } from 'Duck/filters'; +import { edit, save } from 'Duck/search'; import { Button, Modal, Form, Icon, Checkbox } from 'UI'; import stl from './SaveSearchModal.css'; @@ -72,7 +72,7 @@ function SaveSearchModal(props: Props) { } export default connect(state => ({ - filter: state.getIn(['filters', 'instance']), + filter: state.getIn(['search', 'instance']), loading: state.getIn([ 'filters', 'saveRequest', 'loading' ]) || state.getIn([ 'filters', 'updateRequest', 'loading' ]), }), { edit, save })(SaveSearchModal); \ No newline at end of file diff --git a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx index 4bd50d609..caba99eae 100644 --- a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx +++ b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx @@ -15,18 +15,15 @@ function SessionSearch(props) { const onAddFilter = (filter) => { filter.value = [""] - const newFilters = appliedFilter.filter.filters.concat(filter); + const newFilters = appliedFilter.filters.concat(filter); props.edit({ - ...appliedFilter, - filter: { ...appliedFilter.filter, filters: newFilters, - } }); } const onUpdateFilter = (filterIndex, filter) => { - const newFilters = appliedFilter.filter.filters.map((_filter, i) => { + const newFilters = appliedFilter.filters.map((_filter, i) => { if (i === filterIndex) { return filter; } else { @@ -35,45 +32,30 @@ function SessionSearch(props) { }); props.edit({ - ...appliedFilter.toData(), - filter: { ...appliedFilter.filter, filters: newFilters, - } }); } const onRemoveFilter = (filterIndex) => { - const newFilters = appliedFilter.filter.filters.filter((_filter, i) => { + const newFilters = appliedFilter.filters.filter((_filter, i) => { return i !== filterIndex; }); props.edit({ - ...appliedFilter, - filter: { - ...appliedFilter.filter, - filters: newFilters, - } + filters: newFilters, }); } const onChangeEventsOrder = (e, { name, value }) => { props.edit({ - ...appliedFilter.toData(), - filter: { - ...appliedFilter.filter.toData(), - eventsOrder: value, - } + eventsOrder: value, }); } const clearSearch = () => { props.edit({ - ...appliedFilter.toData(), - filter: { - ...appliedFilter.filter.toData(), filters: [], - } }); } @@ -83,7 +65,7 @@ function SessionSearch(props) {
list.filter(item => item.metricId !== action.id)); case success(FETCH): return state.set("instance", ErrorInfo(action.data)); @@ -79,9 +73,9 @@ export default mergeReducers( }), ); - const filterMap = ({value, type, key, operator, source, custom, isEvent }) => ({ - value: Array.isArray(value) ? value: [value], + // value: Array.isArray(value) ? value: [value], + value: value.filter(i => i !== '' && i !== null), custom, type: key, key, operator, @@ -91,7 +85,7 @@ const filterMap = ({value, type, key, operator, source, custom, isEvent }) => ({ const reduceThenFetchResource = actionCreator => (...args) => (dispatch, getState) => { dispatch(actionCreator(...args)); - const filter = getState().getIn([ 'search', 'instance', 'filter' ]).toData(); + const filter = getState().getIn([ 'search', 'instance']).toData(); filter.filters = filter.filters.map(filterMap); filter.isNew = true // TODO remove this line @@ -130,7 +124,11 @@ export function fetch(id) { export function save(instance) { return { types: SAVE.array, - call: client => client.post( `/${ name }s`, instance.toSaveData()), + call: client => client.post('/saved_search', { + name: instance.name, + filter: instance.filter.toSaveData(), + }), + instance, }; } diff --git a/frontend/app/svg/icons/filters/custom.svg b/frontend/app/svg/icons/filters/custom.svg new file mode 100644 index 000000000..c9c41b0d7 --- /dev/null +++ b/frontend/app/svg/icons/filters/custom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/app/types/customMetric.js b/frontend/app/types/customMetric.js index e40eddb27..0334ae2e6 100644 --- a/frontend/app/types/customMetric.js +++ b/frontend/app/types/customMetric.js @@ -1,11 +1,6 @@ import Record from 'Types/Record'; import { List } from 'immutable'; -// import { DateTime } from 'luxon'; -// import { validateEmail, validateName } from 'App/validate'; import Filter from 'Types/filter'; -import NewFilter from 'Types/filter'; -// import { Event } from 'Types/filter'; -// import CustomFilter from 'Types/filter/customFilter'; export const FilterSeries = Record({ seriesId: undefined, diff --git a/frontend/app/types/filter/filter.js b/frontend/app/types/filter/filter.js index 3bb9069a5..a9871c793 100644 --- a/frontend/app/types/filter/filter.js +++ b/frontend/app/types/filter/filter.js @@ -35,8 +35,8 @@ export default Record({ startDate, endDate, - sort: '', - order: '', + sort: 'startTs', + order: 'desc', viewed: undefined, consoleLogCount: undefined, @@ -49,6 +49,24 @@ export default Record({ }, { idKey: 'searchId', methods: { + toSaveData() { + const js = this.toJS(); + js.filters = js.filters.map(filter => { + filter.type = filter.key + + delete filter.category + delete filter.icon + delete filter.operatorOptions + delete filter._key + delete filter.key + return filter; + }); + + delete js.createdAt; + delete js.key; + delete js._key; + return js; + }, toData() { const js = this.toJS(); js.filters = js.filters.map(filter => { diff --git a/frontend/app/types/filter/filterType.ts b/frontend/app/types/filter/filterType.ts index f1e2c33a9..b5449b161 100644 --- a/frontend/app/types/filter/filterType.ts +++ b/frontend/app/types/filter/filterType.ts @@ -1,3 +1,13 @@ +export enum FilterCategory { + INTERACTIONS = "Interactions", + GEAR = "Gear", + RECORDING_ATTRIBUTES = "Recording Attributes", + JAVASCRIPT = "Javascript", + USER = "User", + METADATA = "Metadata", + PERFORMANCE = "Performance", +}; + export enum FilterType { ISSUE = "ISSUE", BOOLEAN = "BOOLEAN", @@ -48,4 +58,5 @@ export enum FilterKey { TTFB = "TTFB", AVG_CPU_LOAD = "AVG_CPU_LOAD", AVG_MEMORY_USAGE = "AVG_MEMORY_USAGE", + FETCH_FAILED = "FETCH_FAILED", } \ No newline at end of file diff --git a/frontend/app/types/filter/newFilter.js b/frontend/app/types/filter/newFilter.js index 1e27ab5b5..07628ffe2 100644 --- a/frontend/app/types/filter/newFilter.js +++ b/frontend/app/types/filter/newFilter.js @@ -1,58 +1,27 @@ import Record from 'Types/Record'; -import { FilterType, FilterKey } from './filterType' +import { FilterType, FilterKey, FilterCategory } from './filterType' import { countries, platformOptions } from 'App/constants'; const countryOptions = Object.keys(countries).map(i => ({ text: countries[i], value: i })); -export const CLICK = 'CLICK'; -export const INPUT = 'INPUT'; -export const LOCATION = 'LOCATION'; -export const VIEW = 'VIEW_IOS'; -export const CONSOLE = 'ERROR'; -export const METADATA = 'METADATA'; -export const CUSTOM = 'CUSTOM'; -export const URL = 'URL'; -export const CLICK_RAGE = 'CLICKRAGE'; -export const USER_BROWSER = 'USERBROWSER'; -export const USER_OS = 'USEROS'; -export const USER_COUNTRY = 'USERCOUNTRY'; -export const USER_DEVICE = 'USERDEVICE'; -export const PLATFORM = 'PLATFORM'; -export const DURATION = 'DURATION'; -export const REFERRER = 'REFERRER'; -export const ERROR = 'ERROR'; -export const MISSING_RESOURCE = 'MISSINGRESOURCE'; -export const SLOW_SESSION = 'SLOWSESSION'; -export const JOURNEY = 'JOUNRNEY'; -export const FETCH = 'REQUEST'; -export const GRAPHQL = 'GRAPHQL'; -export const STATEACTION = 'STATEACTION'; -export const REVID = 'REVID'; -export const USERANONYMOUSID = 'USERANONYMOUSID'; -export const USERID = 'USERID'; - -export const ISSUE = 'ISSUE'; -export const EVENTS_COUNT = 'EVENTS_COUNT'; -export const UTM_SOURCE = 'UTM_SOURCE'; -export const UTM_MEDIUM = 'UTM_MEDIUM'; -export const UTM_CAMPAIGN = 'UTM_CAMPAIGN'; - - -export const DOM_COMPLETE = 'DOM_COMPLETE'; -export const LARGEST_CONTENTFUL_PAINT_TIME = 'LARGEST_CONTENTFUL_PAINT_TIME'; -export const TIME_BETWEEN_EVENTS = 'TIME_BETWEEN_EVENTS'; -export const TTFB = 'TTFB'; -export const AVG_CPU_LOAD = 'AVG_CPU_LOAD'; -export const AVG_MEMORY_USAGE = 'AVG_MEMORY_USAGE'; - const ISSUE_OPTIONS = [ { text: 'Click Range', value: 'click_rage' }, { text: 'Dead Click', value: 'dead_click' }, + { text: 'Excessive Scrolling', value: 'excessive_scrolling' }, + { text: 'Bad Request', value: 'bad_request' }, + { text: 'Missing Resource', value: 'missing_resource' }, + { text: 'Memory', value: 'memory' }, + { text: 'CPU', value: 'cpu' }, + { text: 'Slow Resource', value: 'slow_resource' }, + { text: 'Slow Page Load', value: 'slow_page_load' }, + { text: 'Crash', value: 'crash' }, + { text: 'Custom', value: 'custom' }, + { text: 'JS Exception', value: 'js_exception' }, ] const filterKeys = ['is', 'isNot']; -const stringFilterKeys = ['is', 'isNot', 'contains', 'startsWith', 'endsWith']; -const targetFilterKeys = ['on', 'notOn']; +const stringFilterKeys = ['is', 'isNot', 'contains', 'startsWith', 'endsWith', 'notContains']; +const targetFilterKeys = ['on', 'notOn', 'onAny']; const signUpStatusFilterKeys = ['isSignedUp', 'notSignedUp']; const rangeFilterKeys = ['before', 'after', 'on', 'inRange', 'notInRange', 'withInLast', 'notWithInLast']; @@ -78,9 +47,9 @@ const options = [ text: 'contains', value: 'contains' }, { - key: 'doesNotContain', - text: 'does not contain', - value: 'doesNotContain' + key: 'notContains', + text: 'not contains', + value: 'notContains' }, { key: 'hasAnyValue', text: 'has any value', @@ -172,9 +141,9 @@ const options = [ { - key: 'onAnything', - text: 'on anything', - value: 'onAnything' + key: 'onAny', + text: 'on any', + value: 'onAny' } ]; @@ -186,41 +155,59 @@ export const booleanOptions = [ { key: 'false', text: 'false', value: 'false' }, ] +export const customOperators = [ + { key: '=', text: '=', value: '=' }, + { key: '<', text: '<', value: '<' }, + { key: '>', text: '>', value: '>' }, + { key: '<=', text: '<=', value: '<=' }, + { key: '>=', text: '>=', value: '>=' }, +] + export const filtersMap = { - [FilterKey.CLICK]: { key: FilterKey.CLICK, type: FilterType.MULTIPLE, category: 'interactions', label: 'Click', operator: 'on', operatorOptions: targetFilterOptions, icon: 'filters/click', isEvent: true }, - [FilterKey.INPUT]: { key: FilterKey.INPUT, type: FilterType.MULTIPLE, category: 'interactions', label: 'Input', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/input', isEvent: true }, - [FilterKey.LOCATION]: { key: FilterKey.LOCATION, type: FilterType.MULTIPLE, category: 'interactions', label: 'Page', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/location', isEvent: true }, + [FilterKey.CLICK]: { key: FilterKey.CLICK, type: FilterType.MULTIPLE, category: FilterCategory.INTERACTIONS, label: 'Click', operator: 'on', operatorOptions: targetFilterOptions, icon: 'filters/click', isEvent: true }, + [FilterKey.INPUT]: { key: FilterKey.INPUT, type: FilterType.MULTIPLE, category: FilterCategory.INTERACTIONS, label: 'Input', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/input', isEvent: true }, + [FilterKey.LOCATION]: { key: FilterKey.LOCATION, type: FilterType.MULTIPLE, category: FilterCategory.INTERACTIONS, label: 'Page', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/location', isEvent: true }, + [FilterKey.CUSTOM]: { key: FilterKey.CUSTOM, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Custom Events', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/custom', isEvent: true }, + [FilterKey.FETCH]: { key: FilterKey.FETCH, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Fetch', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/fetch', isEvent: true }, + [FilterKey.GRAPHQL]: { key: FilterKey.GRAPHQL, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'GraphQL', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/graphql', isEvent: true }, + [FilterKey.STATEACTION]: { key: FilterKey.STATEACTION, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'StateAction', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/state-action', isEvent: true }, + [FilterKey.ERROR]: { key: FilterKey.ERROR, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Error', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/error', isEvent: true }, + // [FilterKey.METADATA]: { key: FilterKey.METADATA, type: FilterType.MULTIPLE, category: FilterCategory.METADATA, label: 'Metadata', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/metadata', isEvent: true }, - [FilterKey.USER_OS]: { key: FilterKey.USER_OS, type: FilterType.MULTIPLE, category: 'gear', label: 'User OS', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/os' }, - [FilterKey.USER_BROWSER]: { key: FilterKey.USER_BROWSER, type: FilterType.MULTIPLE, category: 'gear', label: 'User Browser', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/browser' }, - [FilterKey.USER_DEVICE]: { key: FilterKey.USER_DEVICE, type: FilterType.MULTIPLE, category: 'gear', label: 'User Device', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/device' }, - [FilterKey.PLATFORM]: { key: FilterKey.PLATFORM, type: FilterType.MULTIPLE_DROPDOWN, category: 'gear', label: 'Platform', operator: 'is', operatorOptions: filterOptions, icon: 'filters/platform', options: platformOptions }, - [FilterKey.REVID]: { key: FilterKey.REVID, type: FilterType.MULTIPLE, category: 'gear', label: 'RevId', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/rev-id' }, + [FilterKey.USER_OS]: { key: FilterKey.USER_OS, type: FilterType.MULTIPLE, category: FilterCategory.GEAR, label: 'User OS', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/os' }, + [FilterKey.USER_BROWSER]: { key: FilterKey.USER_BROWSER, type: FilterType.MULTIPLE, category: FilterCategory.GEAR, label: 'User Browser', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/browser' }, + [FilterKey.USER_DEVICE]: { key: FilterKey.USER_DEVICE, type: FilterType.MULTIPLE, category: FilterCategory.GEAR, label: 'User Device', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/device' }, + [FilterKey.PLATFORM]: { key: FilterKey.PLATFORM, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.GEAR, label: 'Platform', operator: 'is', operatorOptions: filterOptions, icon: 'filters/platform', options: platformOptions }, + [FilterKey.REVID]: { key: FilterKey.REVID, type: FilterType.MULTIPLE, category: FilterCategory.GEAR, label: 'RevId', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/rev-id' }, - [FilterKey.REFERRER]: { key: FilterKey.REFERRER, type: FilterType.MULTIPLE, category: 'recording_attributes', label: 'Referrer', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/referrer' }, - [FilterKey.DURATION]: { key: FilterKey.DURATION, type: FilterType.DURATION, category: 'recording_attributes', label: 'Duration', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/duration' }, - [FilterKey.USER_COUNTRY]: { key: FilterKey.USER_COUNTRY, type: FilterType.DROPDOWN, category: 'recording_attributes', label: 'User Country', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/country', options: countryOptions }, + [FilterKey.REFERRER]: { key: FilterKey.REFERRER, type: FilterType.MULTIPLE, category: FilterCategory.RECORDING_ATTRIBUTES, label: 'Referrer', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/referrer' }, + [FilterKey.DURATION]: { key: FilterKey.DURATION, type: FilterType.DURATION, category: FilterCategory.RECORDING_ATTRIBUTES, label: 'Duration', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/duration' }, + [FilterKey.USER_COUNTRY]: { key: FilterKey.USER_COUNTRY, type: FilterType.DROPDOWN, category: FilterCategory.RECORDING_ATTRIBUTES, label: 'User Country', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/country', options: countryOptions }, - [FilterKey.CONSOLE]: { key: FilterKey.CONSOLE, type: FilterType.MULTIPLE, category: 'javascript', label: 'Console', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/console' }, - [FilterKey.ERROR]: { key: FilterKey.ERROR, type: FilterType.MULTIPLE, category: 'javascript', label: 'Error', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/error' }, - [FilterKey.FETCH]: { key: FilterKey.FETCH, type: FilterType.MULTIPLE, category: 'javascript', label: 'Fetch', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/fetch' }, - [FilterKey.GRAPHQL]: { key: FilterKey.GRAPHQL, type: FilterType.MULTIPLE, category: 'javascript', label: 'GraphQL', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/graphql' }, - [FilterKey.STATEACTION]: { key: FilterKey.STATEACTION, type: FilterType.MULTIPLE, category: 'javascript', label: 'StateAction', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/state-action' }, - - [FilterKey.USERID]: { key: FilterKey.USERID, type: FilterType.MULTIPLE, category: 'user', label: 'UserId', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/userid' }, - [FilterKey.USERANONYMOUSID]: { key: FilterKey.USERANONYMOUSID, type: FilterType.MULTIPLE, category: 'user', label: 'UserAnonymousId', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/userid' }, + [FilterKey.CONSOLE]: { key: FilterKey.CONSOLE, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Console', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/console' }, + + + + + [FilterKey.USERID]: { key: FilterKey.USERID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'UserId', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/userid' }, + [FilterKey.USERANONYMOUSID]: { key: FilterKey.USERANONYMOUSID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'UserAnonymousId', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/userid' }, + + [FilterKey.DOM_COMPLETE]: { key: FilterKey.DOM_COMPLETE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'DOM Complete', operator: 'is', operatorOptions: stringFilterOptions, sourcesourceOperatorOptions: customOperators, source: [], icon: 'filters/click', isEvent: true }, + [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: stringFilterOptions, icon: 'filters/click', isEvent: true }, + // [FilterKey.TIME_BETWEEN_EVENTS]: { key: FilterKey.TIME_BETWEEN_EVENTS, type: FilterType.NUMBER, category: FilterCategory.PERFORMANCE, label: 'Time Between Events', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/click' }, + [FilterKey.TTFB]: { key: FilterKey.TTFB, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Time to First Byte', operator: 'is', operatorOptions: stringFilterOptions, sourceOperatorOptions: customOperators, source: [], icon: 'filters/click', isEvent: true }, + [FilterKey.AVG_CPU_LOAD]: { key: FilterKey.AVG_CPU_LOAD, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg CPU Load', operator: 'is', operatorOptions: stringFilterOptions, sourceOperatorOptions: customOperators, source: [], icon: 'filters/click', isEvent: true }, + [FilterKey.AVG_MEMORY_USAGE]: { key: FilterKey.AVG_MEMORY_USAGE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg Memory Usage', operator: 'is', operatorOptions: stringFilterOptions, sourceOperatorOptions: customOperators, source: [], icon: 'filters/click', isEvent: true }, + [FilterKey.FETCH_FAILED]: { key: FilterKey.AVG_MEMORY_USAGE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Fetch Failed', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/click', isEvent: true }, + - // [FilterKey.DOM_COMPLETE]: { key: FilterKey.DOM_COMPLETE, type: FilterType.MULTIPLE, category: 'new', label: 'DOM Complete', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/click' }, - // [FilterKey.LARGEST_CONTENTFUL_PAINT_TIME]: { key: FilterKey.LARGEST_CONTENTFUL_PAINT_TIME, type: FilterType.NUMBER, category: 'new', label: 'Largest Contentful Paint Time', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/click' }, - // [FilterKey.TIME_BETWEEN_EVENTS]: { key: FilterKey.TIME_BETWEEN_EVENTS, type: FilterType.NUMBER, category: 'new', label: 'Time Between Events', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/click' }, - // [FilterKey.TTFB]: { key: FilterKey.TTFB, type: 'time', category: 'new', label: 'TTFB', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/click' }, // [FilterKey.AVG_CPU_LOAD]: { key: FilterKey.AVG_CPU_LOAD, type: FilterType.NUMBER, category: 'new', label: 'Avg CPU Load', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/click' }, // [FilterKey.AVG_MEMORY_USAGE]: { key: FilterKey.AVG_MEMORY_USAGE, type: FilterType.NUMBER, category: 'new', label: 'Avg Memory Usage', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/click' }, // [FilterKey.SLOW_SESSION]: { key: FilterKey.SLOW_SESSION, type: FilterType.BOOLEAN, category: 'new', label: 'Slow Session', operator: 'true', operatorOptions: [{ key: 'true', text: 'true', value: 'true' }], icon: 'filters/click' }, // [FilterKey.MISSING_RESOURCE]: { key: FilterKey.MISSING_RESOURCE, type: FilterType.BOOLEAN, category: 'new', label: 'Missing Resource', operator: 'true', operatorOptions: [{ key: 'inImages', text: 'in images', value: 'true' }], icon: 'filters/click' }, // [FilterKey.CLICK_RAGE]: { key: FilterKey.CLICK_RAGE, type: FilterType.BOOLEAN, category: 'new', label: 'Click Rage', operator: 'onAnything', operatorOptions: [{ key: 'onAnything', text: 'on anything', value: 'true' }], icon: 'filters/click' }, - [FilterKey.ISSUE]: { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: 'javascript', label: 'Issue', operator: 'onAnything', operatorOptions: filterOptions, icon: 'filters/click', options: ISSUE_OPTIONS }, + [FilterKey.ISSUE]: { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', operator: 'is', operatorOptions: filterOptions, icon: 'filters/click', options: ISSUE_OPTIONS }, // [FilterKey.URL]: { / [TYPES,TYPES. category: 'interactions', label: 'URL', operator: 'is', operatorOptions: stringFilterOptions }, // [FilterKey.CUSTOM]: { / [TYPES,TYPES. category: 'interactions', label: 'Custom', operator: 'is', operatorOptions: stringFilterOptions }, // [FilterKey.METADATA]: { / [TYPES,TYPES. category: 'interactions', label: 'Metadata', operator: 'is', operatorOptions: stringFilterOptions }, @@ -233,6 +220,7 @@ export default Record({ icon: '', type: '', value: [""], + source: [""], category: '', custom: '', @@ -243,8 +231,10 @@ export default Record({ isFilter: false, actualValue: '', - operator: 'notOn', + operator: '', + sourceOperator: '=', operatorOptions: [], + sourceOptions: [], isEvent: false, index: 0, options: [],