Autoselect prop key (#4253)

* Autoselect propkey on opening properties breakdown

* reset propkey when query changes

* ignore custom prop filter when selecting a prop key

* throttle api requests

* Force loading state on combobox when loading propkey

* Remove artificial throttling of API requests

* Disable combobox when no prop keys are found

* drop prop_key and set loading true when query changes

* Update changelog

---------

Co-authored-by: Robert Joonas <robertjoonas16@gmail.com>
Co-authored-by: RobertJoonas <56999674+RobertJoonas@users.noreply.github.com>
This commit is contained in:
Adrian Gruntkowski 2024-06-21 10:02:39 +02:00 committed by GitHub
parent 486ee68ca8
commit a7ddc1a141
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 41 additions and 19 deletions

View File

@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.
### Added
- Icons for browsers plausible/analytics#4239
- Automatic custom property selection in the dashboard Properties report
### Removed

View File

@ -37,7 +37,7 @@ function optionId(index) {
export default function PlausibleCombobox(props) {
const [options, setOptions] = useState([])
const [loading, setLoading] = useState(false)
const [isLoading, setLoading] = useState(false)
const [isOpen, setOpen] = useState(false)
const [input, setInput] = useState('')
const [highlightedIndex, setHighlightedIndex] = useState(0)
@ -45,6 +45,8 @@ export default function PlausibleCombobox(props) {
const containerRef = useRef(null)
const listRef = useRef(null)
const loading = isLoading || !!props.forceLoading
const visibleOptions = [...options]
if (props.freeChoice && input.length > 0 && options.every(option => option.value !== input)) {
visibleOptions.push({value: input, label: input, freeChoice: true})

View File

@ -1,11 +1,12 @@
import React, { useCallback, useState } from "react"
import ListReport from "../reports/list";
import React, { useCallback, useEffect, useState } from "react"
import ListReport, { MIN_HEIGHT } from "../reports/list";
import Combobox from '../../components/combobox'
import * as api from '../../api'
import * as url from '../../util/url'
import { CR_METRIC, PERCENTAGE_METRIC } from "../reports/metrics";
import * as storage from "../../util/storage";
import { getFiltersByKeyPrefix, EVENT_PROPS_PREFIX, getPropertyKeyFromFilterKey, getGoalFilter, FILTER_OPERATIONS, hasGoalFilter } from "../../util/filters"
import { EVENT_PROPS_PREFIX, getGoalFilter, FILTER_OPERATIONS, hasGoalFilter } from "../../util/filters"
import classNames from "classnames";
export default function Properties(props) {
@ -16,7 +17,8 @@ export default function Properties(props) {
return `${goal}__prop_key__${site.domain}`
}
const [propKey, setPropKey] = useState(choosePropKey())
const [propKey, setPropKey] = useState(null)
const [propKeyLoading, setPropKeyLoading] = useState(true)
function singleGoalFilterApplied() {
const goalFilter = getGoalFilter(query)
@ -28,15 +30,26 @@ export default function Properties(props) {
}
}
function choosePropKey() {
const propFilters = getFiltersByKeyPrefix(query, EVENT_PROPS_PREFIX)
if (propFilters.length > 0) {
const [_operation, filterKey, _clauses] = propFilters[0]
return getPropertyKeyFromFilterKey(filterKey)
} else {
return getPropKeyFromStorage()
}
}
useEffect(() => {
setPropKeyLoading(true)
setPropKey(null)
fetchPropKeyOptions()("").then((propKeys) => {
const propKeyValues = propKeys.map(entry => entry.value)
if (propKeyValues.length > 0) {
const storedPropKey = getPropKeyFromStorage()
if (propKeyValues.includes(storedPropKey)) {
setPropKey(storedPropKey)
} else {
setPropKey(propKeys[0].value)
}
}
setPropKeyLoading(false)
})
}, [query])
function getPropKeyFromStorage() {
if (singleGoalFilterApplied()) {
@ -99,13 +112,19 @@ export default function Properties(props) {
filter: ["is", `${EVENT_PROPS_PREFIX}${propKey}`, [listItem.name]]
})
const comboboxDisabled = !propKeyLoading && !propKey
const comboboxPlaceholder = comboboxDisabled ? 'No custom properties found' : ''
const comboboxValues = propKey ? [{ value: propKey, label: propKey }] : []
const boxClass = 'pl-2 pr-8 py-1 bg-transparent dark:text-gray-300 rounded-md shadow-sm border border-gray-300 dark:border-gray-500'
const boxClass = classNames('pl-2 pr-8 py-1 bg-transparent dark:text-gray-300 rounded-md shadow-sm border border-gray-300 dark:border-gray-500', {
'pointer-events-none': comboboxDisabled
})
const COMBOBOX_HEIGHT = 40
return (
<div className="w-full mt-4">
<div>
<Combobox boxClass={boxClass} fetchOptions={fetchPropKeyOptions()} singleOption={true} values={comboboxValues} onSelect={onPropKeySelect()} placeholder={'Select a property'} />
<div className="w-full mt-4" style={{ minHeight: `${COMBOBOX_HEIGHT + MIN_HEIGHT}px` }}>
<div style={{ minHeight: `${COMBOBOX_HEIGHT}px` }}>
<Combobox boxClass={boxClass} forceLoading={propKeyLoading} fetchOptions={fetchPropKeyOptions()} singleOption={true} values={comboboxValues} onSelect={onPropKeySelect()} placeholder={comboboxPlaceholder} />
</div>
{propKey && renderBreakdown()}
</div>

View File

@ -11,7 +11,7 @@ import classNames from 'classnames'
import { trimURL, updatedQuery } from '../../util/url'
import { cleanLabels, hasGoalFilter, replaceFilterByPrefix } from '../../util/filters'
const MAX_ITEMS = 9
const MIN_HEIGHT = 380
export const MIN_HEIGHT = 380
const ROW_HEIGHT = 32
const ROW_GAP_HEIGHT = 4
const DATA_CONTAINER_HEIGHT = (ROW_HEIGHT + ROW_GAP_HEIGHT) * (MAX_ITEMS - 1) + ROW_HEIGHT