Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement filtering for imported data #4118

Merged
merged 41 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
19e9736
move imported.ex to imported subfolder
RobertJoonas May 13, 2024
41df563
move constructing base imported query into a separate module
RobertJoonas May 13, 2024
2491c7f
Implement imported table deciding and filtering
RobertJoonas May 14, 2024
02001aa
add top stats test with country filter
RobertJoonas May 14, 2024
c0c1d36
add timeseries test
RobertJoonas May 15, 2024
c49f8f8
Drop bounce_rate and time_on_page from imported & page-filtered Top S…
RobertJoonas May 15, 2024
f38cc04
rename field returned by top stats
RobertJoonas May 20, 2024
961d0e8
turn pages into a fn comp
RobertJoonas May 21, 2024
a59cc5b
Move dashboard API results under a results key
RobertJoonas May 21, 2024
453cdab
extend ListReport component with an optional afterFetchData prop
RobertJoonas May 21, 2024
1238437
turn Devices into a fn comp
RobertJoonas May 21, 2024
3a6c229
add not_requested as a skip_imported_reason
RobertJoonas May 21, 2024
ad5d16f
display warning icons in the dashboard
RobertJoonas May 21, 2024
92580e2
Implement filtering suggestions and translate filter fields for imported
zoldar May 24, 2024
cec119c
WIP
zoldar May 27, 2024
cb40a28
Improve and cover filtering suggestions with tests
zoldar May 27, 2024
a7dadb8
Rename imported suggestions query helpers
zoldar May 27, 2024
6039e7d
fix screen size breakdown with screen size filter
RobertJoonas May 27, 2024
6982dbe
support filtering by the same suggestion property
RobertJoonas May 27, 2024
b652cbb
support location filters when fetching location suggestions
RobertJoonas May 27, 2024
82ba93d
support filtering by multiple props from the same table
RobertJoonas May 28, 2024
39fec37
Implement filtering by goals
RobertJoonas May 28, 2024
a86219f
Make views per visit metric work for import entry and exit pages
zoldar May 28, 2024
502b7ae
Get rid of circular dependencies between Stats.Imported and Stats.Imp…
zoldar May 29, 2024
222a20d
Clean up Query struct manipulation in Breakdown
zoldar May 29, 2024
08e7284
Rename helper function for clarity
zoldar May 29, 2024
431b4a8
Automatically refresh query struct state after modifications
zoldar May 29, 2024
22d11a6
Shutup credo
zoldar May 29, 2024
09bc702
display imported warning bubble in prop breakdown section
RobertJoonas May 29, 2024
c9f6a6f
Render warning bubble for funnels whenever imported data is in the view
RobertJoonas May 29, 2024
4962ba4
Transform any operator on respective goal filters
zoldar May 29, 2024
6596596
Fix percentage and conversion_rate calculation in presence of custom …
zoldar May 29, 2024
17564db
add tests for for combining page and pageview goal filters
RobertJoonas May 30, 2024
95b5a90
add skip_refresh option to query tweaking functions
RobertJoonas May 30, 2024
a027cfe
add imported CR support for timeseries
RobertJoonas May 30, 2024
e8c43d6
still show url breakdown when special goal + url in filter
RobertJoonas May 30, 2024
7039fec
rename Query.refresh
RobertJoonas May 31, 2024
29bc847
use flat_map instead of map and concat
RobertJoonas May 31, 2024
905687a
fix darkmode color
RobertJoonas May 31, 2024
4e932dd
Handle invalid imported region codes in suggestions gracefully
zoldar Jun 3, 2024
7b610e3
Add an entry to CHANGELOG.md
zoldar Jun 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
### Added

- Snippet integration verification
- Limited filtering support for imported data in the dashboard and via Stats API

### Removed

