mirror of
https://github.com/plausible/analytics.git
synced 2024-12-22 00:51:36 +03:00
f576fa2a2c
* Add details=True to export API parameters This makes the ZIP export add `%{"details" => "True"}` to the query's `params` when fetching data internally for packaging in the ZIP. This adds bounce_rate and time_on_page to the data in pages.csv, and bounce_rate and visit_duration to sources.csv. * Make API return data with consistent names Some of the data types returned via the JSON or CSV API use inconsistent naming, and some have redundant name changes (i.e. count -> visitors -> count). This makes these all consistent and removes the redundancy. This addresses #1426, fixes some of the CSV headers, and unifies the JSON and CSV return data labels. * Update changelog * Test should use Timex.shift, not relative time * Return full country names in CSV export This also replaces the " character with ' in two country names, as those are the characters used in the names, yielding a more predictable and 'correct' output. * Fetch CSV exported data concurrently * Use spinner to indicate when export has started * Use 300 as default number of brekadown entries for export Higher numbers (e.g. 1000) seem to cause clickhouse errors when there many pages to request. It is unclear what is causing the error, as clickhouse returns an "unknown" error code and an empty error message.
147 lines
5.0 KiB
JavaScript
147 lines
5.0 KiB
JavaScript
import React from "react";
|
|
import { Link, withRouter } from 'react-router-dom'
|
|
|
|
import Modal from './modal'
|
|
import * as api from '../../api'
|
|
import numberFormatter from '../../number-formatter'
|
|
import {parseQuery, toHuman} from '../../query'
|
|
import RocketIcon from './rocket-icon'
|
|
|
|
class GoogleKeywordsModal extends React.Component {
|
|
constructor(props) {
|
|
super(props)
|
|
this.state = {
|
|
loading: true,
|
|
query: parseQuery(props.location.search, props.site)
|
|
}
|
|
}
|
|
|
|
componentDidMount() {
|
|
if (this.state.query.filters.goal) {
|
|
api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/goal/referrers/Google`, this.state.query, {limit: 100})
|
|
.then((res) => this.setState({
|
|
loading: false,
|
|
searchTerms: res.search_terms,
|
|
totalVisitors: res.total_visitors,
|
|
notConfigured: res.not_configured,
|
|
isOwner: res.is_owner
|
|
}))
|
|
} else {
|
|
api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/referrers/Google`, this.state.query, {limit: 100})
|
|
.then((res) => this.setState({
|
|
loading: false,
|
|
searchTerms: res.search_terms,
|
|
totalVisitors: res.total_visitors,
|
|
notConfigured: res.not_configured,
|
|
isOwner: res.is_owner
|
|
}))
|
|
}
|
|
}
|
|
|
|
renderTerm(term) {
|
|
return (
|
|
<React.Fragment key={term.name}>
|
|
|
|
<tr className="text-sm dark:text-gray-200" key={term.name}>
|
|
<td className="p-2 truncate">{term.name}</td>
|
|
<td className="p-2 w-32 font-medium" align="right">{numberFormatter(term.visitors)}</td>
|
|
</tr>
|
|
</React.Fragment>
|
|
)
|
|
}
|
|
|
|
renderKeywords() {
|
|
if (this.state.query.filters.goal) {
|
|
return (
|
|
<div className="text-center text-gray-700 dark:text-gray-300 mt-6">
|
|
<RocketIcon />
|
|
<div className="text-lg">Sorry, we cannot show which keywords converted best for goal <b>{this.state.query.filters.goal}</b></div>
|
|
<div className="text-lg">Google does not share this information</div>
|
|
</div>
|
|
)
|
|
} else if (this.state.notConfigured) {
|
|
if (this.state.isOwner) {
|
|
return (
|
|
<div className="text-center text-gray-700 dark:text-gray-300 mt-6">
|
|
<RocketIcon />
|
|
<div className="text-lg">The site is not connected to Google Search Keywords</div>
|
|
<div className="text-lg">Configure the integration to view search terms</div>
|
|
<a href={`/${encodeURIComponent(this.props.site.domain)}/settings/search-console`} className="button mt-4">Connect with Google</a>
|
|
</div>
|
|
)
|
|
} else {
|
|
return (
|
|
<div className="text-center text-gray-700 dark:text-gray-300 mt-6">
|
|
<RocketIcon />
|
|
<div className="text-lg">The site is not connected to Google Search Kewyords</div>
|
|
<div className="text-lg">Cannot show search terms</div>
|
|
</div>
|
|
)
|
|
}
|
|
} else if (this.state.searchTerms.length > 0) {
|
|
return (
|
|
<table className="w-max overflow-x-auto md:w-full table-striped table-fixed">
|
|
<thead>
|
|
<tr>
|
|
<th className="p-2 w-48 md:w-56 lg:w-1/3 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="left">Search Term</th>
|
|
<th className="p-2 w-32 lg:w-1/2 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">Visitors</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{this.state.searchTerms.map(this.renderTerm.bind(this))}
|
|
</tbody>
|
|
</table>
|
|
)
|
|
} else {
|
|
return (
|
|
<div className="text-center text-gray-700 dark:text-gray-300 mt-6">
|
|
<RocketIcon />
|
|
<div className="text-lg">Could not find any search terms for this period</div>
|
|
</div>
|
|
)
|
|
}
|
|
}
|
|
|
|
renderGoalText() {
|
|
if (this.state.query.filters.goal) {
|
|
return (
|
|
<h1 className="text-xl font-semibold text-gray-500 dark:text-gray-200 leading-none">completed {this.state.query.filters.goal}</h1>
|
|
)
|
|
}
|
|
}
|
|
|
|
renderBody() {
|
|
if (this.state.loading) {
|
|
return (
|
|
<div className="loading mt-32 mx-auto"><div></div></div>
|
|
)
|
|
} else {
|
|
return (
|
|
<React.Fragment>
|
|
<Link to={`/${encodeURIComponent(this.props.site.domain)}/referrers${window.location.search}`} className="font-bold text-gray-700 dark:text-gray-200 hover:underline">← All referrers</Link>
|
|
|
|
<div className="my-4 border-b border-gray-300 dark:border-gray-500"></div>
|
|
<main className="modal__content">
|
|
<h1 className="text-xl font-semibold mb-0 leading-none dark:text-gray-200">
|
|
{this.state.totalVisitors} visitors from Google<br />
|
|
{toHuman(this.state.query)}
|
|
</h1>
|
|
{this.renderGoalText()}
|
|
{ this.renderKeywords() }
|
|
</main>
|
|
</React.Fragment>
|
|
)
|
|
}
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<Modal site={this.props.site} show={!this.state.loading}>
|
|
{ this.renderBody() }
|
|
</Modal>
|
|
)
|
|
}
|
|
}
|
|
|
|
export default withRouter(GoogleKeywordsModal)
|