mirror of
https://github.com/plausible/analytics.git
synced 2024-12-23 17:44:43 +03:00
Partially revert #3661 - just keep the real errors wrapped, but don't display anything to the user (#3677)
* Revert "Remove unused RocketIcon" This reverts commitc5e8d0c172
. * Revert "Display either hash or actual error message" This reverts commit0c091ab35f
. * Revert "Use ApiErrorNotice in funnels" This reverts commit5929de248e
. * Revert "Don't render "No data yet" when there's a NetworkError for example" This reverts commit70bee07632
. * Revert "Show the sinking shuttle notice whenever an API error occurs" This reverts commit9a62c8af2b
. * Revert "Add Hahash dependency" This reverts commitb94207ea0a
. * Remove support hash
This commit is contained in:
parent
403f559b35
commit
c1a1d697a4
@ -1,6 +1,4 @@
|
||||
import { formatISO } from './util/date'
|
||||
import React from 'react';
|
||||
import RocketIcon from './stats/modals/rocket-icon'
|
||||
import {formatISO} from './util/date'
|
||||
|
||||
let abortController = new AbortController()
|
||||
let SHARED_LINK_AUTH = null
|
||||
@ -13,23 +11,6 @@ class ApiError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export function ApiErrorNotice({ error }) {
|
||||
return (
|
||||
<div>
|
||||
<div className="text-center text-gray-900 dark:text-gray-100 mt-16 mb-16">
|
||||
<RocketIcon />
|
||||
<div className="text-lg font-bold">Oops! Our servers had trouble retrieving your data.</div>
|
||||
<div className="text-xs mt-4">If the problem persists after refreshing your browser, please <a rel="noreferrer" target="_blank" href="https://plausible.io/contact" className="underline text-indigo-400">contact support</a> with the following code:
|
||||
</div>
|
||||
<div className="mt-4 text-xs font-mono">
|
||||
{!error.payload && error.message}
|
||||
{error.payload && error.payload.support_hash}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function serialize(obj) {
|
||||
var str = [];
|
||||
for (var p in obj)
|
||||
@ -55,14 +36,14 @@ function serializeFilters(filters) {
|
||||
return JSON.stringify(cleaned)
|
||||
}
|
||||
|
||||
export function serializeQuery(query, extraQuery = []) {
|
||||
export function serializeQuery(query, extraQuery=[]) {
|
||||
const queryObj = {}
|
||||
if (query.period) { queryObj.period = query.period }
|
||||
if (query.date) { queryObj.date = formatISO(query.date) }
|
||||
if (query.from) { queryObj.from = formatISO(query.from) }
|
||||
if (query.to) { queryObj.to = formatISO(query.to) }
|
||||
if (query.filters) { queryObj.filters = serializeFilters(query.filters) }
|
||||
if (query.with_imported) { queryObj.with_imported = query.with_imported }
|
||||
if (query.period) { queryObj.period = query.period }
|
||||
if (query.date) { queryObj.date = formatISO(query.date) }
|
||||
if (query.from) { queryObj.from = formatISO(query.from) }
|
||||
if (query.to) { queryObj.to = formatISO(query.to) }
|
||||
if (query.filters) { queryObj.filters = serializeFilters(query.filters) }
|
||||
if (query.with_imported) { queryObj.with_imported = query.with_imported }
|
||||
if (SHARED_LINK_AUTH) { queryObj.auth = SHARED_LINK_AUTH }
|
||||
|
||||
if (query.comparison) {
|
||||
@ -77,11 +58,11 @@ export function serializeQuery(query, extraQuery = []) {
|
||||
return '?' + serialize(queryObj)
|
||||
}
|
||||
|
||||
export function get(url, query = {}, ...extraQuery) {
|
||||
const headers = SHARED_LINK_AUTH ? { 'X-Shared-Link-Auth': SHARED_LINK_AUTH } : {}
|
||||
export function get(url, query={}, ...extraQuery) {
|
||||
const headers = SHARED_LINK_AUTH ? {'X-Shared-Link-Auth': SHARED_LINK_AUTH} : {}
|
||||
url = url + serializeQuery(query, extraQuery)
|
||||
return fetch(url, { signal: abortController.signal, headers: headers })
|
||||
.then(response => {
|
||||
return fetch(url, {signal: abortController.signal, headers: headers})
|
||||
.then( response => {
|
||||
if (!response.ok) {
|
||||
return response.json().then((msg) => {
|
||||
throw new ApiError(msg.error, msg)
|
||||
|
@ -5,11 +5,13 @@ import FunnelTooltip from './funnel-tooltip.js'
|
||||
import ChartDataLabels from 'chartjs-plugin-datalabels'
|
||||
import numberFormatter from '../util/number-formatter'
|
||||
import Bar from '../stats/bar'
|
||||
import { ApiErrorNotice } from '../api'
|
||||
|
||||
import RocketIcon from '../stats/modals/rocket-icon'
|
||||
|
||||
import * as api from '../api'
|
||||
import LazyLoader from '../components/lazy-loader'
|
||||
|
||||
|
||||
export default function Funnel(props) {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [visible, setVisible] = useState(false)
|
||||
@ -273,7 +275,12 @@ export default function Funnel(props) {
|
||||
return (
|
||||
<>
|
||||
{header()}
|
||||
<ApiErrorNotice error={error} />
|
||||
<div className="text-center text-gray-900 dark:text-gray-100 mt-16">
|
||||
<RocketIcon />
|
||||
<div className="text-lg font-bold">Oops! Something went wrong</div>
|
||||
<div className="text-lg">{error.message ? error.message : 'Failed to render funnel'}</div>
|
||||
<div className="text-xs mt-8">Please try refreshing your browser or selecting the funnel again.</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -2,21 +2,15 @@ import React from 'react';
|
||||
import * as api from '../../api'
|
||||
import * as url from '../../util/url'
|
||||
import { escapeFilterValue } from '../../util/filters'
|
||||
import { ApiErrorNotice } from '../../api'
|
||||
|
||||
|
||||
import { CR_METRIC } from '../reports/metrics';
|
||||
import ListReport from '../reports/list';
|
||||
|
||||
export default function Conversions(props) {
|
||||
const { site, query } = props
|
||||
const [error, setError] = React.useState(undefined)
|
||||
|
||||
function fetchConversions() {
|
||||
return api.get(url.apiPath(site, '/conversions'), query, { limit: 9 })
|
||||
.catch((err) => {
|
||||
setError(err)
|
||||
})
|
||||
}
|
||||
|
||||
function getFilterFor(listItem) {
|
||||
@ -24,30 +18,24 @@ export default function Conversions(props) {
|
||||
}
|
||||
|
||||
/*global BUILD_EXTRA*/
|
||||
if (error) {
|
||||
return (
|
||||
<ApiErrorNotice error={error} />
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<ListReport
|
||||
fetchData={fetchConversions}
|
||||
getFilterFor={getFilterFor}
|
||||
keyLabel="Goal"
|
||||
onClick={props.onGoalFilterClick}
|
||||
metrics={[
|
||||
{ name: 'visitors', label: "Uniques", plot: true },
|
||||
{ name: 'events', label: "Total", hiddenOnMobile: true },
|
||||
CR_METRIC,
|
||||
BUILD_EXTRA && { name: 'total_revenue', label: 'Revenue', hiddenOnMobile: true },
|
||||
BUILD_EXTRA && { name: 'average_revenue', label: 'Average', hiddenOnMobile: true }
|
||||
]}
|
||||
detailsLink={url.sitePath(site, '/conversions')}
|
||||
maybeHideDetails={true}
|
||||
query={query}
|
||||
color="bg-red-50"
|
||||
colMinWidth={90}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<ListReport
|
||||
fetchData={fetchConversions}
|
||||
getFilterFor={getFilterFor}
|
||||
keyLabel="Goal"
|
||||
onClick={props.onGoalFilterClick}
|
||||
metrics={[
|
||||
{ name: 'visitors', label: "Uniques", plot: true },
|
||||
{ name: 'events', label: "Total", hiddenOnMobile: true },
|
||||
CR_METRIC,
|
||||
BUILD_EXTRA && { name: 'total_revenue', label: 'Revenue', hiddenOnMobile: true },
|
||||
BUILD_EXTRA && { name: 'average_revenue', label: 'Average', hiddenOnMobile: true }
|
||||
]}
|
||||
detailsLink={url.sitePath(site, '/conversions')}
|
||||
maybeHideDetails={true}
|
||||
query={query}
|
||||
color="bg-red-50"
|
||||
colMinWidth={90}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -6,13 +6,13 @@ import * as url from '../../util/url'
|
||||
import { CR_METRIC, PERCENTAGE_METRIC } from "../reports/metrics";
|
||||
import * as storage from "../../util/storage";
|
||||
import { parsePrefix, escapeFilterValue } from "../../util/filters"
|
||||
import { ApiErrorNotice } from '../../api'
|
||||
|
||||
|
||||
export default function Properties(props) {
|
||||
const { site, query } = props
|
||||
const propKeyStorageName = `prop_key__${site.domain}`
|
||||
const propKeyStorageNameForGoal = `${query.filters.goal}__prop_key__${site.domain}`
|
||||
const [error, setError] = useState(undefined)
|
||||
|
||||
const [propKey, setPropKey] = useState(choosePropKey())
|
||||
|
||||
useEffect(() => {
|
||||
@ -48,17 +48,11 @@ export default function Properties(props) {
|
||||
|
||||
function fetchProps() {
|
||||
return api.get(url.apiPath(site, `/custom-prop-values/${encodeURIComponent(propKey)}`), query)
|
||||
.catch((err) => {
|
||||
setError(err)
|
||||
})
|
||||
}
|
||||
|
||||
const fetchPropKeyOptions = useCallback(() => {
|
||||
return (input) => {
|
||||
return api.get(url.apiPath(site, "/suggestions/prop_key"), query, { q: input.trim() })
|
||||
.catch((err) => {
|
||||
setError(err)
|
||||
})
|
||||
}
|
||||
}, [query])
|
||||
|
||||
@ -77,48 +71,37 @@ export default function Properties(props) {
|
||||
|
||||
/*global BUILD_EXTRA*/
|
||||
function renderBreakdown() {
|
||||
if (error) {
|
||||
return (
|
||||
<ApiErrorNotice error={error} />
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<ListReport
|
||||
fetchData={fetchProps}
|
||||
getFilterFor={getFilterFor}
|
||||
keyLabel={propKey}
|
||||
metrics={[
|
||||
{ name: 'visitors', label: 'Visitors', plot: true },
|
||||
{ name: 'events', label: 'Events', hiddenOnMobile: true },
|
||||
query.filters.goal ? CR_METRIC : PERCENTAGE_METRIC,
|
||||
BUILD_EXTRA && { name: 'total_revenue', label: 'Revenue', hiddenOnMobile: true },
|
||||
BUILD_EXTRA && { name: 'average_revenue', label: 'Average', hiddenOnMobile: true }
|
||||
]}
|
||||
detailsLink={url.sitePath(site, `/custom-prop-values/${propKey}`)}
|
||||
maybeHideDetails={true}
|
||||
query={query}
|
||||
color="bg-red-50"
|
||||
colMinWidth={90}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<ListReport
|
||||
fetchData={fetchProps}
|
||||
getFilterFor={getFilterFor}
|
||||
keyLabel={propKey}
|
||||
metrics={[
|
||||
{ name: 'visitors', label: 'Visitors', plot: true },
|
||||
{ name: 'events', label: 'Events', hiddenOnMobile: true },
|
||||
query.filters.goal ? CR_METRIC : PERCENTAGE_METRIC,
|
||||
BUILD_EXTRA && { name: 'total_revenue', label: 'Revenue', hiddenOnMobile: true },
|
||||
BUILD_EXTRA && { name: 'average_revenue', label: 'Average', hiddenOnMobile: true }
|
||||
]}
|
||||
detailsLink={url.sitePath(site, `/custom-prop-values/${propKey}`)}
|
||||
maybeHideDetails={true}
|
||||
query={query}
|
||||
color="bg-red-50"
|
||||
colMinWidth={90}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const getFilterFor = (listItem) => { return { 'props': JSON.stringify({ [propKey]: escapeFilterValue(listItem.name) }) } }
|
||||
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'
|
||||
|
||||
if (error) {
|
||||
return (<ApiErrorNotice error={error} />)
|
||||
|
||||
} else {
|
||||
return (
|
||||
<div className="w-full mt-4">
|
||||
<div>
|
||||
<Combobox isDisabled={!!query.filters.props} boxClass={boxClass} fetchOptions={fetchPropKeyOptions()} singleOption={true} values={comboboxValues} onSelect={onPropKeySelect()} placeholder={'Select a property'} />
|
||||
</div>
|
||||
{propKey && renderBreakdown()}
|
||||
return (
|
||||
<div className="w-full mt-4">
|
||||
<div>
|
||||
<Combobox isDisabled={!!query.filters.props} boxClass={boxClass} fetchOptions={fetchPropKeyOptions()} singleOption={true} values={comboboxValues} onSelect={onPropKeySelect()} placeholder={'Select a property'} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{propKey && renderBreakdown()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -10,8 +10,6 @@ import MoreLink from '../more-link'
|
||||
import * as api from '../../api'
|
||||
import { navigateToQuery } from '../../query'
|
||||
|
||||
import { ApiErrorNotice } from '../../api'
|
||||
|
||||
class Countries extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
@ -29,7 +27,7 @@ class Countries extends React.Component {
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.query !== prevProps.query) {
|
||||
// eslint-disable-next-line react/no-did-update-set-state
|
||||
this.setState({ loading: true, countries: null })
|
||||
this.setState({loading: true, countries: null})
|
||||
this.fetchCountries().then(this.drawMap)
|
||||
}
|
||||
}
|
||||
@ -50,19 +48,19 @@ class Countries extends React.Component {
|
||||
getDataset() {
|
||||
const dataset = {};
|
||||
|
||||
var onlyValues = this.state.countries.map(function(obj) { return obj.visitors });
|
||||
var onlyValues = this.state.countries.map(function(obj){ return obj.visitors });
|
||||
var maxValue = Math.max.apply(null, onlyValues);
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const paletteScale = d3.scale.linear()
|
||||
.domain([0, maxValue])
|
||||
.domain([0,maxValue])
|
||||
.range([
|
||||
this.state.darkTheme ? "#2e3954" : "#f3ebff",
|
||||
this.state.darkTheme ? "#6366f1" : "#a779e9"
|
||||
])
|
||||
|
||||
this.state.countries.forEach(function(item) {
|
||||
dataset[item.alpha_3] = { numberOfThings: item.visitors, fillColor: paletteScale(item.visitors) };
|
||||
this.state.countries.forEach(function(item){
|
||||
dataset[item.alpha_3] = {numberOfThings: item.visitors, fillColor: paletteScale(item.visitors)};
|
||||
});
|
||||
|
||||
return dataset
|
||||
@ -70,14 +68,13 @@ class Countries extends React.Component {
|
||||
|
||||
updateCountries() {
|
||||
this.fetchCountries().then(() => {
|
||||
this.map.updateChoropleth(this.getDataset(), { reset: true })
|
||||
this.map.updateChoropleth(this.getDataset(), {reset: true})
|
||||
})
|
||||
}
|
||||
|
||||
fetchCountries() {
|
||||
return api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/countries`, this.props.query, { limit: 300 })
|
||||
.then((res) => this.setState({ loading: false, countries: res }))
|
||||
.catch((err) => this.setState({ loading: false, error: err }))
|
||||
return api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/countries`, this.props.query, {limit: 300})
|
||||
.then((res) => this.setState({loading: false, countries: res}))
|
||||
}
|
||||
|
||||
resizeMap() {
|
||||
@ -148,9 +145,9 @@ class Countries extends React.Component {
|
||||
if (this.state.countries) {
|
||||
return (
|
||||
<>
|
||||
<div className="mx-auto mt-4" style={{ width: '100%', maxWidth: '475px', height: '335px' }} id="map-container"></div>
|
||||
<div className="mx-auto mt-4" style={{width: '100%', maxWidth: '475px', height: '335px'}} id="map-container"></div>
|
||||
<MoreLink site={this.props.site} list={this.state.countries} endpoint="countries" />
|
||||
{this.geolocationDbNotice()}
|
||||
{ this.geolocationDbNotice() }
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -161,10 +158,9 @@ class Countries extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<LazyLoader onVisible={this.onVisible}>
|
||||
{this.state.loading && <div className="mx-auto my-32 loading"><div></div></div>}
|
||||
{this.state.error && <ApiErrorNotice error={this.state.error} />}
|
||||
{ this.state.loading && <div className="mx-auto my-32 loading"><div></div></div> }
|
||||
<FadeIn show={!this.state.loading}>
|
||||
{this.renderBody()}
|
||||
{ this.renderBody() }
|
||||
</FadeIn>
|
||||
</LazyLoader>
|
||||
)
|
||||
|
@ -8,7 +8,6 @@ import * as url from "../../util/url";
|
||||
import numberFormatter from '../../util/number-formatter'
|
||||
import { parseQuery } from '../../query'
|
||||
import { escapeFilterValue } from '../../util/filters'
|
||||
import { ApiErrorNotice } from '../../api'
|
||||
|
||||
/*global BUILD_EXTRA*/
|
||||
/*global require*/
|
||||
@ -25,7 +24,7 @@ const Money = maybeRequire().default
|
||||
function ConversionsModal(props) {
|
||||
const site = props.site
|
||||
const query = parseQuery(props.location.search, site)
|
||||
const [error, setError] = useState(undefined)
|
||||
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [moreResultsAvailable, setMoreResultsAvailable] = useState(false)
|
||||
const [page, setPage] = useState(1)
|
||||
@ -43,9 +42,6 @@ function ConversionsModal(props) {
|
||||
setPage(page + 1)
|
||||
setMoreResultsAvailable(res.length >= 100)
|
||||
})
|
||||
.catch((err) => {
|
||||
setError(err)
|
||||
})
|
||||
}
|
||||
|
||||
function loadMore() {
|
||||
@ -124,8 +120,7 @@ function ConversionsModal(props) {
|
||||
return (
|
||||
<Modal site={site}>
|
||||
{renderBody()}
|
||||
{error && <ApiErrorNotice error={error} />}
|
||||
{!error && loading && renderLoading()}
|
||||
{loading && renderLoading()}
|
||||
{!loading && moreResultsAvailable && renderLoadMore()}
|
||||
</Modal>
|
||||
)
|
||||
|
@ -8,8 +8,6 @@ import numberFormatter, { durationFormatter } from '../../util/number-formatter'
|
||||
import { parseQuery } from '../../query'
|
||||
import { trimURL } from '../../util/url'
|
||||
|
||||
import { ApiErrorNotice } from '../../api'
|
||||
|
||||
class EntryPagesModal extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
@ -18,8 +16,7 @@ class EntryPagesModal extends React.Component {
|
||||
query: parseQuery(props.location.search, props.site),
|
||||
pages: [],
|
||||
page: 1,
|
||||
moreResultsAvailable: false,
|
||||
error: undefined
|
||||
moreResultsAvailable: false
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,7 +39,6 @@ class EntryPagesModal extends React.Component {
|
||||
moreResultsAvailable: res.length === 100
|
||||
}))
|
||||
)
|
||||
.catch((err) => this.setState({ loading: false, error: err }))
|
||||
}
|
||||
|
||||
loadMore() {
|
||||
@ -154,7 +150,6 @@ class EntryPagesModal extends React.Component {
|
||||
return (
|
||||
<Modal site={this.props.site}>
|
||||
{this.renderBody()}
|
||||
{this.state.error && <ApiErrorNotice error={this.state.error} />}
|
||||
{this.renderLoading()}
|
||||
</Modal>
|
||||
)
|
||||
|
@ -7,9 +7,6 @@ import * as api from '../../api'
|
||||
import numberFormatter from '../../util/number-formatter'
|
||||
import { parseQuery } from '../../query'
|
||||
import { trimURL } from '../../util/url'
|
||||
|
||||
import { ApiErrorNotice } from '../../api'
|
||||
|
||||
class ExitPagesModal extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
@ -18,8 +15,7 @@ class ExitPagesModal extends React.Component {
|
||||
query: parseQuery(props.location.search, props.site),
|
||||
pages: [],
|
||||
page: 1,
|
||||
moreResultsAvailable: false,
|
||||
error: undefined
|
||||
moreResultsAvailable: false
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +28,6 @@ class ExitPagesModal extends React.Component {
|
||||
|
||||
api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/exit-pages`, query, { limit: 100, page })
|
||||
.then((res) => this.setState((state) => ({ loading: false, pages: state.pages.concat(res), moreResultsAvailable: res.length === 100 })))
|
||||
.catch((err) => this.setState({ loading: false, error: err }))
|
||||
}
|
||||
|
||||
loadMore() {
|
||||
@ -132,7 +127,6 @@ class ExitPagesModal extends React.Component {
|
||||
return (
|
||||
<Modal site={this.props.site}>
|
||||
{this.renderBody()}
|
||||
{this.state.error && <ApiErrorNotice error={this.state.error} />}
|
||||
{this.renderLoading()}
|
||||
</Modal>
|
||||
)
|
||||
|
@ -8,8 +8,6 @@ import numberFormatter, { durationFormatter } from '../../util/number-formatter'
|
||||
import { parseQuery } from '../../query'
|
||||
import { trimURL } from '../../util/url'
|
||||
|
||||
import { ApiErrorNotice } from '../../api'
|
||||
|
||||
class PagesModal extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
@ -18,8 +16,7 @@ class PagesModal extends React.Component {
|
||||
query: parseQuery(props.location.search, props.site),
|
||||
pages: [],
|
||||
page: 1,
|
||||
moreResultsAvailable: false,
|
||||
error: undefined
|
||||
moreResultsAvailable: false
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,7 +30,6 @@ class PagesModal extends React.Component {
|
||||
|
||||
api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/pages`, query, { limit: 100, page, detailed })
|
||||
.then((res) => this.setState((state) => ({ loading: false, pages: state.pages.concat(res), moreResultsAvailable: res.length === 100 })))
|
||||
.catch((err) => this.setState({ loading: false, error: err }))
|
||||
}
|
||||
|
||||
loadMore() {
|
||||
@ -140,7 +136,6 @@ class PagesModal extends React.Component {
|
||||
return (
|
||||
<Modal site={this.props.site}>
|
||||
{this.renderBody()}
|
||||
{this.state.error && <ApiErrorNotice error={this.state.error} />}
|
||||
{this.renderLoading()}
|
||||
</Modal>
|
||||
)
|
||||
|
@ -9,7 +9,6 @@ import numberFormatter from '../../util/number-formatter'
|
||||
import { parseQuery } from '../../query'
|
||||
import { specialTitleWhenGoalFilter } from "../behaviours/goal-conversions";
|
||||
import { escapeFilterValue } from "../../util/filters"
|
||||
import { ApiErrorNotice } from '../../api'
|
||||
|
||||
/*global BUILD_EXTRA*/
|
||||
/*global require*/
|
||||
@ -27,7 +26,7 @@ function PropsModal(props) {
|
||||
const site = props.site
|
||||
const query = parseQuery(props.location.search, site)
|
||||
const propKey = props.location.pathname.split('/').pop()
|
||||
const [error, setError] = useState(undefined)
|
||||
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [moreResultsAvailable, setMoreResultsAvailable] = useState(false)
|
||||
const [page, setPage] = useState(1)
|
||||
@ -45,9 +44,6 @@ function PropsModal(props) {
|
||||
setPage(page + 1)
|
||||
setMoreResultsAvailable(res.length >= 100)
|
||||
})
|
||||
.catch((err) => {
|
||||
setError(err)
|
||||
})
|
||||
}
|
||||
|
||||
function loadMore() {
|
||||
@ -127,8 +123,7 @@ function PropsModal(props) {
|
||||
return (
|
||||
<Modal site={site}>
|
||||
{renderBody()}
|
||||
{error && <ApiErrorNotice error={error} />}
|
||||
{!error && loading && renderLoading()}
|
||||
{loading && renderLoading()}
|
||||
{!loading && moreResultsAvailable && renderLoadMore()}
|
||||
</Modal>
|
||||
)
|
||||
|
@ -6,8 +6,6 @@ import * as api from '../../api'
|
||||
import numberFormatter, { durationFormatter } from '../../util/number-formatter'
|
||||
import { parseQuery } from '../../query'
|
||||
|
||||
import { ApiErrorNotice } from '../../api'
|
||||
|
||||
const TITLES = {
|
||||
sources: 'Top Sources',
|
||||
utm_mediums: 'Top UTM mediums',
|
||||
@ -25,8 +23,7 @@ class SourcesModal extends React.Component {
|
||||
sources: [],
|
||||
query: parseQuery(props.location.search, props.site),
|
||||
page: 1,
|
||||
moreResultsAvailable: false,
|
||||
error: undefined
|
||||
moreResultsAvailable: false
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,7 +34,6 @@ class SourcesModal extends React.Component {
|
||||
const detailed = this.showExtra()
|
||||
api.get(`/api/stats/${encodeURIComponent(site.domain)}/${this.currentFilter()}`, query, { limit: 100, page, detailed })
|
||||
.then((res) => this.setState({ loading: false, sources: sources.concat(res), moreResultsAvailable: res.length === 100 }))
|
||||
.catch((err) => this.setState({ loading: false, error: err }))
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -175,7 +171,6 @@ class SourcesModal extends React.Component {
|
||||
</main>
|
||||
|
||||
{this.renderLoading()}
|
||||
{this.state.error && <ApiErrorNotice error={this.state.error} />}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
@ -4,24 +4,20 @@ import { Link, withRouter } from 'react-router-dom'
|
||||
import Modal from './modal'
|
||||
import * as api from '../../api'
|
||||
import numberFormatter from '../../util/number-formatter'
|
||||
import { parseQuery } from '../../query'
|
||||
|
||||
import { ApiErrorNotice } from '../../api'
|
||||
import {parseQuery} from '../../query'
|
||||
|
||||
class ModalTable extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
loading: true,
|
||||
query: parseQuery(props.location.search, props.site),
|
||||
error: undefined
|
||||
query: parseQuery(props.location.search, props.site)
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
api.get(this.props.endpoint, this.state.query, { limit: 100 })
|
||||
.then((res) => this.setState({ loading: false, list: res }))
|
||||
.catch((err) => this.setState({ loading: false, error: err }))
|
||||
api.get(this.props.endpoint, this.state.query, {limit: 100})
|
||||
.then((res) => this.setState({loading: false, list: res}))
|
||||
}
|
||||
|
||||
label() {
|
||||
@ -38,7 +34,7 @@ class ModalTable extends React.Component {
|
||||
return (
|
||||
<tr className="text-sm dark:text-gray-200" key={tableItem.name}>
|
||||
<td className="p-2">
|
||||
<Link className="hover:underline" to={{ search: query.toString(), pathname: `/${encodeURIComponent(this.props.site.domain)}` }}>
|
||||
<Link className="hover:underline" to={{search: query.toString(), pathname: `/${encodeURIComponent(this.props.site.domain)}`}}>
|
||||
{this.props.renderIcon && this.props.renderIcon(tableItem)}
|
||||
{this.props.renderIcon && ' '}
|
||||
{tableItem.name}
|
||||
@ -47,7 +43,7 @@ class ModalTable extends React.Component {
|
||||
<td className="p-2 w-32 font-medium" align="right">
|
||||
{numberFormatter(tableItem.visitors)}
|
||||
{tableItem.percentage >= 0 &&
|
||||
<span className="inline-block text-xs w-8 pl-1 text-right">({tableItem.percentage}%)</span>}
|
||||
<span className="inline-block text-xs w-8 pl-1 text-right">({tableItem.percentage}%)</span> }
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
@ -86,7 +82,7 @@ class ModalTable extends React.Component {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.state.list.map(this.renderTableItem.bind(this))}
|
||||
{ this.state.list.map(this.renderTableItem.bind(this)) }
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
@ -100,8 +96,7 @@ class ModalTable extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Modal site={this.props.site} show={!this.state.loading}>
|
||||
{this.state.error && <ApiErrorNotice error={this.state.error} />}
|
||||
{this.renderBody()}
|
||||
{ this.renderBody() }
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
@ -9,8 +9,6 @@ import Bar from '../bar'
|
||||
import LazyLoader from '../../components/lazy-loader'
|
||||
import classNames from 'classnames'
|
||||
import { trimURL } from '../../util/url'
|
||||
import { ApiErrorNotice } from '../../api'
|
||||
|
||||
const MAX_ITEMS = 9
|
||||
const MIN_HEIGHT = 380
|
||||
const ROW_HEIGHT = 32
|
||||
@ -124,8 +122,6 @@ export default function ListReport(props) {
|
||||
}
|
||||
props.fetchData()
|
||||
.then((res) => setState({ loading: false, list: res }))
|
||||
.catch((err) => setState({ loading: false, error: err }))
|
||||
|
||||
}, [props.keyLabel, props.query])
|
||||
|
||||
const onVisible = () => { setVisible(true) }
|
||||
@ -180,9 +176,8 @@ export default function ListReport(props) {
|
||||
{maybeRenderDetailsLink()}
|
||||
</div>
|
||||
)
|
||||
} else if (!state.error) {
|
||||
return renderNoDataYet()
|
||||
}
|
||||
return renderNoDataYet()
|
||||
}
|
||||
|
||||
function renderReportHeader() {
|
||||
@ -319,7 +314,6 @@ export default function ListReport(props) {
|
||||
<LazyLoader onVisible={onVisible} >
|
||||
<div className="w-full" style={{ minHeight: `${MIN_HEIGHT}px` }}>
|
||||
{state.loading && renderLoading()}
|
||||
{state.error && <ApiErrorNotice error={state.error} />}
|
||||
{!state.loading && <FadeIn show={!state.loading} className="h-full">
|
||||
{renderReport()}
|
||||
</FadeIn>}
|
||||
|
@ -13,9 +13,7 @@ defmodule PlausibleWeb.Plugs.ErrorHandler do
|
||||
|
||||
@impl Plug.ErrorHandler
|
||||
def handle_errors(conn, %{kind: kind, reason: reason}) do
|
||||
hash = Hahash.name({kind, reason})
|
||||
Sentry.Context.set_tags_context(%{hash: hash})
|
||||
json(conn, %{error: "internal server error", support_hash: hash})
|
||||
json(conn, %{error: "internal server error"})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
3
mix.exs
3
mix.exs
@ -136,8 +136,7 @@ defmodule Plausible.MixProject do
|
||||
{:scrivener_ecto, "~> 2.0"},
|
||||
{:esbuild, "~> 0.7", runtime: Mix.env() in [:dev, :small_dev]},
|
||||
{:tailwind, "~> 0.2.0", runtime: Mix.env() in [:dev, :small_dev]},
|
||||
{:ex_json_logger, "~> 1.3.0"},
|
||||
{:hahash, "~> 0.2.0"}
|
||||
{:ex_json_logger, "~> 1.3.0"}
|
||||
]
|
||||
end
|
||||
|
||||
|
1
mix.lock
1
mix.lock
@ -61,7 +61,6 @@
|
||||
"gproc": {:hex, :gproc, "0.8.0", "cea02c578589c61e5341fce149ea36ccef236cc2ecac8691fba408e7ea77ec2f", [:rebar3], [], "hexpm", "580adafa56463b75263ef5a5df4c86af321f68694e7786cb057fd805d1e2a7de"},
|
||||
"grpcbox": {:hex, :grpcbox, "0.16.0", "b83f37c62d6eeca347b77f9b1ec7e9f62231690cdfeb3a31be07cd4002ba9c82", [:rebar3], [{:acceptor_pool, "~>1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~>0.13.0", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~>0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~>0.8.0", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "294df743ae20a7e030889f00644001370a4f7ce0121f3bbdaf13cf3169c62913"},
|
||||
"hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
|
||||
"hahash": {:hex, :hahash, "0.2.0", "5d21c44b1b65d1f591adfd20ea4720e8a81db3caa377c28b20b434b8afc2115f", [:mix], [], "hexpm", "c39ce3f6163bbcf668e7a0bef88b608a9f37f99fcf181e7a17e4d10d02a5fbbd"},
|
||||
"heroicons": {:hex, :heroicons, "0.5.3", "ee8ae8335303df3b18f2cc07f46e1cb6e761ba4cf2c901623fbe9a28c0bc51dd", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:phoenix_live_view, ">= 0.18.2", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "a210037e8a09ac17e2a0a0779d729e89c821c944434c3baa7edfc1f5b32f3502"},
|
||||
"hpack": {:hex, :hpack_erl, "0.2.3", "17670f83ff984ae6cd74b1c456edde906d27ff013740ee4d9efaa4f1bf999633", [:rebar3], [], "hexpm", "06f580167c4b8b8a6429040df36cc93bba6d571faeaec1b28816523379cbb23a"},
|
||||
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
|
||||
|
Loading…
Reference in New Issue
Block a user