Expand Down
4 changes: 2 additions & 2 deletions assets/js/dashboard/historical.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function Historical(props) {
<ComparisonInput site={props.site} query={props.query} />
</div>
</div>
<VisitorGraph site={props.site} query={props.query} />
<VisitorGraph site={props.site} query={props.query} updateImportedDataInView={props.updateImportedDataInView}/>

<div className="w-full md:flex">
<div className={ statsBoxClass }>
Expand All @@ -51,7 +51,7 @@ function Historical(props) {
</div>
</div>

<Behaviours site={props.site} query={props.query} currentUserRole={props.currentUserRole} />
<Behaviours site={props.site} query={props.query} currentUserRole={props.currentUserRole} importedDataInView={props.importedDataInView}/>
</div>
)
}
Expand Down
8 changes: 7 additions & 1 deletion assets/js/dashboard/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ class Dashboard extends React.Component {
constructor(props) {
super(props)
this.updateLastLoadTimestamp = this.updateLastLoadTimestamp.bind(this)
this.updateImportedDataInView = this.updateImportedDataInView.bind(this)
this.state = {
query: parseQuery(props.location.search, this.props.site),
importedDataInView: false,
lastLoadTimestamp: new Date()
}
}
Expand All @@ -35,14 +37,18 @@ class Dashboard extends React.Component {
this.setState({lastLoadTimestamp: new Date()})
}

updateImportedDataInView(newBoolean) {
this.setState({importedDataInView: newBoolean})
}

render() {
const { site, loggedIn, currentUserRole } = this.props
const { query, lastLoadTimestamp } = this.state

if (this.state.query.period === 'realtime') {
return <Realtime site={site} loggedIn={loggedIn} currentUserRole={currentUserRole} query={query} lastLoadTimestamp={lastLoadTimestamp}/>
} else {
return <Historical site={site} loggedIn={loggedIn} currentUserRole={currentUserRole} query={query} lastLoadTimestamp={lastLoadTimestamp}/>
return <Historical site={site} loggedIn={loggedIn} currentUserRole={currentUserRole} query={query} lastLoadTimestamp={lastLoadTimestamp} importedDataInView={this.state.importedDataInView} updateImportedDataInView={this.updateImportedDataInView}/>
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion assets/js/dashboard/stats/behaviours/conversions.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { CR_METRIC } from '../reports/metrics';
import ListReport from '../reports/list';

export default function Conversions(props) {
const { site, query } = props
const { site, query, afterFetchData } = props

function fetchConversions() {
return api.get(url.apiPath(site, '/conversions'), query, { limit: 9 })
Expand All @@ -23,6 +23,7 @@ export default function Conversions(props) {
return (
<ListReport
fetchData={fetchConversions}
afterFetchData={afterFetchData}
getFilterFor={getFilterFor}
keyLabel="Goal"
onClick={props.onGoalFilterClick}
Expand Down
9 changes: 5 additions & 4 deletions assets/js/dashboard/stats/behaviours/goal-conversions.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function specialTitleWhenGoalFilter(query, defaultTitle) {
}

function SpecialPropBreakdown(props) {
const { site, query, prop } = props
const { site, query, prop, afterFetchData } = props

function fetchData() {
return api.get(url.apiPath(site, `/custom-prop-values/${prop}`), query)
Expand All @@ -55,6 +55,7 @@ function SpecialPropBreakdown(props) {
return (
<ListReport
fetchData={fetchData}
afterFetchData={afterFetchData}
getFilterFor={getFilterFor}
keyLabel={prop}
metrics={[
Expand All @@ -73,12 +74,12 @@ function SpecialPropBreakdown(props) {
}

export default function GoalConversions(props) {
const {site, query} = props
const {site, query, afterFetchData} = props

const specialGoal = getSpecialGoal(query)
if (specialGoal) {
return <SpecialPropBreakdown site={site} query={props.query} prop={specialGoal.prop} />
return <SpecialPropBreakdown site={site} query={props.query} prop={specialGoal.prop} afterFetchData={afterFetchData} />
} else {
return <Conversions site={site} query={props.query} onGoalFilterClick={props.onGoalFilterClick}/>
return <Conversions site={site} query={props.query} onGoalFilterClick={props.onGoalFilterClick} afterFetchData={afterFetchData} />
}
}
24 changes: 18 additions & 6 deletions assets/js/dashboard/stats/behaviours/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Menu, Transition } from '@headlessui/react'
import { ChevronDownIcon } from '@heroicons/react/20/solid'
import classNames from 'classnames'
import * as storage from '../../util/storage'

import ImportedQueryUnsupportedWarning from '../imported-query-unsupported-warning'
import GoalConversions, { specialTitleWhenGoalFilter } from './goal-conversions'
import Properties from './props'
import { FeatureSetupNotice } from '../../components/notice'
Expand Down Expand Up @@ -48,6 +48,8 @@ export default function Behaviours(props) {

const [showingPropsForGoalFilter, setShowingPropsForGoalFilter] = useState(false)

const [importedQueryUnsupported, setImportedQueryUnsupported] = useState(false)

const onGoalFilterClick = useCallback((e) => {
const goalName = e.target.innerHTML
const isSpecialGoal = Object.keys(SPECIAL_GOALS).includes(goalName)
Expand Down Expand Up @@ -170,9 +172,14 @@ export default function Behaviours(props) {
)
}

function afterFetchData(apiResponse) {
const unsupportedQuery = apiResponse.skip_imported_reason === 'unsupported_query'
setImportedQueryUnsupported(unsupportedQuery && !isRealtime())
}

function renderConversions() {
if (site.hasGoals) {
return <GoalConversions site={site} query={query} onGoalFilterClick={onGoalFilterClick} />
return <GoalConversions site={site} query={query} onGoalFilterClick={onGoalFilterClick} afterFetchData={afterFetchData}/>
}
else if (adminAccess) {
return (
Expand Down Expand Up @@ -224,7 +231,7 @@ export default function Behaviours(props) {

function renderProps() {
if (site.hasProps && site.propsAvailable) {
return <Properties site={site} query={query} />
return <Properties site={site} query={query} afterFetchData={afterFetchData}/>
} else if (adminAccess) {
let callToAction

Expand Down Expand Up @@ -330,9 +337,14 @@ export default function Behaviours(props) {
<div className="items-start justify-between block w-full mt-6 md:flex">
<div className="w-full p-4 bg-white rounded shadow-xl dark:bg-gray-825">
<div className="flex justify-between w-full">
<h3 className="font-bold dark:text-gray-100">
{sectionTitle() + (isRealtime() ? ' (last 30min)' : '')}
</h3>
<div className="flex gap-x-1">
<h3 className="font-bold dark:text-gray-100">
{sectionTitle() + (isRealtime() ? ' (last 30min)' : '')}
</h3>
<ImportedQueryUnsupportedWarning condition={mode === CONVERSIONS && importedQueryUnsupported}/>
<ImportedQueryUnsupportedWarning condition={mode === PROPS && importedQueryUnsupported} message="Imported data is unavailable in this view"/>
<ImportedQueryUnsupportedWarning condition={mode === FUNNELS && props.importedDataInView} message="Imported data is unavailable in this view"/>
</div>
{tabs()}
</div>
{renderContent()}
Expand Down
1 change: 1 addition & 0 deletions assets/js/dashboard/stats/behaviours/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export default function Properties(props) {
return (
<ListReport
fetchData={fetchProps}
afterFetchData={props.afterFetchData}
getFilterFor={getFilterFor}
keyLabel={propKey}
metrics={[
Expand Down
97 changes: 51 additions & 46 deletions assets/js/dashboard/stats/devices/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React from 'react';

import React, {useState} from 'react';
import * as storage from '../../util/storage'
import { getFiltersByKeyPrefix, isFilteringOnFixedValue } from '../../util/filters'
import ListReport from '../reports/list'
import * as api from '../../api'
import * as url from '../../util/url'
import { VISITORS_METRIC, PERCENTAGE_METRIC, maybeWithCR } from '../reports/metrics';
import ImportedQueryUnsupportedWarning from '../imported-query-unsupported-warning';

function Browsers({ query, site }) {
function Browsers({ query, site, afterFetchData }) {
function fetchData() {
return api.get(url.apiPath(site, '/browsers'), query)
}
Expand All @@ -22,6 +22,7 @@ function Browsers({ query, site }) {
return (
<ListReport
fetchData={fetchData}
afterFetchData={afterFetchData}
getFilterFor={getFilterFor}
keyLabel="Browser"
metrics={maybeWithCR([VISITORS_METRIC, PERCENTAGE_METRIC], query)}
Expand All @@ -30,7 +31,7 @@ function Browsers({ query, site }) {
)
}

function BrowserVersions({ query, site }) {
function BrowserVersions({ query, site, afterFetchData }) {
function fetchData() {
return api.get(url.apiPath(site, '/browser-versions'), query)
}
Expand All @@ -48,6 +49,7 @@ function BrowserVersions({ query, site }) {
return (
<ListReport
fetchData={fetchData}
afterFetchData={afterFetchData}
getFilterFor={getFilterFor}
keyLabel="Browser version"
metrics={maybeWithCR([VISITORS_METRIC, PERCENTAGE_METRIC], query)}
Expand All @@ -57,7 +59,7 @@ function BrowserVersions({ query, site }) {

}

function OperatingSystems({ query, site }) {
function OperatingSystems({ query, site, afterFetchData }) {
function fetchData() {
return api.get(url.apiPath(site, '/operating-systems'), query)
}
Expand All @@ -72,6 +74,7 @@ function OperatingSystems({ query, site }) {
return (
<ListReport
fetchData={fetchData}
afterFetchData={afterFetchData}
getFilterFor={getFilterFor}
keyLabel="Operating system"
metrics={maybeWithCR([VISITORS_METRIC, PERCENTAGE_METRIC], query)}
Expand All @@ -80,7 +83,7 @@ function OperatingSystems({ query, site }) {
)
}

function OperatingSystemVersions({ query, site }) {
function OperatingSystemVersions({ query, site, afterFetchData }) {
function fetchData() {
return api.get(url.apiPath(site, '/operating-system-versions'), query)
}
Expand All @@ -98,6 +101,7 @@ function OperatingSystemVersions({ query, site }) {
return (
<ListReport
fetchData={fetchData}
afterFetchData={afterFetchData}
getFilterFor={getFilterFor}
keyLabel="Operating System Version"
metrics={maybeWithCR([VISITORS_METRIC, PERCENTAGE_METRIC], query)}
Expand All @@ -107,7 +111,7 @@ function OperatingSystemVersions({ query, site }) {

}

function ScreenSizes({ query, site }) {
function ScreenSizes({ query, site, afterFetchData }) {
function fetchData() {
return api.get(url.apiPath(site, '/screen-sizes'), query)
}
Expand All @@ -126,6 +130,7 @@ function ScreenSizes({ query, site }) {
return (
<ListReport
fetchData={fetchData}
afterFetchData={afterFetchData}
getFilterFor={getFilterFor}
keyLabel="Screen size"
metrics={maybeWithCR([VISITORS_METRIC, PERCENTAGE_METRIC], query)}
Expand Down Expand Up @@ -157,45 +162,44 @@ function iconFor(screenSize) {
}
}

export default class Devices extends React.Component {
constructor(props) {
super(props)
this.tabKey = `deviceTab__${props.site.domain}`
const storedTab = storage.getItem(this.tabKey)
this.state = {
mode: storedTab || 'browser'
}
export default function Devices(props) {
const {site, query} = props
const tabKey = `deviceTab__${site.domain}`
const storedTab = storage.getItem(tabKey)
const [mode, setMode] = useState(storedTab || 'browser')
const [importedQueryUnsupported, setImportedQueryUnsupported] = useState(false)

function switchTab(mode) {
storage.setItem(tabKey, mode)
setMode(mode)
}

setMode(mode) {
return () => {
storage.setItem(this.tabKey, mode)
this.setState({ mode })
}
function afterFetchData(apiResponse) {
const unsupportedQuery = apiResponse.skip_imported_reason === 'unsupported_query'
const isRealtime = query.period === 'realtime'
setImportedQueryUnsupported(unsupportedQuery && !isRealtime)
}

renderContent() {
switch (this.state.mode) {
function renderContent() {
switch (mode) {
case 'browser':
if (isFilteringOnFixedValue(this.props.query, 'browser')) {
return <BrowserVersions site={this.props.site} query={this.props.query} />
if (isFilteringOnFixedValue(query, 'browser')) {
return <BrowserVersions site={site} query={query} afterFetchData={afterFetchData} />
}
return <Browsers site={this.props.site} query={this.props.query} />
return <Browsers site={site} query={query} afterFetchData={afterFetchData} />
case 'os':
if (isFilteringOnFixedValue(this.props.query, 'os')) {
return <OperatingSystemVersions site={this.props.site} query={this.props.query} />
if (isFilteringOnFixedValue(query, 'os')) {
return <OperatingSystemVersions site={site} query={query} afterFetchData={afterFetchData} />
}
return <OperatingSystems site={this.props.site} query={this.props.query} />
return <OperatingSystems site={site} query={query} afterFetchData={afterFetchData} />
case 'size':
default:
return (
<ScreenSizes site={this.props.site} query={this.props.query} />
)
return <ScreenSizes site={site} query={query} afterFetchData={afterFetchData} />
}
}

renderPill(name, mode) {
const isActive = this.state.mode === mode
function renderPill(name, pill) {
const isActive = mode === pill

if (isActive) {
return (
Expand All @@ -210,28 +214,29 @@ export default class Devices extends React.Component {
return (
<button
className="cursor-pointer hover:text-indigo-600"
onClick={this.setMode(mode)}
onClick={() => switchTab(pill)}
>
{name}
</button>
)
}

render() {
return (
<div>
<div className="flex justify-between w-full">
return (
<div>
<div className="flex justify-between w-full">
<div className="flex gap-x-1">
<h3 className="font-bold dark:text-gray-100">Devices</h3>
<div className="flex text-xs font-medium text-gray-500 dark:text-gray-400 space-x-2">
{this.renderPill('Browser', 'browser')}
{this.renderPill('OS', 'os')}
{this.renderPill('Size', 'size')}
</div>
<ImportedQueryUnsupportedWarning condition={importedQueryUnsupported}/>
</div>
<div className="flex text-xs font-medium text-gray-500 dark:text-gray-400 space-x-2">
{renderPill('Browser', 'browser')}
{renderPill('OS', 'os')}
{renderPill('Size', 'size')}
</div>
{this.renderContent()}
</div>
)
}
{renderContent()}
</div>
)
}

function getSingleFilter(query, filterKey) {
Expand Down
3 changes: 3 additions & 0 deletions assets/js/dashboard/stats/graph/visitor-graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ export default function VisitorGraph(props) {
function fetchTopStatsAndGraphData() {
fetchTopStats(site, query)
.then((res) => {
if (props.updateImportedDataInView) {
props.updateImportedDataInView(res.includes_imported)
}
setTopStatData(res)
setTopStatsLoading(false)
})
Expand Down
Loading
Loading