diff --git a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx index c0de60d2d..49f3659da 100644 --- a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx +++ b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx @@ -15,6 +15,7 @@ interface Props { hideHeader?: boolean; emptyMessage?: any; observeChanges?: () => void; + excludeFilterKeys?: Array } function FilterSeries(props: Props) { @@ -25,6 +26,7 @@ function FilterSeries(props: Props) { hideHeader = false, emptyMessage = 'Add user event or filter to define the series by clicking Add Step.', supportsEmpty = true, + excludeFilterKeys = [] } = props; const [expanded, setExpanded] = useState(true) const { series, seriesIndex } = props; @@ -76,6 +78,7 @@ function FilterSeries(props: Props) { onRemoveFilter={onRemoveFilter} onChangeEventsOrder={onChangeEventsOrder} supportsEmpty={supportsEmpty} + excludeFilterKeys={excludeFilterKeys} /> ) : (
{emptyMessage}
@@ -86,6 +89,7 @@ function FilterSeries(props: Props) { diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx index a24db7391..d9eaf0e65 100644 --- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx +++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx @@ -19,9 +19,8 @@ import { PERFORMANCE, WEB_VITALS, } from 'App/constants/card'; -import { clickmapFilter } from 'App/types/filter/newFilter'; +import { clickmapFilter, eventKeys } from 'App/types/filter/newFilter'; import { renderClickmapThumbnail } from './renderMap'; - interface Props { history: any; match: any; @@ -51,6 +50,8 @@ function WidgetForm(props: Props) { metric.metricType ); + const excludeFilterKeys = isClickmap ? eventKeys : [] + const writeOption = ({ value, name }: { value: any; name: any }) => { value = Array.isArray(value) ? value : value.value; const obj: any = { [name]: value }; @@ -202,6 +203,7 @@ function WidgetForm(props: Props) {
metric.updateKey('hasChanged', true)} hideHeader={isTable || isClickmap} seriesIndex={index} diff --git a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx index c76cc7938..854a15b75 100644 --- a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx +++ b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx @@ -14,10 +14,11 @@ interface Props { onRemoveFilter: () => void; isFilter?: boolean; saveRequestPayloads?: boolean; - disableDelete?: boolean + disableDelete?: boolean; + excludeFilterKeys?: Array; } function FilterItem(props: Props) { - const { isFilter = false, filterIndex, filter, saveRequestPayloads, disableDelete = false } = props; + const { isFilter = false, filterIndex, filter, saveRequestPayloads, disableDelete = false, excludeFilterKeys = [] } = props; const canShowValues = !(filter.operator === 'isAny' || filter.operator === 'onAny' || filter.operator === 'isUndefined'); const isSubFilter = filter.type === FilterType.SUB_FILTERS; @@ -57,7 +58,7 @@ function FilterItem(props: Props) { {filterIndex + 1}
)} - + {/* Filter with Source */} {filter.hasSource && ( diff --git a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx index afdd02823..521a70eeb 100644 --- a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx +++ b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx @@ -13,14 +13,15 @@ interface Props { observeChanges?: () => void; saveRequestPayloads?: boolean; supportsEmpty?: boolean + excludeFilterKeys?: Array } function FilterList(props: Props) { - const { observeChanges = () => {}, filter, hideEventsOrder = false, saveRequestPayloads, supportsEmpty = true } = props; + const { observeChanges = () => {}, filter, hideEventsOrder = false, saveRequestPayloads, supportsEmpty = true, excludeFilterKeys = [] } = props; const filters = List(filter.filters); const hasEvents = filters.filter((i: any) => i.isEvent).size > 0; const hasFilters = filters.filter((i: any) => !i.isEvent).size > 0; let rowIndex = 0; - const cannotDeleteFilter = filters.size === 1 && !supportsEmpty; + const cannotDeleteFilter = hasEvents && !supportsEmpty; useEffect(observeChanges, [filters]); @@ -72,6 +73,7 @@ function FilterList(props: Props) { onRemoveFilter={() => onRemoveFilter(filterIndex)} saveRequestPayloads={saveRequestPayloads} disableDelete={cannotDeleteFilter} + excludeFilterKeys={excludeFilterKeys} /> ) : null )} @@ -92,6 +94,7 @@ function FilterList(props: Props) { filter={filter} onUpdate={(filter) => props.onUpdateFilter(filterIndex, filter)} onRemoveFilter={() => onRemoveFilter(filterIndex)} + excludeFilterKeys={excludeFilterKeys} /> ) : null )} diff --git a/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx b/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx index 5106dd8c6..fa259b9f6 100644 --- a/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx +++ b/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { Icon, Loader } from 'UI'; import { connect } from 'react-redux'; import cn from 'classnames'; @@ -6,10 +6,27 @@ import stl from './FilterModal.module.css'; import { filtersMap } from 'Types/filter/newFilter'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; +function filterJson( + jsonObj: Record, + excludeKeys: string[] = [] +): Record { + let filtered: Record = {}; + + for (const key in jsonObj) { + const arr = jsonObj[key].filter((i: any) => !excludeKeys.includes(i.key)); + if (arr.length) { + filtered[key] = arr; + } + } + + return filtered; +} + export const getMatchingEntries = (searchQuery: string, filters: Record) => { const matchingCategories: string[] = []; const matchingFilters: Record = {}; const lowerCaseQuery = searchQuery.toLowerCase(); + if (lowerCaseQuery.length === 0) return { matchingCategories: Object.keys(filters), matchingFilters: filters, @@ -33,12 +50,13 @@ export const getMatchingEntries = (searchQuery: string, filters: Record void, + onFilterClick?: (filter: any) => void, filterSearchList: any, // metaOptions: any, isMainSearch?: boolean, fetchingFilterSearchList: boolean, searchQuery?: string, + excludeFilterKeys?: Array } function FilterModal(props: Props) { const { @@ -48,6 +66,7 @@ function FilterModal(props: Props) { isMainSearch = false, fetchingFilterSearchList, searchQuery = '', + excludeFilterKeys = [] } = props; const showSearchList = isMainSearch && searchQuery.length > 0; @@ -57,7 +76,7 @@ function FilterModal(props: Props) { onFilterClick(_filter); } - const { matchingCategories, matchingFilters } = getMatchingEntries(searchQuery, filters); + const { matchingCategories, matchingFilters } = getMatchingEntries(searchQuery, filterJson(filters, excludeFilterKeys)); const isResultEmpty = (!filterSearchList || Object.keys(filterSearchList).length === 0) && matchingCategories.length === 0 && Object.keys(matchingFilters).length === 0 diff --git a/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx b/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx index d4e20d322..e78904936 100644 --- a/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx +++ b/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx @@ -3,7 +3,8 @@ import FilterModal from '../FilterModal'; import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; import { Icon } from 'UI'; import { connect } from 'react-redux'; -import { assist as assistRoute, isRoute } from "App/routes"; +import { assist as assistRoute, isRoute } from 'App/routes'; +import cn from 'classnames'; const ASSIST_ROUTE = assistRoute(); @@ -14,40 +15,54 @@ interface Props { onFilterClick: (filter: any) => void; children?: any; isLive?: boolean; + excludeFilterKeys?: Array + disabled?: boolean } function FilterSelection(props: Props) { - const { filter, onFilterClick, children } = props; + const { filter, onFilterClick, children, excludeFilterKeys = [], disabled = false } = props; const [showModal, setShowModal] = useState(false); return (
setTimeout(function() { - setShowModal(false) - }, 200)} + onClickOutside={() => + setTimeout(function () { + setShowModal(false); + }, 200) + } > - { children ? React.cloneElement(children, { onClick: (e) => { - e.stopPropagation(); - e.preventDefault(); - setShowModal(true); - }}) : ( + {children ? ( + React.cloneElement(children, { + onClick: (e) => { + e.stopPropagation(); + e.preventDefault(); + setShowModal(true); + }, + disabled: disabled + }) + ) : (
setShowModal(true)} > -
{filter.label}
+
+ {filter.label} +
- ) } + )}
{showModal && (
)} @@ -55,8 +70,11 @@ function FilterSelection(props: Props) { ); } -export default connect((state: any) => ({ - filterList: state.getIn([ 'search', 'filterList' ]), - filterListLive: state.getIn([ 'search', 'filterListLive' ]), - isLive: state.getIn([ 'sessions', 'activeTab' ]).type === 'live', -}), { })(FilterSelection); \ No newline at end of file +export default connect( + (state: any) => ({ + filterList: state.getIn(['search', 'filterList']), + filterListLive: state.getIn(['search', 'filterListLive']), + isLive: state.getIn(['sessions', 'activeTab']).type === 'live', + }), + {} +)(FilterSelection); diff --git a/frontend/app/types/filter/newFilter.js b/frontend/app/types/filter/newFilter.js index 488eb0331..2f4ffe127 100644 --- a/frontend/app/types/filter/newFilter.js +++ b/frontend/app/types/filter/newFilter.js @@ -53,6 +53,8 @@ export const filters = [ { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', placeholder: 'Select an issue', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is', 'isAny', 'isNot']), icon: 'filters/click', options: filterOptions.issueOptions }, ]; +export const eventKeys = filters.filter((i) => i.isEvent).map(i => i.key); + export const clickmapFilter = { key: FilterKey.LOCATION, type: FilterType.MULTIPLE,