Support using matches/contains for most filters (#3721)

* Support using matches/contains for most filters

* Change behavior where we auto-zoom to specific browser/os/source to only do so if filtering on a single value

* No contains filtering on `location`

* Update CHANGELOG.md

* Fix merge conflict
This commit is contained in:
Karl-Aksel Puulmann 2024-02-14 13:49:17 +02:00 committed by GitHub
parent ac7da6a9d4
commit 0065cd3052
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 14 additions and 7 deletions

View File

@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
### Added ### Added
- IP Block List in Site Settings - IP Block List in Site Settings
- Allow filtering with `contains`/`matches` operator for Sources, Browsers and Operating Systems.
- Allow filtering by multiple custom properties - Allow filtering by multiple custom properties
- Wildcard and member filtering on the Stats API `event:goal` property - Wildcard and member filtering on the Stats API `event:goal` property
- Allow filtering with `contains`/`matches` operator for custom properties - Allow filtering with `contains`/`matches` operator for custom properties

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import * as storage from '../../util/storage' import * as storage from '../../util/storage'
import { isFilteringOnFixedValue } from '../../util/filters'
import ListReport from '../reports/list' import ListReport from '../reports/list'
import * as api from '../../api' import * as api from '../../api'
import * as url from '../../util/url' import * as url from '../../util/url'
@ -161,12 +162,12 @@ export default class Devices extends React.Component {
renderContent() { renderContent() {
switch (this.state.mode) { switch (this.state.mode) {
case 'browser': case 'browser':
if (this.props.query.filters.browser) { if (isFilteringOnFixedValue(this.props.query, 'browser')) {
return <BrowserVersions site={this.props.site} query={this.props.query} /> return <BrowserVersions site={this.props.site} query={this.props.query} />
} }
return <Browsers site={this.props.site} query={this.props.query} /> return <Browsers site={this.props.site} query={this.props.query} />
case 'os': case 'os':
if (this.props.query.filters.os) { if (isFilteringOnFixedValue(this.props.query, 'os')) {
return <OperatingSystemVersions site={this.props.site} query={this.props.query} /> return <OperatingSystemVersions site={this.props.site} query={this.props.query} />
} }
return <OperatingSystems site={this.props.site} query={this.props.query} /> return <OperatingSystems site={this.props.site} query={this.props.query} />

View File

@ -2,11 +2,13 @@ import React from 'react';
import SearchTerms from './search-terms' import SearchTerms from './search-terms'
import SourceList from './source-list' import SourceList from './source-list'
import ReferrerList from './referrer-list' import ReferrerList from './referrer-list'
import { isFilteringOnFixedValue } from '../../util/filters'
export default function Sources(props) { export default function Sources(props) {
if (props.query.filters.source === 'Google') { if (props.query.filters.source === 'Google') {
return <SearchTerms {...props} /> return <SearchTerms {...props} />
} else if (props.query.filters.source) { } else if (isFilteringOnFixedValue(props.query, 'source')) {
return <ReferrerList {...props} /> return <ReferrerList {...props} />
} else { } else {
return <SourceList {...props} /> return <SourceList {...props} />

View File

@ -10,9 +10,7 @@ export const FILTER_GROUPS = {
'props': ['prop_key', 'prop_value'] 'props': ['prop_key', 'prop_value']
} }
export const ALLOW_FREE_CHOICE = new Set( export const NO_CONTAINS_OPERATOR = new Set(['goal', 'screen'].concat(FILTER_GROUPS['location']))
FILTER_GROUPS['page'].concat(FILTER_GROUPS['utm']).concat(['prop_value'])
)
export const FILTER_OPERATIONS = { export const FILTER_OPERATIONS = {
isNot: 'is not', isNot: 'is not',
@ -31,7 +29,7 @@ export function supportsIsNot(filterName) {
} }
export function isFreeChoiceFilter(filterName) { export function isFreeChoiceFilter(filterName) {
return ALLOW_FREE_CHOICE.has(filterName) return !NO_CONTAINS_OPERATOR.has(filterName)
} }
// As of March 2023, Safari does not support negative lookbehind regexes. In case it throws an error, falls back to plain | matching. This means // As of March 2023, Safari does not support negative lookbehind regexes. In case it throws an error, falls back to plain | matching. This means
@ -102,6 +100,11 @@ export function parseQueryFilter(query, filter) {
return {type, clauses} return {type, clauses}
} }
export function isFilteringOnFixedValue(query, filter) {
const { type, clauses } = parseQueryFilter(query, filter)
return type == FILTER_OPERATIONS.is && clauses.length == 1
}
export function formatFilterGroup(filterGroup) { export function formatFilterGroup(filterGroup) {
if (filterGroup === 'utm') { if (filterGroup === 'utm') {
return 'UTM tags' return 'UTM tags'