Skip to content

Commit

Permalink
Feature/goals bar text (plausible#1165)
Browse files Browse the repository at this point in the history
* chore(docker): improve repeat contributions workflow

* This change adds two new commands to gracefully stop and remove the Postgres and Clickhouse docker containers. To do so, it also gives them a recognizable name.

* Additionally, the Postgres container is updated to use a named volume for its data. This lower friction for repeat contributions where one would otherwise sign up and activate their accounts again and again each time.

* Improve bar component to work in a variety of situations

This fixes two issues from plausible#972

- Goals area has display issues depending on the name of your custom events
- It is not possible to view labels for outbound links, 404 and custom props

* Update changelog entry

* Move content to children for Bar component

* Remove redundant fallback width

* Fix text color on root conversion texts

Hyperlinks (<a>) inherit their color. In the case of the root conversion text, we needed to specify the color somewhere.

The same color as the breakdown texts has been chosen.

The Bar component no longer needs to take in text classes.
  • Loading branch information
MaybeThisIsRu authored Jul 8, 2021
1 parent af3445c commit 9ce9264
Show file tree
Hide file tree
Showing 13 changed files with 288 additions and 95 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 23,7 @@ All notable changes to this project will be documented in this file.
- Crash when changing theme on a loaded dashboard plausible/analytics#1123
- UI fix for details button overlapping content on mobile plausible/analytics#1114
- UI fix for the main graph on mobile overlapping its tick items on both axis
- UI fixes for text not showing properly in bars across multiple lines. This hides the totals on <768px and only shows the uniques and % to accommodate the goals text too.

### Removed
- Removes AppSignal monitoring package
Expand Down
13 changes: 11 additions & 2 deletions assets/js/dashboard/stats/bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 10,20 @@ function barWidth(count, all) {
return count / maxVal * 100
}

export default function Bar({count, all, bg}) {
export default function Bar({count, all, bg, maxWidthDeduction, children}) {
const width = barWidth(count, all)

return (
<div className={bg} style={{width: width '%', height: '30px'}}>
<div
className="w-full relative"
style={{maxWidth: `calc(100% - ${maxWidthDeduction})`}}
>
<div
className={`absolute top-0 left-0 h-full ${bg || ''}`}
style={{width: `${width}%`}}
>
</div>
{children}
</div>
)
}
50 changes: 42 additions & 8 deletions assets/js/dashboard/stats/conversions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 8,32 @@ import numberFormatter from '../../number-formatter'
import * as api from '../../api'
import LazyLoader from '../../lazy-loader'

const MOBILE_UPPER_WIDTH = 767
const DEFAULT_WIDTH = 1080

export default class Conversions extends React.Component {
constructor(props) {
super(props)
this.state = {loading: true}
this.state = {
loading: true,
viewport: DEFAULT_WIDTH,
}
this.onVisible = this.onVisible.bind(this)

this.handleResize = this.handleResize.bind(this);
}

componentDidMount() {
window.addEventListener('resize', this.handleResize, false);
this.handleResize();
}

componentWillUnmount() {
window.removeEventListener('resize', this.handleResize, false);
}

handleResize() {
this.setState({ viewport: window.innerWidth });
}

onVisible() {
Expand All @@ -26,39 47,51 @@ export default class Conversions extends React.Component {
}
}

getBarMaxWidth() {
const { viewport } = this.state;
return viewport > MOBILE_UPPER_WIDTH ? "16rem" : "10rem";
}

fetchConversions() {
api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/conversions`, this.props.query)
.then((res) => this.setState({loading: false, goals: res}))
}

renderGoalText(goalName) {
if (this.props.query.period === 'realtime') {
return <span className="block px-2" style={{marginTop: '-26px'}}>{goalName}</span>
return <span className="block px-2 py-1.5 relative z-9 break-words dark:text-gray-200">{goalName}</span>
} else {
const query = new URLSearchParams(window.location.search)
query.set('goal', goalName)

return (
<Link to={{pathname: window.location.pathname, search: query.toString()}} style={{marginTop: '-26px'}} className="block px-2 hover:underline">
<Link to={{pathname: window.location.pathname, search: query.toString()}} className="block px-2 py-1.5 hover:underline relative z-9 break-words dark:text-gray-200">
{ goalName }
</Link>
)
}
}



renderGoal(goal) {
const { viewport } = this.state;
const renderProps = this.props.query.filters['goal'] == goal.name && goal.prop_names

return (
<div className="my-2 text-sm" key={goal.name}>
<div className="flex items-center justify-between my-2">
<div className="relative w-full h-8 dark:text-gray-300" style={{maxWidth: 'calc(100% - 16rem)'}}>
<Bar count={goal.count} all={this.state.goals} bg="bg-red-50 dark:bg-gray-500 dark:bg-opacity-15" />
<Bar
count={goal.count}
all={this.state.goals}
bg="bg-red-50 dark:bg-gray-500 dark:bg-opacity-15"
maxWidthDeduction={this.getBarMaxWidth()}
>
{this.renderGoalText(goal.name)}
</div>
</Bar>
<div className="dark:text-gray-200">
<span className="inline-block w-20 font-medium text-right">{numberFormatter(goal.count)}</span>
<span className="inline-block w-20 font-medium text-right">{numberFormatter(goal.total_count)}</span>
{viewport > MOBILE_UPPER_WIDTH && <span className="inline-block w-20 font-medium text-right">{numberFormatter(goal.total_count)}</span>}
<span className="inline-block w-20 font-medium text-right">{goal.conversion_rate}%</span>
</div>
</div>
Expand All @@ -68,6 101,7 @@ export default class Conversions extends React.Component {
}

renderInner() {
const { viewport } = this.state;
if (this.state.loading) {
return <div className="mx-auto my-2 loading"><div></div></div>
} else if (this.state.goals) {
Expand All @@ -78,7 112,7 @@ export default class Conversions extends React.Component {
<span>Goal</span>
<div className="text-right">
<span className="inline-block w-20">Uniques</span>
<span className="inline-block w-20">Total</span>
{viewport > MOBILE_UPPER_WIDTH && <span className="inline-block w-20">Total</span>}
<span className="inline-block w-20">CR</span>
</div>
</div>
Expand Down
68 changes: 57 additions & 11 deletions assets/js/dashboard/stats/conversions/prop-breakdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 6,9 @@ import Bar from '../bar'
import numberFormatter from '../../number-formatter'
import * as api from '../../api'

const MOBILE_UPPER_WIDTH = 767
const DEFAULT_WIDTH = 1080

export default class PropertyBreakdown extends React.Component {
constructor(props) {
super(props)
Expand All @@ -21,14 24,33 @@ export default class PropertyBreakdown extends React.Component {

this.state = {
loading: true,
propKey: propKey
propKey: propKey,
viewport: DEFAULT_WIDTH,
}

this.handleResize = this.handleResize.bind(this);
}

componentDidMount() {
window.addEventListener('resize', this.handleResize, false);

this.handleResize();
this.fetchPropBreakdown()
}

componentWillUnmount() {
window.removeEventListener('resize', this.handleResize, false);
}

handleResize() {
this.setState({ viewport: window.innerWidth });
}

getBarMaxWidth() {
const { viewport } = this.state;
return viewport > MOBILE_UPPER_WIDTH ? "16rem" : "10rem";
}

fetchPropBreakdown() {
if (this.props.query.filters['goal']) {
api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/property/${encodeURIComponent(this.state.propKey)}`, this.props.query)
Expand All @@ -46,24 68,48 @@ export default class PropertyBreakdown extends React.Component {
}
return null
}

renderPropContent(value, query) {
return (
<span className="flex px-2 py-1.5 group dark:text-gray-300 relative z-9 break-words">
<Link
to={{pathname: window.location.pathname, search: query.toString()}}
className="hover:underline block"
>
{ value.name }
</Link>
{ this.renderUrl(value) }
</span>
)
}

renderPropValue(value) {
const query = new URLSearchParams(window.location.search)
query.set('props', JSON.stringify({[this.state.propKey]: value.name}))
const { viewport } = this.state;

return (
<div className="flex items-center justify-between my-2" key={value.name}>
<div className="w-full h-8 relative" style={{maxWidth: 'calc(100% - 16rem)'}}>
<Bar count={value.count} all={this.state.breakdown} bg="bg-red-50 dark:bg-gray-500 dark:bg-opacity-15" />
<span className="flex px-2 group dark:text-gray-300" style={{marginTop: '-26px'}}>
<Link to={{pathname: window.location.pathname, search: query.toString()}} className="hover:underline block truncate">
{ value.name }
</Link>
{ this.renderUrl(value) }
</span>
</div>
<Bar
count={value.count}
all={this.state.breakdown}
bg="bg-red-50 dark:bg-gray-500 dark:bg-opacity-15"
maxWidthDeduction={this.getBarMaxWidth()}
>
{this.renderPropContent(value, query)}
</Bar>
<div className="dark:text-gray-200">
<span className="font-medium inline-block w-20 text-right">{numberFormatter(value.count)}</span>
<span className="font-medium inline-block w-20 text-right">{numberFormatter(value.total_count)}</span>
{
viewport > MOBILE_UPPER_WIDTH ?
(
<span
className="font-medium inline-block w-20 text-right"
>{numberFormatter(value.total_count)}
</span>
)
: null
}
<span className="font-medium inline-block w-20 text-right">{numberFormatter(value.conversion_rate)}%</span>
</div>
</div>
Expand Down
41 changes: 27 additions & 14 deletions assets/js/dashboard/stats/devices/browsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 12,7 @@ export default class Browsers extends React.Component {
super(props)
this.state = {loading: true}
this.onVisible = this.onVisible.bind(this)
this.renderBrowserContent = this.renderBrowserContent.bind(this)
}

componentDidUpdate(prevProps) {
Expand All @@ -36,6 37,20 @@ export default class Browsers extends React.Component {
}
}

label() {
return this.props.query.period === 'realtime' ? 'Current visitors' : 'Visitors'
}

renderBrowserContent(browser, query) {
return (
<span className="flex px-2 py-1.5 dark:text-gray-300 relative z-9 break-words">
<Link className="block hover:underline" to={{search: query.toString()}}>
{browser.name}
</Link>
</span>
)
}

renderBrowser(browser) {
const query = new URLSearchParams(window.location.search)
if (this.props.query.filters.browser) {
Expand All @@ -46,24 61,22 @@ export default class Browsers extends React.Component {

return (
<div className="flex items-center justify-between my-1 text-sm" key={browser.name}>
<div className="w-full h-8" style={{maxWidth: 'calc(100% - 6rem)'}}>
<Bar count={browser.count} all={this.state.browsers} bg="bg-green-50 dark:bg-gray-500 dark:bg-opacity-15" />
<span className="flex px-2 dark:text-gray-300" style={{marginTop: '-26px'}} >
<Link className="block truncate hover:underline" to={{search: query.toString()}}>
{browser.name}
</Link>
</span>
</div>
<span className="font-medium dark:text-gray-200">{numberFormatter(browser.count)} <span className="inline-block w-8 text-xs text-right">({browser.percentage}%)</span></span>
<Bar
count={browser.count}
all={this.state.browsers}
bg="bg-green-50 dark:bg-gray-500 dark:bg-opacity-15"
maxWidthDeduction="6rem"
>
{this.renderBrowserContent(browser, query)}
</Bar>
<span className="font-medium dark:text-gray-200">
{numberFormatter(browser.count)}
<span className="inline-block w-8 text-xs text-right">({browser.percentage}%)</span>
</span>
</div>
)
}

label() {
return this.props.query.period === 'realtime' ? 'Current visitors' : 'Visitors'
}


renderList() {
const key = this.props.query.filters.browser ? this.props.query.filters.browser ' version' : 'Browser'

Expand Down
19 changes: 9 additions & 10 deletions assets/js/dashboard/stats/devices/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,22 76,21 @@ class ScreenSizes extends React.Component {

return (
<div className="flex items-center justify-between my-1 text-sm" key={size.name}>
<div className="w-full h-8" style={{maxWidth: 'calc(100% - 6rem)'}}>
<Bar
count={size.count}
all={this.state.sizes}
bg="bg-green-50 dark:bg-gray-500 dark:bg-opacity-15"
/>
<Bar
count={size.count}
all={this.state.sizes}
bg="bg-green-50 dark:bg-gray-500 dark:bg-opacity-15"
maxWidthDeduction="6rem"
>
<span
tooltip={EXPLANATION[size.name]}
className="flex px-2 dark:text-gray-300"
style={{marginTop: '-26px'}}
className="flex px-2 py-1.5 dark:text-gray-300"
>
<Link className="block truncate hover:underline" to={{search: query.toString()}}>
<Link className="block hover:underline" to={{search: query.toString()}}>
{iconFor(size.name)} {size.name}
</Link>
</span>
</div>
</Bar>
<span
className="font-medium dark:text-gray-200"
>
Expand Down
19 changes: 13 additions & 6 deletions assets/js/dashboard/stats/devices/operating-systems.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 45,22 @@ export default class OperatingSystems extends React.Component {
}

return (
<div className="flex items-center justify-between my-1 text-sm" key={os.name}>
<div className="w-full h-8" style={{maxWidth: 'calc(100% - 6rem)'}}>
<Bar count={os.count} all={this.state.operatingSystems} bg="bg-green-50 dark:gray-500 dark:bg-opacity-15" />
<span className="flex px-2 dark:text-gray-300" style={{marginTop: '-26px'}}>
<Link className="block truncate hover:underline" to={{search: query.toString()}}>
<div
className="flex items-center justify-between my-1 text-sm"
key={os.name}
>
<Bar
count={os.count}
all={this.state.operatingSystems}
bg="bg-green-50 dark:gray-500 dark:bg-opacity-15"
maxWidthDeduction="6rem"
>
<span className="flex px-2 py-1.5 dark:text-gray-300 relative z-9 break-words">
<Link className="block hover:underline" to={{search: query.toString()}}>
{os.name}
</Link>
</span>
</div>
</Bar>
<span className="font-medium dark:text-gray-200">{numberFormatter(os.count)} <span className="inline-block w-8 text-xs text-right">({os.percentage}%)</span></span>
</div>
)
Expand Down
Loading

0 comments on commit 9ce9264

Please sign in to comment.