From 05cd257e02b8fbd4052a9b758f299dc6978453ca Mon Sep 17 00:00:00 2001 From: Uku Taht Date: Mon, 17 Aug 2020 11:59:27 +0300 Subject: [PATCH 1/3] Extract referrers report --- assets/js/dashboard/historical.js | 4 +- assets/js/dashboard/stats/sources/index.js | 15 ++ .../dashboard/stats/sources/referrer-list.js | 133 ++++++++++++++++++ .../dashboard/stats/sources/search-terms.js | 89 ++++++++++++ .../js/dashboard/stats/sources/source-list.js | 108 ++++++++++++++ 5 files changed, 347 insertions(+), 2 deletions(-) create mode 100644 assets/js/dashboard/stats/sources/index.js create mode 100644 assets/js/dashboard/stats/sources/referrer-list.js create mode 100644 assets/js/dashboard/stats/sources/search-terms.js create mode 100644 assets/js/dashboard/stats/sources/source-list.js diff --git a/assets/js/dashboard/historical.js b/assets/js/dashboard/historical.js index 55124fbbbb2d..e19a1e39a968 100644 --- a/assets/js/dashboard/historical.js +++ b/assets/js/dashboard/historical.js @@ -5,7 +5,7 @@ import SiteSwitcher from './site-switcher' import Filters from './filters' import CurrentVisitors from './stats/current-visitors' import VisitorGraph from './stats/visitor-graph' -import Referrers from './stats/referrers' +import Sources from './stats/sources' import Pages from './stats/pages' import Countries from './stats/countries' import Devices from './stats/devices' @@ -35,7 +35,7 @@ export default class Historical extends React.Component {
- +
diff --git a/assets/js/dashboard/stats/sources/index.js b/assets/js/dashboard/stats/sources/index.js new file mode 100644 index 000000000000..6e32d4785ba0 --- /dev/null +++ b/assets/js/dashboard/stats/sources/index.js @@ -0,0 +1,15 @@ +import React from 'react'; +import SearchTerms from './search-terms' +import SourceList from './source-list' +import ReferrerList from './referrer-list' + +export default function Sources(props) { + console.log(props.query) + if (props.query.filters.source === 'Google') { + return + } else if (props.query.filters.source) { + return + } else { + return + } +} diff --git a/assets/js/dashboard/stats/sources/referrer-list.js b/assets/js/dashboard/stats/sources/referrer-list.js new file mode 100644 index 000000000000..9fc520b8501b --- /dev/null +++ b/assets/js/dashboard/stats/sources/referrer-list.js @@ -0,0 +1,133 @@ +import React from 'react'; +import { Link } from 'react-router-dom' +import FlipMove from 'react-flip-move'; + +import FadeIn from '../../fade-in' +import Bar from '../bar' +import MoreLink from '../more-link' +import numberFormatter from '../../number-formatter' +import * as api from '../../api' + +function LinkOption(props) { + if (props.disabled) { + return {props.children} + } else { + props = Object.assign({}, props, {className: props.className + ' hover:underline'}) + return {props.children} + } +} + +export default class Referrers extends React.Component { + constructor(props) { + super(props) + this.state = {loading: true} + } + + componentDidMount() { + this.fetchReferrers() + if (this.props.timer) this.props.timer.onTick(this.fetchReferrers.bind(this)) + } + + componentDidUpdate(prevProps) { + if (this.props.query !== prevProps.query) { + this.setState({loading: true, referrers: null}) + this.fetchReferrers() + } + } + + showNoRef() { + return this.props.query.period === 'realtime' + } + + fetchReferrers() { + if (this.props.query.filters.source) { + api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/referrers/${encodeURIComponent(this.props.query.filters.source)}`, this.props.query, {show_noref: this.showNoRef()}) + .then((res) => res.search_terms || res.referrers) + .then((referrers) => this.setState({loading: false, referrers: referrers})) + } else if (this.props.query.filters.goal) { + api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/goal/referrers`, this.props.query) + .then((res) => this.setState({loading: false, referrers: res})) + } else { + api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/referrers`, this.props.query, {show_noref: this.showNoRef()}) + .then((res) => this.setState({loading: false, referrers: res})) + } + } + + renderExternalLink(referrer) { + if (this.props.query.filters.source && this.props.query.filters.source !== 'Google' && referrer.name !== 'Direct / None') { + return ( + + + + ) + } + return null + } + + renderReferrer(referrer) { + const query = new URLSearchParams(window.location.search) + query.set('referrer', referrer.name) + + return ( +
+
+ + + + + { referrer.name } + + { this.renderExternalLink(referrer) } + +
+ {numberFormatter(referrer.count)} +
+ ) + } + + label() { + return this.props.query.period === 'realtime' ? 'Active visitors' : 'Visitors' + } + + renderList() { + if (this.state.referrers.length > 0) { + return ( + +
+ Referrer + { this.label() } +
+ + + {this.state.referrers.map(this.renderReferrer.bind(this))} + +
+ ) + } else { + return
No data yet
+ } + } + + renderContent() { + if (this.state.referrers) { + return ( + +

Top Referrers

+ { this.renderList() } + +
+ ) + } + } + + render() { + return ( +
+ { this.state.loading &&
} + + { this.renderContent() } + +
+ ) + } +} diff --git a/assets/js/dashboard/stats/sources/search-terms.js b/assets/js/dashboard/stats/sources/search-terms.js new file mode 100644 index 000000000000..e79d0d9b85d1 --- /dev/null +++ b/assets/js/dashboard/stats/sources/search-terms.js @@ -0,0 +1,89 @@ +import React from 'react'; +import FadeIn from '../../fade-in' +import Bar from '../bar' +import MoreLink from '../more-link' +import numberFormatter from '../../number-formatter' +import * as api from '../../api' + +export default class SearchTerms extends React.Component { + constructor(props) { + super(props) + this.state = {loading: true} + } + + componentDidMount() { + this.fetchSearchTerms() + } + + componentDidUpdate(prevProps) { + if (this.props.query !== prevProps.query) { + this.setState({loading: true, terms: null}) + this.fetchSearchTerms() + } + } + + fetchSearchTerms() { + api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/referrers/${encodeURIComponent(this.props.query.filters.source)}`, this.props.query) + .then((res) => res.search_terms) + .then((terms) => this.setState({loading: false, terms: terms})) + } + + renderSearchTerm(term) { + return ( +
+
+ + + + + { term.name } + + +
+ {numberFormatter(term.count)} +
+ ) + } + + renderList() { + if (this.state.terms.length > 0) { + const valLabel = this.props.query.period === 'realtime' ? 'Active visitors' : 'Visitors' + + return ( + +
+ Search term + {valLabel} +
+ + {this.state.terms.map(this.renderSearchTerm.bind(this))} +
+ ) + } else { + return
No data yet
+ } + } + + renderContent() { + if (this.state.terms) { + return ( + +

Search Terms

+ { this.renderList() } + +
+ ) + } + } + + render() { + return ( +
+ { this.state.loading &&
} + + { this.renderContent() } + +
+ ) + } +} diff --git a/assets/js/dashboard/stats/sources/source-list.js b/assets/js/dashboard/stats/sources/source-list.js new file mode 100644 index 000000000000..0c7d33929c2a --- /dev/null +++ b/assets/js/dashboard/stats/sources/source-list.js @@ -0,0 +1,108 @@ +import React from 'react'; +import { Link } from 'react-router-dom' +import FlipMove from 'react-flip-move'; + +import FadeIn from '../../fade-in' +import Bar from '../bar' +import MoreLink from '../more-link' +import numberFormatter from '../../number-formatter' +import * as api from '../../api' + +export default class Referrers extends React.Component { + constructor(props) { + super(props) + this.state = {loading: true} + } + + componentDidMount() { + this.fetchReferrers() + if (this.props.timer) this.props.timer.onTick(this.fetchReferrers.bind(this)) + } + + componentDidUpdate(prevProps) { + if (this.props.query !== prevProps.query) { + this.setState({loading: true, referrers: null}) + this.fetchReferrers() + } + } + + showNoRef() { + return this.props.query.period === 'realtime' + } + + fetchReferrers() { + if (this.props.query.filters.goal) { + api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/goal/referrers`, this.props.query) + .then((res) => this.setState({loading: false, referrers: res})) + } else { + api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/referrers`, this.props.query, {show_noref: this.showNoRef()}) + .then((res) => this.setState({loading: false, referrers: res})) + } + } + + renderReferrer(referrer) { + const query = new URLSearchParams(window.location.search) + query.set('source', referrer.name) + + return ( +
+
+ + + + + { referrer.name } + + +
+ {numberFormatter(referrer.count)} +
+ ) + } + + label() { + return this.props.query.period === 'realtime' ? 'Active visitors' : 'Visitors' + } + + renderList() { + if (this.state.referrers.length > 0) { + return ( + +
+ Source + {this.label()} +
+ + + {this.state.referrers.map(this.renderReferrer.bind(this))} + +
+ ) + } else { + return
No data yet
+ } + } + + renderContent() { + if (this.state.referrers) { + return ( + +

Top sources

+ { this.renderList() } + +
+ ) + } + } + + render() { + return ( +
+ { this.state.loading &&
} + + { this.renderContent() } + +
+ ) + } +} From 9bdd48422ed67a3ac9f375677b9b2e1935f8ce84 Mon Sep 17 00:00:00 2001 From: Uku Taht Date: Mon, 17 Aug 2020 12:01:03 +0300 Subject: [PATCH 2/3] Use new sourcs report in realtime --- assets/js/dashboard/realtime.js | 4 +- assets/js/dashboard/stats/referrers.js | 146 ------------------------- 2 files changed, 2 insertions(+), 148 deletions(-) delete mode 100644 assets/js/dashboard/stats/referrers.js diff --git a/assets/js/dashboard/realtime.js b/assets/js/dashboard/realtime.js index 5f3a3e912f49..c07b66878d70 100644 --- a/assets/js/dashboard/realtime.js +++ b/assets/js/dashboard/realtime.js @@ -5,7 +5,7 @@ import SiteSwitcher from './site-switcher' import Filters from './filters' import CurrentVisitors from './stats/current-visitors' import VisitorGraph from './stats/visitor-graph' -import Referrers from './stats/referrers' +import Sources from './stats/sources' import Pages from './stats/pages' import Countries from './stats/countries' import Devices from './stats/devices' @@ -34,7 +34,7 @@ export default class Stats extends React.Component {
- +
diff --git a/assets/js/dashboard/stats/referrers.js b/assets/js/dashboard/stats/referrers.js deleted file mode 100644 index 39bd61b49935..000000000000 --- a/assets/js/dashboard/stats/referrers.js +++ /dev/null @@ -1,146 +0,0 @@ -import React from 'react'; -import { Link } from 'react-router-dom' -import FlipMove from 'react-flip-move'; - -import FadeIn from '../fade-in' -import Bar from './bar' -import MoreLink from './more-link' -import numberFormatter from '../number-formatter' -import * as api from '../api' - -function LinkOption(props) { - if (props.disabled) { - return {props.children} - } else { - props = Object.assign({}, props, {className: props.className + ' hover:underline'}) - return {props.children} - } -} - -export default class Referrers extends React.Component { - constructor(props) { - super(props) - this.state = {loading: true} - } - - componentDidMount() { - this.fetchReferrers() - if (this.props.timer) this.props.timer.onTick(this.fetchReferrers.bind(this)) - } - - componentDidUpdate(prevProps) { - if (this.props.query !== prevProps.query) { - this.setState({loading: true, referrers: null}) - this.fetchReferrers() - } - } - - showNoRef() { - return this.props.query.period === 'realtime' - } - - fetchReferrers() { - if (this.props.query.filters.source) { - api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/referrers/${encodeURIComponent(this.props.query.filters.source)}`, this.props.query, {show_noref: this.showNoRef()}) - .then((res) => res.search_terms || res.referrers) - .then((referrers) => this.setState({loading: false, referrers: referrers})) - } else if (this.props.query.filters.goal) { - api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/goal/referrers`, this.props.query) - .then((res) => this.setState({loading: false, referrers: res})) - } else { - api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/referrers`, this.props.query, {show_noref: this.showNoRef()}) - .then((res) => this.setState({loading: false, referrers: res})) - } - } - - renderExternalLink(referrer) { - if (this.props.query.filters.source && this.props.query.filters.source !== 'Google' && referrer.name !== 'Direct / None') { - return ( - - - - ) - } - return null - } - - renderReferrer(referrer) { - const query = new URLSearchParams(window.location.search) - - if (this.props.query.filters.source) { - query.set('referrer', referrer.name) - } else { - query.set('source', referrer.name) - } - - return ( -
-
- - - - - { referrer.name } - - { this.renderExternalLink(referrer) } - -
- {numberFormatter(referrer.count)} -
- ) - } - - label() { - return this.props.query.period === 'realtime' ? 'Active visitors' : 'Visitors' - } - - renderList() { - if (this.state.referrers.length > 0) { - const source = this.props.query.filters.source - const keyLabel = source === 'Google' ? 'Search term' : source ? 'Referrer' : 'Source' - const valLabel = this.props.query.period === 'realtime' ? 'Active visitors' : 'Visitors' - - return ( - -
- { keyLabel } - { valLabel } -
- - - {this.state.referrers.map(this.renderReferrer.bind(this))} - -
- ) - } else { - return
No data yet
- } - } - - renderContent() { - const source = this.props.query.filters.source - const title = source === 'Google' ? 'Search terms' : source ? 'Top Referrers' : 'Top Sources' - const endpoint = source ? 'referrers/' + encodeURIComponent(source) : 'referrers' - - if (this.state.referrers) { - return ( - -

{title}

- { this.renderList() } - -
- ) - } - } - - render() { - return ( -
- { this.state.loading &&
} - - { this.renderContent() } - -
- ) - } -} From 33ae8a11a0d7b23a4a00b2b4a8b39fe730c040ba Mon Sep 17 00:00:00 2001 From: Uku Taht Date: Mon, 17 Aug 2020 15:07:26 +0300 Subject: [PATCH 3/3] Show appropriate messages for error conditions --- assets/js/dashboard/stats/sources/index.js | 1 - .../dashboard/stats/sources/search-terms.js | 48 +++++++++++++++---- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/assets/js/dashboard/stats/sources/index.js b/assets/js/dashboard/stats/sources/index.js index 6e32d4785ba0..716f62689c96 100644 --- a/assets/js/dashboard/stats/sources/index.js +++ b/assets/js/dashboard/stats/sources/index.js @@ -4,7 +4,6 @@ import SourceList from './source-list' import ReferrerList from './referrer-list' export default function Sources(props) { - console.log(props.query) if (props.query.filters.source === 'Google') { return } else if (props.query.filters.source) { diff --git a/assets/js/dashboard/stats/sources/search-terms.js b/assets/js/dashboard/stats/sources/search-terms.js index e79d0d9b85d1..1f73fd53dafa 100644 --- a/assets/js/dashboard/stats/sources/search-terms.js +++ b/assets/js/dashboard/stats/sources/search-terms.js @@ -3,6 +3,7 @@ import FadeIn from '../../fade-in' import Bar from '../bar' import MoreLink from '../more-link' import numberFormatter from '../../number-formatter' +import RocketIcon from '../modals/rocket-icon' import * as api from '../../api' export default class SearchTerms extends React.Component { @@ -23,16 +24,20 @@ export default class SearchTerms extends React.Component { } fetchSearchTerms() { - api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/referrers/${encodeURIComponent(this.props.query.filters.source)}`, this.props.query) - .then((res) => res.search_terms) - .then((terms) => this.setState({loading: false, terms: terms})) + api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/referrers/Google`, this.props.query) + .then((res) => this.setState({ + loading: false, + searchTerms: res.search_terms || [], + notConfigured: res.not_configured, + isOwner: res.is_owner + })) } renderSearchTerm(term) { return (
- + @@ -46,7 +51,25 @@ export default class SearchTerms extends React.Component { } renderList() { - if (this.state.terms.length > 0) { + if (this.props.query.filters.goal) { + return ( +
+ +
Sorry, we cannot show which keywords converted best for goal {this.props.query.filters.goal}
+
Google does not share this information
+
+ ) + + } else if (this.state.notConfigured) { + return ( +
+ +
The site is not connected to Google Search Keywords
+
Cannot show search terms
+ {this.state.isOwner && Connect with Google } +
+ ) + } else if (this.state.searchTerms.length > 0) { const valLabel = this.props.query.period === 'realtime' ? 'Active visitors' : 'Visitors' return ( @@ -56,21 +79,28 @@ export default class SearchTerms extends React.Component { {valLabel}
- {this.state.terms.map(this.renderSearchTerm.bind(this))} + {this.state.searchTerms.map(this.renderSearchTerm.bind(this))} ) } else { - return
No data yet
+ return ( +
+ +
Could not find any search terms for this period
+
Google Search Console data is sampled and delayed by 24-36h
+
Read more on our documentation
+
+ ) } } renderContent() { - if (this.state.terms) { + if (this.state.searchTerms) { return (

Search Terms

{ this.renderList() } - +
) }